conson-xp 1.1.0__py3-none-any.whl → 1.3.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.1.0.dist-info → conson_xp-1.3.0.dist-info}/METADATA +1 -5
- conson_xp-1.3.0.dist-info/RECORD +164 -0
- xp/__init__.py +4 -3
- xp/cli/__init__.py +1 -1
- xp/cli/commands/__init__.py +1 -2
- xp/cli/commands/conbus/conbus.py +9 -37
- xp/cli/commands/conbus/conbus_actiontable_commands.py +26 -4
- xp/cli/commands/conbus/conbus_autoreport_commands.py +58 -30
- xp/cli/commands/conbus/conbus_blink_commands.py +61 -29
- xp/cli/commands/conbus/conbus_config_commands.py +10 -5
- xp/cli/commands/conbus/conbus_custom_commands.py +16 -5
- xp/cli/commands/conbus/conbus_datapoint_commands.py +32 -10
- xp/cli/commands/conbus/conbus_discover_commands.py +20 -7
- xp/cli/commands/conbus/conbus_lightlevel_commands.py +114 -39
- xp/cli/commands/conbus/conbus_linknumber_commands.py +50 -25
- xp/cli/commands/conbus/conbus_msactiontable_commands.py +36 -5
- xp/cli/commands/conbus/conbus_output_commands.py +52 -14
- xp/cli/commands/conbus/conbus_raw_commands.py +17 -6
- xp/cli/commands/conbus/conbus_receive_commands.py +20 -10
- xp/cli/commands/conbus/conbus_scan_commands.py +17 -4
- xp/cli/commands/file_commands.py +35 -18
- xp/cli/commands/homekit/homekit.py +14 -8
- xp/cli/commands/homekit/homekit_start_commands.py +8 -6
- xp/cli/commands/module_commands.py +38 -23
- xp/cli/commands/reverse_proxy_commands.py +27 -19
- 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 -3
- 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 +25 -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/conbus/conbus_writeconfig.py +60 -0
- 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_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 +42 -18
- xp/services/conbus/conbus_discover_service.py +43 -7
- 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 +44 -18
- xp/services/conbus/write_config_service.py +193 -0
- 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 +110 -16
- 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_datapoint_service.py +70 -0
- 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 +4 -47
- xp/utils/serialization.py +6 -0
- xp/utils/time_utils.py +6 -11
- conson_xp-1.1.0.dist-info/RECORD +0 -181
- xp/api/__init__.py +0 -1
- xp/api/main.py +0 -110
- xp/api/models/__init__.py +0 -1
- xp/api/models/api.py +0 -20
- xp/api/models/discover.py +0 -21
- xp/api/routers/__init__.py +0 -17
- xp/api/routers/conbus.py +0 -5
- xp/api/routers/conbus_blink.py +0 -105
- xp/api/routers/conbus_custom.py +0 -63
- xp/api/routers/conbus_datapoint.py +0 -67
- xp/api/routers/conbus_output.py +0 -147
- xp/api/routers/errors.py +0 -37
- xp/cli/commands/api.py +0 -16
- xp/cli/commands/api_start_commands.py +0 -126
- xp/services/conbus/conbus_autoreport_get_service.py +0 -85
- xp/services/conbus/conbus_autoreport_set_service.py +0 -128
- xp/services/conbus/conbus_lightlevel_get_service.py +0 -101
- xp/services/conbus/conbus_lightlevel_set_service.py +0 -205
- xp/services/conbus/conbus_linknumber_get_service.py +0 -86
- xp/services/conbus/conbus_linknumber_set_service.py +0 -155
- {conson_xp-1.1.0.dist-info → conson_xp-1.3.0.dist-info}/WHEEL +0 -0
- {conson_xp-1.1.0.dist-info → conson_xp-1.3.0.dist-info}/entry_points.txt +0 -0
- {conson_xp-1.1.0.dist-info → conson_xp-1.3.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -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}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
"""HomeKit Dimming Light Accessory.
|
|
2
|
+
|
|
3
|
+
This module provides a dimming light accessory for HomeKit integration.
|
|
4
|
+
"""
|
|
5
|
+
|
|
1
6
|
import logging
|
|
2
7
|
|
|
3
8
|
from bubus import EventBus
|
|
@@ -16,7 +21,20 @@ from xp.models.protocol.conbus_protocol import (
|
|
|
16
21
|
|
|
17
22
|
|
|
18
23
|
class DimmingLight(Accessory):
|
|
19
|
-
"""
|
|
24
|
+
"""HomeKit dimming light accessory.
|
|
25
|
+
|
|
26
|
+
Attributes:
|
|
27
|
+
category: HomeKit category (CATEGORY_LIGHTBULB).
|
|
28
|
+
event_bus: Event bus for inter-service communication.
|
|
29
|
+
logger: Logger instance.
|
|
30
|
+
identifier: Unique identifier for the accessory.
|
|
31
|
+
accessory: Accessory configuration.
|
|
32
|
+
module: Module configuration.
|
|
33
|
+
is_on: Current on/off state.
|
|
34
|
+
brightness: Current brightness level (0-100).
|
|
35
|
+
char_on: On characteristic.
|
|
36
|
+
char_brightness: Brightness characteristic.
|
|
37
|
+
"""
|
|
20
38
|
|
|
21
39
|
category = CATEGORY_LIGHTBULB
|
|
22
40
|
event_bus: EventBus
|
|
@@ -28,6 +46,14 @@ class DimmingLight(Accessory):
|
|
|
28
46
|
accessory: HomekitAccessoryConfig,
|
|
29
47
|
event_bus: EventBus,
|
|
30
48
|
):
|
|
49
|
+
"""Initialize the dimming light accessory.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
driver: HAP accessory driver.
|
|
53
|
+
module: Module configuration.
|
|
54
|
+
accessory: Accessory configuration.
|
|
55
|
+
event_bus: Event bus for inter-service communication.
|
|
56
|
+
"""
|
|
31
57
|
super().__init__(driver, accessory.description)
|
|
32
58
|
|
|
33
59
|
self.logger = logging.getLogger(__name__)
|
|
@@ -76,6 +102,11 @@ class DimmingLight(Accessory):
|
|
|
76
102
|
)
|
|
77
103
|
|
|
78
104
|
def set_on(self, value: bool) -> None:
|
|
105
|
+
"""Set the on/off state of the dimming light.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
value: True to turn on, False to turn off.
|
|
109
|
+
"""
|
|
79
110
|
# Emit set event
|
|
80
111
|
self.logger.debug(f"set_on {value}")
|
|
81
112
|
|
|
@@ -93,6 +124,11 @@ class DimmingLight(Accessory):
|
|
|
93
124
|
)
|
|
94
125
|
|
|
95
126
|
def get_on(self) -> bool:
|
|
127
|
+
"""Get the on/off state of the dimming light.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
True if on, False if off.
|
|
131
|
+
"""
|
|
96
132
|
# Emit event and get response
|
|
97
133
|
self.logger.debug("get_on")
|
|
98
134
|
|
|
@@ -108,6 +144,11 @@ class DimmingLight(Accessory):
|
|
|
108
144
|
return self.is_on
|
|
109
145
|
|
|
110
146
|
def set_brightness(self, value: int) -> None:
|
|
147
|
+
"""Set the brightness level of the dimming light.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
value: Brightness level (0-100).
|
|
151
|
+
"""
|
|
111
152
|
self.logger.debug(f"set_brightness {value}")
|
|
112
153
|
self.brightness = value
|
|
113
154
|
|
|
@@ -122,6 +163,11 @@ class DimmingLight(Accessory):
|
|
|
122
163
|
)
|
|
123
164
|
|
|
124
165
|
def get_brightness(self) -> int:
|
|
166
|
+
"""Get the brightness level of the dimming light.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
Current brightness level (0-100).
|
|
170
|
+
"""
|
|
125
171
|
self.logger.debug("get_brightness")
|
|
126
172
|
|
|
127
173
|
# Dispatch event from HAP thread (thread-safe)
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
"""HomeKit Dimming Light Service.
|
|
2
|
+
|
|
3
|
+
This module provides service implementation for dimming light accessories.
|
|
4
|
+
"""
|
|
5
|
+
|
|
1
6
|
import logging
|
|
2
7
|
|
|
3
8
|
from bubus import EventBus
|
|
@@ -14,12 +19,21 @@ from xp.models.telegram.datapoint_type import DataPointType
|
|
|
14
19
|
|
|
15
20
|
|
|
16
21
|
class HomeKitDimmingLightService:
|
|
17
|
-
"""Dimming light service for HomeKit
|
|
22
|
+
"""Dimming light service for HomeKit.
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
event_bus: Event bus for inter-service communication.
|
|
26
|
+
logger: Logger instance.
|
|
27
|
+
"""
|
|
18
28
|
|
|
19
29
|
event_bus: EventBus
|
|
20
30
|
|
|
21
31
|
def __init__(self, event_bus: EventBus) -> None:
|
|
32
|
+
"""Initialize the dimming light service.
|
|
22
33
|
|
|
34
|
+
Args:
|
|
35
|
+
event_bus: Event bus instance.
|
|
36
|
+
"""
|
|
23
37
|
self.logger = logging.getLogger(__name__)
|
|
24
38
|
self.event_bus = event_bus
|
|
25
39
|
|
|
@@ -34,6 +48,11 @@ class HomeKitDimmingLightService:
|
|
|
34
48
|
)
|
|
35
49
|
|
|
36
50
|
def handle_dimminglight_get_on(self, event: DimmingLightGetOnEvent) -> None:
|
|
51
|
+
"""Handle dimming light get on event.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
event: Dimming light get on event.
|
|
55
|
+
"""
|
|
37
56
|
self.logger.info(
|
|
38
57
|
f"Getting dimming light state for serial {event.serial_number}, output {event.output_number}"
|
|
39
58
|
)
|
|
@@ -48,6 +67,11 @@ class HomeKitDimmingLightService:
|
|
|
48
67
|
self.logger.debug(f"Dispatched ReadDatapointEvent for {event.serial_number}")
|
|
49
68
|
|
|
50
69
|
def handle_dimminglight_set_on(self, event: DimmingLightSetOnEvent) -> None:
|
|
70
|
+
"""Handle dimming light set on event.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
event: Dimming light set on event.
|
|
74
|
+
"""
|
|
51
75
|
brightness = event.brightness if event.value else 0
|
|
52
76
|
self.logger.debug(
|
|
53
77
|
f"Setting on light for "
|
|
@@ -74,6 +98,11 @@ class HomeKitDimmingLightService:
|
|
|
74
98
|
def handle_dimminglight_set_brightness(
|
|
75
99
|
self, event: DimmingLightSetBrightnessEvent
|
|
76
100
|
) -> None:
|
|
101
|
+
"""Handle dimming light set brightness event.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
event: Dimming light set brightness event.
|
|
105
|
+
"""
|
|
77
106
|
self.logger.info(
|
|
78
107
|
f"Setting dimming light brightness"
|
|
79
108
|
f"serial {event.serial_number}, "
|
|
@@ -97,6 +126,11 @@ class HomeKitDimmingLightService:
|
|
|
97
126
|
def handle_dimminglight_get_brightness(
|
|
98
127
|
self, event: DimmingLightGetBrightnessEvent
|
|
99
128
|
) -> None:
|
|
129
|
+
"""Handle dimming light get brightness event.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
event: Dimming light get brightness event.
|
|
133
|
+
"""
|
|
100
134
|
self.logger.info(
|
|
101
135
|
f"Getting dimming light brightness "
|
|
102
136
|
f"for serial {event.serial_number}, "
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
"""HomeKit HAP Service for Apple HomeKit integration.
|
|
2
|
+
|
|
3
|
+
This module provides the main HAP (HomeKit Accessory Protocol) service.
|
|
4
|
+
"""
|
|
5
|
+
|
|
1
6
|
import logging
|
|
2
7
|
import signal
|
|
3
8
|
import threading
|
|
@@ -30,11 +35,20 @@ from xp.services.homekit.homekit_outlet import Outlet
|
|
|
30
35
|
|
|
31
36
|
|
|
32
37
|
class HomekitHapService:
|
|
33
|
-
"""
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
"""HomeKit HAP service.
|
|
39
|
+
|
|
40
|
+
Manages HAP accessory protocol, handles bridge and accessory setup,
|
|
41
|
+
and processes HomeKit events for device state synchronization.
|
|
42
|
+
|
|
43
|
+
Attributes:
|
|
44
|
+
event_bus: Event bus for inter-service communication.
|
|
45
|
+
last_activity: Timestamp of last service activity.
|
|
46
|
+
logger: Logger instance.
|
|
47
|
+
config: HomeKit configuration.
|
|
48
|
+
accessory_registry: Registry of accessories by identifier.
|
|
49
|
+
module_registry: Registry of accessories by module key.
|
|
50
|
+
modules: Module service for module lookup.
|
|
51
|
+
driver: HAP accessory driver.
|
|
38
52
|
"""
|
|
39
53
|
|
|
40
54
|
event_bus: EventBus
|
|
@@ -45,12 +59,12 @@ class HomekitHapService:
|
|
|
45
59
|
module_service: HomekitModuleService,
|
|
46
60
|
event_bus: EventBus,
|
|
47
61
|
):
|
|
48
|
-
"""Initialize the
|
|
62
|
+
"""Initialize the HomeKit HAP service.
|
|
49
63
|
|
|
50
64
|
Args:
|
|
51
|
-
homekit_config:
|
|
52
|
-
module_service:
|
|
53
|
-
event_bus:
|
|
65
|
+
homekit_config: HomeKit configuration.
|
|
66
|
+
module_service: Module service for dependency injection.
|
|
67
|
+
event_bus: Event bus for dependency injection.
|
|
54
68
|
"""
|
|
55
69
|
self.last_activity: Optional[datetime] = None
|
|
56
70
|
|
|
@@ -82,13 +96,13 @@ class HomekitHapService:
|
|
|
82
96
|
self.driver: AccessoryDriver = driver
|
|
83
97
|
|
|
84
98
|
async def async_start(self) -> None:
|
|
85
|
-
"""
|
|
86
|
-
self.logger.info("Loading accessories
|
|
99
|
+
"""Start the HAP service asynchronously."""
|
|
100
|
+
self.logger.info("Loading accessories.")
|
|
87
101
|
self.build_bridge()
|
|
88
102
|
self.logger.info("Accessories loaded successfully")
|
|
89
103
|
|
|
90
104
|
# Start HAP-python in a separate thread to avoid event loop conflicts
|
|
91
|
-
self.logger.info("Starting HAP-python driver in separate thread
|
|
105
|
+
self.logger.info("Starting HAP-python driver in separate thread.")
|
|
92
106
|
hap_thread = threading.Thread(
|
|
93
107
|
target=self._run_driver_in_thread, daemon=True, name="HAP-Python"
|
|
94
108
|
)
|
|
@@ -96,19 +110,26 @@ class HomekitHapService:
|
|
|
96
110
|
self.logger.info("HAP-python driver thread started")
|
|
97
111
|
|
|
98
112
|
def _run_driver_in_thread(self) -> None:
|
|
99
|
-
"""Run the HAP-python driver in a separate thread with its own event loop"""
|
|
113
|
+
"""Run the HAP-python driver in a separate thread with its own event loop."""
|
|
100
114
|
try:
|
|
101
|
-
self.logger.info("HAP-python thread starting, creating new event loop
|
|
115
|
+
self.logger.info("HAP-python thread starting, creating new event loop.")
|
|
102
116
|
# Create a new event loop for this thread
|
|
103
117
|
|
|
104
|
-
self.logger.info("Starting HAP-python driver
|
|
118
|
+
self.logger.info("Starting HAP-python driver.")
|
|
105
119
|
self.driver.start()
|
|
106
120
|
self.logger.info("HAP-python driver started successfully")
|
|
107
121
|
except Exception as e:
|
|
108
122
|
self.logger.error(f"HAP-python driver error: {e}", exc_info=True)
|
|
109
123
|
|
|
110
124
|
def handle_output_state_received(self, event: OutputStateReceivedEvent) -> str:
|
|
125
|
+
"""Handle output state received event.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
event: Output state received event.
|
|
111
129
|
|
|
130
|
+
Returns:
|
|
131
|
+
Data value from the event.
|
|
132
|
+
"""
|
|
112
133
|
self.logger.debug(f"Received OutputStateReceivedEvent {event}")
|
|
113
134
|
output_number = 0
|
|
114
135
|
for output in event.data_value[::-1]:
|
|
@@ -131,7 +152,14 @@ class HomekitHapService:
|
|
|
131
152
|
return event.data_value
|
|
132
153
|
|
|
133
154
|
def handle_light_level_received(self, event: LightLevelReceivedEvent) -> str:
|
|
155
|
+
"""Handle light level received event.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
event: Light level received event.
|
|
134
159
|
|
|
160
|
+
Returns:
|
|
161
|
+
Data value from the event.
|
|
162
|
+
"""
|
|
135
163
|
# Parse response format like "00:050,01:025,02:100"
|
|
136
164
|
self.logger.debug("Received LightLevelReceivedEvent", extra={"event": event})
|
|
137
165
|
output_number = 0
|
|
@@ -165,6 +193,7 @@ class HomekitHapService:
|
|
|
165
193
|
return event.data_value
|
|
166
194
|
|
|
167
195
|
def build_bridge(self) -> None:
|
|
196
|
+
"""Build the HomeKit bridge with all configured accessories."""
|
|
168
197
|
bridge_config = self.config.bridge
|
|
169
198
|
bridge = Bridge(self.driver, bridge_config.name)
|
|
170
199
|
bridge.set_info_service(
|
|
@@ -177,7 +206,12 @@ class HomekitHapService:
|
|
|
177
206
|
self.driver.add_accessory(accessory=bridge)
|
|
178
207
|
|
|
179
208
|
def add_room(self, bridge: Bridge, room: RoomConfig) -> None:
|
|
180
|
-
"""
|
|
209
|
+
"""Add a room with its accessories to the bridge.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
bridge: HAP bridge instance.
|
|
213
|
+
room: Room configuration.
|
|
214
|
+
"""
|
|
181
215
|
temperature = TemperatureSensor(self.driver, room.name)
|
|
182
216
|
bridge.add_accessory(temperature)
|
|
183
217
|
|
|
@@ -205,7 +239,14 @@ class HomekitHapService:
|
|
|
205
239
|
def get_accessory(
|
|
206
240
|
self, homekit_accessory: HomekitAccessoryConfig
|
|
207
241
|
) -> Union[LightBulb, Outlet, DimmingLight, None]:
|
|
208
|
-
"""
|
|
242
|
+
"""Get an accessory instance from configuration.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
homekit_accessory: HomeKit accessory configuration.
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
Accessory instance or None if not found or invalid service type.
|
|
249
|
+
"""
|
|
209
250
|
module_config = self.modules.get_module_by_serial(
|
|
210
251
|
homekit_accessory.serial_number
|
|
211
252
|
)
|
|
@@ -241,12 +282,24 @@ class HomekitHapService:
|
|
|
241
282
|
return None
|
|
242
283
|
|
|
243
284
|
def get_accessory_by_name(self, name: str) -> Optional[HomekitAccessoryConfig]:
|
|
285
|
+
"""Get an accessory configuration by name.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
name: Name of the accessory to find.
|
|
289
|
+
|
|
290
|
+
Returns:
|
|
291
|
+
Accessory configuration if found, None otherwise.
|
|
292
|
+
"""
|
|
244
293
|
return next(
|
|
245
294
|
(module for module in self.config.accessories if module.name == name), None
|
|
246
295
|
)
|
|
247
296
|
|
|
248
297
|
def handle_module_state_changed(self, event: ModuleStateChangedEvent) -> None:
|
|
249
|
-
"""Handle module state change by refreshing affected accessories
|
|
298
|
+
"""Handle module state change by refreshing affected accessories.
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
event: Module state changed event.
|
|
302
|
+
"""
|
|
250
303
|
self.logger.debug(
|
|
251
304
|
f"Module state changed: module_type={event.module_type_code}, "
|
|
252
305
|
f"link={event.link_number}, input={event.input_number}"
|