conson-xp 1.51.1__py3-none-any.whl → 2.0.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.51.1.dist-info → conson_xp-2.0.0.dist-info}/METADATA +1 -11
- {conson_xp-1.51.1.dist-info → conson_xp-2.0.0.dist-info}/RECORD +20 -38
- xp/__init__.py +1 -1
- xp/cli/commands/__init__.py +0 -4
- xp/cli/commands/term/term_commands.py +1 -1
- xp/cli/main.py +0 -3
- xp/models/homekit/homekit_config.py +4 -0
- xp/models/protocol/conbus_protocol.py +30 -25
- xp/models/term/accessory_state.py +1 -1
- xp/services/protocol/__init__.py +2 -3
- xp/services/protocol/conbus_event_protocol.py +5 -5
- xp/services/term/homekit_accessory_driver.py +171 -0
- xp/services/term/homekit_service.py +187 -10
- xp/term/homekit.py +146 -14
- xp/term/homekit.tcss +4 -4
- xp/term/widgets/room_list.py +61 -3
- xp/utils/dependencies.py +34 -154
- xp/cli/commands/homekit/__init__.py +0 -3
- xp/cli/commands/homekit/homekit.py +0 -120
- xp/cli/commands/homekit/homekit_start_commands.py +0 -44
- xp/services/homekit/__init__.py +0 -1
- xp/services/homekit/homekit_cache_service.py +0 -313
- xp/services/homekit/homekit_conbus_service.py +0 -99
- xp/services/homekit/homekit_config_validator.py +0 -327
- xp/services/homekit/homekit_conson_validator.py +0 -130
- xp/services/homekit/homekit_dimminglight.py +0 -189
- xp/services/homekit/homekit_dimminglight_service.py +0 -155
- xp/services/homekit/homekit_hap_service.py +0 -351
- xp/services/homekit/homekit_lightbulb.py +0 -125
- xp/services/homekit/homekit_lightbulb_service.py +0 -91
- xp/services/homekit/homekit_module_service.py +0 -60
- xp/services/homekit/homekit_outlet.py +0 -175
- xp/services/homekit/homekit_outlet_service.py +0 -127
- xp/services/homekit/homekit_service.py +0 -371
- xp/services/protocol/protocol_factory.py +0 -84
- xp/services/protocol/telegram_protocol.py +0 -270
- {conson_xp-1.51.1.dist-info → conson_xp-2.0.0.dist-info}/WHEEL +0 -0
- {conson_xp-1.51.1.dist-info → conson_xp-2.0.0.dist-info}/entry_points.txt +0 -0
- {conson_xp-1.51.1.dist-info → conson_xp-2.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,327 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
HomeKit Configuration Validator.
|
|
3
|
-
|
|
4
|
-
This module validates HomeKit configuration files for correctness and consistency.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from contextlib import suppress
|
|
8
|
-
from typing import List, Set
|
|
9
|
-
|
|
10
|
-
from xp.models.homekit.homekit_config import HomekitConfig
|
|
11
|
-
from xp.services.homekit.homekit_conson_validator import ConsonConfigValidator
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class HomekitConfigValidator:
|
|
15
|
-
"""Validates homekit.yml configuration file for HomeKit integration."""
|
|
16
|
-
|
|
17
|
-
def __init__(self, config: HomekitConfig):
|
|
18
|
-
"""
|
|
19
|
-
Initialize the HomeKit config validator.
|
|
20
|
-
|
|
21
|
-
Args:
|
|
22
|
-
config: HomeKit configuration to validate.
|
|
23
|
-
"""
|
|
24
|
-
self.config = config
|
|
25
|
-
|
|
26
|
-
def validate_unique_accessory_names(self) -> List[str]:
|
|
27
|
-
"""
|
|
28
|
-
Validate that all accessory names are unique.
|
|
29
|
-
|
|
30
|
-
Returns:
|
|
31
|
-
List of validation error messages.
|
|
32
|
-
"""
|
|
33
|
-
names: Set[str] = set()
|
|
34
|
-
errors = []
|
|
35
|
-
|
|
36
|
-
for accessory in self.config.accessories:
|
|
37
|
-
if accessory.name in names:
|
|
38
|
-
errors.append(f"Duplicate accessory name: {accessory.name}")
|
|
39
|
-
names.add(accessory.name)
|
|
40
|
-
|
|
41
|
-
return errors
|
|
42
|
-
|
|
43
|
-
def validate_service_types(self) -> List[str]:
|
|
44
|
-
"""
|
|
45
|
-
Validate that service types are valid.
|
|
46
|
-
|
|
47
|
-
Returns:
|
|
48
|
-
List of validation error messages.
|
|
49
|
-
"""
|
|
50
|
-
valid_services = {"lightbulb", "outlet", "dimminglight"}
|
|
51
|
-
errors = [
|
|
52
|
-
(
|
|
53
|
-
f"Invalid "
|
|
54
|
-
f"service type '{accessory.service}' "
|
|
55
|
-
f"for accessory '{accessory.name}'. "
|
|
56
|
-
f"Valid types: {', '.join(valid_services)}"
|
|
57
|
-
)
|
|
58
|
-
for accessory in self.config.accessories
|
|
59
|
-
if accessory.service not in valid_services
|
|
60
|
-
]
|
|
61
|
-
|
|
62
|
-
return errors
|
|
63
|
-
|
|
64
|
-
def validate_output_numbers(self) -> List[str]:
|
|
65
|
-
"""
|
|
66
|
-
Validate that output numbers are positive integers.
|
|
67
|
-
|
|
68
|
-
Returns:
|
|
69
|
-
List of validation error messages.
|
|
70
|
-
"""
|
|
71
|
-
errors = [
|
|
72
|
-
f"Invalid output number {accessory.output_number} for accessory '{accessory.name}'. Must be positive."
|
|
73
|
-
for accessory in self.config.accessories
|
|
74
|
-
if accessory.output_number < 0
|
|
75
|
-
]
|
|
76
|
-
|
|
77
|
-
return errors
|
|
78
|
-
|
|
79
|
-
def validate_unique_room_names(self) -> List[str]:
|
|
80
|
-
"""
|
|
81
|
-
Validate that all room names are unique.
|
|
82
|
-
|
|
83
|
-
Returns:
|
|
84
|
-
List of validation error messages.
|
|
85
|
-
"""
|
|
86
|
-
names: Set[str] = set()
|
|
87
|
-
errors = []
|
|
88
|
-
|
|
89
|
-
for room in self.config.bridge.rooms:
|
|
90
|
-
if room.name in names:
|
|
91
|
-
errors.append(f"Duplicate room name: {room.name}")
|
|
92
|
-
names.add(room.name)
|
|
93
|
-
|
|
94
|
-
return errors
|
|
95
|
-
|
|
96
|
-
def validate_room_accessory_references(self) -> List[str]:
|
|
97
|
-
"""
|
|
98
|
-
Validate that all room accessories exist in accessories section.
|
|
99
|
-
|
|
100
|
-
Returns:
|
|
101
|
-
List of validation error messages.
|
|
102
|
-
"""
|
|
103
|
-
accessory_names = {acc.name for acc in self.config.accessories}
|
|
104
|
-
errors = []
|
|
105
|
-
|
|
106
|
-
for room in self.config.bridge.rooms:
|
|
107
|
-
for acc_name in room.accessories:
|
|
108
|
-
if acc_name not in accessory_names:
|
|
109
|
-
errors.append(
|
|
110
|
-
f"Room '{room.name}' references unknown accessory '{acc_name}'"
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
return errors
|
|
114
|
-
|
|
115
|
-
def validate_no_orphaned_accessories(self) -> List[str]:
|
|
116
|
-
"""
|
|
117
|
-
Validate that all accessories are assigned to at least one room.
|
|
118
|
-
|
|
119
|
-
Returns:
|
|
120
|
-
List of validation error messages.
|
|
121
|
-
"""
|
|
122
|
-
assigned_accessories: Set[str] = set()
|
|
123
|
-
for room in self.config.bridge.rooms:
|
|
124
|
-
assigned_accessories.update(room.accessories)
|
|
125
|
-
|
|
126
|
-
errors = [
|
|
127
|
-
f"Accessory '{accessory.name}' is not assigned to any room"
|
|
128
|
-
for accessory in self.config.accessories
|
|
129
|
-
if accessory.name not in assigned_accessories
|
|
130
|
-
]
|
|
131
|
-
|
|
132
|
-
return errors
|
|
133
|
-
|
|
134
|
-
def validate_no_duplicate_accessory_assignments(self) -> List[str]:
|
|
135
|
-
"""
|
|
136
|
-
Validate that accessories are not assigned to multiple rooms.
|
|
137
|
-
|
|
138
|
-
Returns:
|
|
139
|
-
List of validation error messages.
|
|
140
|
-
"""
|
|
141
|
-
assigned_accessories: Set[str] = set()
|
|
142
|
-
errors = []
|
|
143
|
-
|
|
144
|
-
for room in self.config.bridge.rooms:
|
|
145
|
-
for acc_name in room.accessories:
|
|
146
|
-
if acc_name in assigned_accessories:
|
|
147
|
-
errors.append(
|
|
148
|
-
f"Accessory '{acc_name}' is assigned to multiple rooms"
|
|
149
|
-
)
|
|
150
|
-
assigned_accessories.add(acc_name)
|
|
151
|
-
|
|
152
|
-
return errors
|
|
153
|
-
|
|
154
|
-
def validate_all(self) -> List[str]:
|
|
155
|
-
"""
|
|
156
|
-
Run all validations and return combined errors.
|
|
157
|
-
|
|
158
|
-
Returns:
|
|
159
|
-
List of all validation error messages.
|
|
160
|
-
"""
|
|
161
|
-
all_errors = []
|
|
162
|
-
all_errors.extend(self.validate_unique_accessory_names())
|
|
163
|
-
all_errors.extend(self.validate_service_types())
|
|
164
|
-
all_errors.extend(self.validate_output_numbers())
|
|
165
|
-
all_errors.extend(self.validate_unique_room_names())
|
|
166
|
-
all_errors.extend(self.validate_room_accessory_references())
|
|
167
|
-
all_errors.extend(self.validate_no_orphaned_accessories())
|
|
168
|
-
all_errors.extend(self.validate_no_duplicate_accessory_assignments())
|
|
169
|
-
return all_errors
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
class CrossReferenceValidator:
|
|
173
|
-
"""Validates cross-references between conson.yml and homekit.yml configurations."""
|
|
174
|
-
|
|
175
|
-
def __init__(
|
|
176
|
-
self,
|
|
177
|
-
conson_validator: ConsonConfigValidator,
|
|
178
|
-
homekit_validator: HomekitConfigValidator,
|
|
179
|
-
):
|
|
180
|
-
"""
|
|
181
|
-
Initialize the cross-reference validator.
|
|
182
|
-
|
|
183
|
-
Args:
|
|
184
|
-
conson_validator: Conson configuration validator.
|
|
185
|
-
homekit_validator: HomeKit configuration validator.
|
|
186
|
-
"""
|
|
187
|
-
self.conson_validator = conson_validator
|
|
188
|
-
self.homekit_validator = homekit_validator
|
|
189
|
-
|
|
190
|
-
def validate_serial_number_references(self) -> List[str]:
|
|
191
|
-
"""
|
|
192
|
-
Validate that all accessory serial numbers exist in conson configuration.
|
|
193
|
-
|
|
194
|
-
Returns:
|
|
195
|
-
List of validation error messages.
|
|
196
|
-
"""
|
|
197
|
-
conson_serials = self.conson_validator.get_all_serial_numbers()
|
|
198
|
-
errors = [
|
|
199
|
-
f"Accessory '{accessory.name}' references unknown serial number {accessory.serial_number}"
|
|
200
|
-
for accessory in self.homekit_validator.config.accessories
|
|
201
|
-
if accessory.serial_number not in conson_serials
|
|
202
|
-
]
|
|
203
|
-
|
|
204
|
-
return errors
|
|
205
|
-
|
|
206
|
-
def validate_output_capabilities(self) -> List[str]:
|
|
207
|
-
"""
|
|
208
|
-
Validate that output numbers are within module capabilities.
|
|
209
|
-
|
|
210
|
-
Returns:
|
|
211
|
-
List of validation error messages.
|
|
212
|
-
"""
|
|
213
|
-
errors = []
|
|
214
|
-
|
|
215
|
-
for accessory in self.homekit_validator.config.accessories:
|
|
216
|
-
with suppress(ValueError):
|
|
217
|
-
module = self.conson_validator.get_module_by_serial(
|
|
218
|
-
accessory.serial_number
|
|
219
|
-
)
|
|
220
|
-
|
|
221
|
-
# Define output limits by module type
|
|
222
|
-
output_limits = {
|
|
223
|
-
"XP130": 0, # Example limits
|
|
224
|
-
"XP20": 8,
|
|
225
|
-
"XP24": 4,
|
|
226
|
-
"XP33": 3,
|
|
227
|
-
"XP33LR": 3,
|
|
228
|
-
"XP33LED": 3,
|
|
229
|
-
"XXP31LR": 1,
|
|
230
|
-
"XXP31CR": 1,
|
|
231
|
-
"XXP31BC": 1,
|
|
232
|
-
"XXP31LED": 1,
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
max_outputs = output_limits.get(module.module_type, 4) # Default to 8
|
|
236
|
-
|
|
237
|
-
if accessory.output_number > max_outputs:
|
|
238
|
-
errors.append(
|
|
239
|
-
f"Accessory '{accessory.name}' "
|
|
240
|
-
f"output {accessory.output_number} "
|
|
241
|
-
f"exceeds module '{module.name}' ({module.module_type}) "
|
|
242
|
-
f"limit of {max_outputs}"
|
|
243
|
-
)
|
|
244
|
-
|
|
245
|
-
return errors
|
|
246
|
-
|
|
247
|
-
def validate_all(self) -> List[str]:
|
|
248
|
-
"""
|
|
249
|
-
Run all cross-reference validations and return combined errors.
|
|
250
|
-
|
|
251
|
-
Returns:
|
|
252
|
-
List of all cross-reference validation error messages.
|
|
253
|
-
"""
|
|
254
|
-
all_errors = []
|
|
255
|
-
all_errors.extend(self.validate_serial_number_references())
|
|
256
|
-
all_errors.extend(self.validate_output_capabilities())
|
|
257
|
-
return all_errors
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
class ConfigValidationService:
|
|
261
|
-
"""Main service for validating HomeKit configuration coherence."""
|
|
262
|
-
|
|
263
|
-
def __init__(self, conson_config_path: str, homekit_config_path: str):
|
|
264
|
-
"""
|
|
265
|
-
Initialize the config validation service.
|
|
266
|
-
|
|
267
|
-
Args:
|
|
268
|
-
conson_config_path: Path to conson.yml configuration file.
|
|
269
|
-
homekit_config_path: Path to homekit.yml configuration file.
|
|
270
|
-
"""
|
|
271
|
-
from xp.models.config.conson_module_config import ConsonModuleListConfig
|
|
272
|
-
from xp.models.homekit.homekit_config import HomekitConfig
|
|
273
|
-
|
|
274
|
-
self.conson_config = ConsonModuleListConfig.from_yaml(conson_config_path)
|
|
275
|
-
self.homekit_config = HomekitConfig.from_yaml(homekit_config_path)
|
|
276
|
-
|
|
277
|
-
self.conson_validator = ConsonConfigValidator(self.conson_config)
|
|
278
|
-
self.homekit_validator = HomekitConfigValidator(self.homekit_config)
|
|
279
|
-
self.cross_validator = CrossReferenceValidator(
|
|
280
|
-
self.conson_validator, self.homekit_validator
|
|
281
|
-
)
|
|
282
|
-
|
|
283
|
-
def validate_all(self) -> dict:
|
|
284
|
-
"""
|
|
285
|
-
Run all validations and return organized results.
|
|
286
|
-
|
|
287
|
-
Returns:
|
|
288
|
-
Dictionary containing validation results and error counts.
|
|
289
|
-
"""
|
|
290
|
-
conson_errors = self.conson_validator.validate_all()
|
|
291
|
-
homekit_errors = self.homekit_validator.validate_all()
|
|
292
|
-
cross_errors = self.cross_validator.validate_all()
|
|
293
|
-
|
|
294
|
-
return {
|
|
295
|
-
"conson_errors": conson_errors,
|
|
296
|
-
"homekit_errors": homekit_errors,
|
|
297
|
-
"cross_reference_errors": cross_errors,
|
|
298
|
-
"total_errors": len(conson_errors)
|
|
299
|
-
+ len(homekit_errors)
|
|
300
|
-
+ len(cross_errors),
|
|
301
|
-
"is_valid": len(conson_errors) + len(homekit_errors) + len(cross_errors)
|
|
302
|
-
== 0,
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
def print_config_summary(self) -> str:
|
|
306
|
-
"""
|
|
307
|
-
Generate a summary of the configuration.
|
|
308
|
-
|
|
309
|
-
Returns:
|
|
310
|
-
String containing configuration summary.
|
|
311
|
-
"""
|
|
312
|
-
summary = [
|
|
313
|
-
f"Conson Modules: {len(self.conson_config.root)}",
|
|
314
|
-
f"HomeKit Accessories: {len(self.homekit_config.accessories)}",
|
|
315
|
-
f"HomeKit Rooms: {len(self.homekit_config.bridge.rooms)}",
|
|
316
|
-
]
|
|
317
|
-
|
|
318
|
-
# Count accessories by service type
|
|
319
|
-
service_counts: dict[str, int] = {}
|
|
320
|
-
for acc in self.homekit_config.accessories:
|
|
321
|
-
service_counts[acc.service] = service_counts.get(acc.service, 0) + 1
|
|
322
|
-
|
|
323
|
-
summary.append("Service Types:")
|
|
324
|
-
for service, count in service_counts.items():
|
|
325
|
-
summary.append(f" - {service}: {count}")
|
|
326
|
-
|
|
327
|
-
return "\n".join(summary)
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Conson Configuration Validator for HomeKit.
|
|
3
|
-
|
|
4
|
-
This module validates conson.yml configuration files for HomeKit integration.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from typing import List, Set
|
|
8
|
-
|
|
9
|
-
from xp.models.config.conson_module_config import (
|
|
10
|
-
ConsonModuleConfig,
|
|
11
|
-
ConsonModuleListConfig,
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class ConsonConfigValidator:
|
|
16
|
-
"""Validates conson.yml configuration file for HomeKit integration."""
|
|
17
|
-
|
|
18
|
-
def __init__(self, config: ConsonModuleListConfig):
|
|
19
|
-
"""
|
|
20
|
-
Initialize the Conson config validator.
|
|
21
|
-
|
|
22
|
-
Args:
|
|
23
|
-
config: Conson module list configuration to validate.
|
|
24
|
-
"""
|
|
25
|
-
self.config = config
|
|
26
|
-
|
|
27
|
-
def validate_unique_names(self) -> List[str]:
|
|
28
|
-
"""
|
|
29
|
-
Validate that all module names are unique.
|
|
30
|
-
|
|
31
|
-
Returns:
|
|
32
|
-
List of validation error messages.
|
|
33
|
-
"""
|
|
34
|
-
names: Set[str] = set()
|
|
35
|
-
errors = []
|
|
36
|
-
|
|
37
|
-
for module in self.config.root:
|
|
38
|
-
if module.name in names:
|
|
39
|
-
errors.append(f"Duplicate module name: {module.name}")
|
|
40
|
-
names.add(module.name)
|
|
41
|
-
|
|
42
|
-
return errors
|
|
43
|
-
|
|
44
|
-
def validate_unique_serial_numbers(self) -> List[str]:
|
|
45
|
-
"""
|
|
46
|
-
Validate that all serial numbers are unique.
|
|
47
|
-
|
|
48
|
-
Returns:
|
|
49
|
-
List of validation error messages.
|
|
50
|
-
"""
|
|
51
|
-
serials: Set[str] = set()
|
|
52
|
-
errors = []
|
|
53
|
-
|
|
54
|
-
for module in self.config.root:
|
|
55
|
-
if module.serial_number in serials:
|
|
56
|
-
errors.append(f"Duplicate serial number: {module.serial_number}")
|
|
57
|
-
serials.add(module.serial_number)
|
|
58
|
-
|
|
59
|
-
return errors
|
|
60
|
-
|
|
61
|
-
def validate_module_type_codes(self) -> List[str]:
|
|
62
|
-
"""
|
|
63
|
-
Validate module type code ranges.
|
|
64
|
-
|
|
65
|
-
Returns:
|
|
66
|
-
List of validation error messages.
|
|
67
|
-
"""
|
|
68
|
-
errors = [
|
|
69
|
-
f"Invalid module_type_code {module.module_type_code} for module {module.name}. Must be between 1 and 255."
|
|
70
|
-
for module in self.config.root
|
|
71
|
-
if not (1 <= module.module_type_code <= 255)
|
|
72
|
-
]
|
|
73
|
-
|
|
74
|
-
return errors
|
|
75
|
-
|
|
76
|
-
def validate_network_config(self) -> List[str]:
|
|
77
|
-
"""
|
|
78
|
-
Validate IP/port configuration.
|
|
79
|
-
|
|
80
|
-
Returns:
|
|
81
|
-
List of validation error messages.
|
|
82
|
-
"""
|
|
83
|
-
errors = [
|
|
84
|
-
f"Invalid conbus_port {module.conbus_port} for module {module.name}. Must be between 1 and 65535."
|
|
85
|
-
for module in self.config.root
|
|
86
|
-
if module.conbus_port is not None and not (1 <= module.conbus_port <= 65535)
|
|
87
|
-
]
|
|
88
|
-
|
|
89
|
-
return errors
|
|
90
|
-
|
|
91
|
-
def validate_all(self) -> List[str]:
|
|
92
|
-
"""
|
|
93
|
-
Run all validations and return combined errors.
|
|
94
|
-
|
|
95
|
-
Returns:
|
|
96
|
-
List of all validation error messages.
|
|
97
|
-
"""
|
|
98
|
-
all_errors = []
|
|
99
|
-
all_errors.extend(self.validate_unique_names())
|
|
100
|
-
all_errors.extend(self.validate_unique_serial_numbers())
|
|
101
|
-
all_errors.extend(self.validate_module_type_codes())
|
|
102
|
-
all_errors.extend(self.validate_network_config())
|
|
103
|
-
return all_errors
|
|
104
|
-
|
|
105
|
-
def get_module_by_serial(self, serial_number: str) -> ConsonModuleConfig:
|
|
106
|
-
"""
|
|
107
|
-
Get module configuration by serial number.
|
|
108
|
-
|
|
109
|
-
Args:
|
|
110
|
-
serial_number: Serial number of the module to find.
|
|
111
|
-
|
|
112
|
-
Returns:
|
|
113
|
-
Module configuration for the specified serial number.
|
|
114
|
-
|
|
115
|
-
Raises:
|
|
116
|
-
ValueError: If module with serial number is not found.
|
|
117
|
-
"""
|
|
118
|
-
for module in self.config.root:
|
|
119
|
-
if module.serial_number == serial_number:
|
|
120
|
-
return module
|
|
121
|
-
raise ValueError(f"Module with serial number {serial_number} not found")
|
|
122
|
-
|
|
123
|
-
def get_all_serial_numbers(self) -> Set[str]:
|
|
124
|
-
"""
|
|
125
|
-
Get all serial numbers from the configuration.
|
|
126
|
-
|
|
127
|
-
Returns:
|
|
128
|
-
Set of all serial numbers in the configuration.
|
|
129
|
-
"""
|
|
130
|
-
return {module.serial_number for module in self.config.root}
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
HomeKit Dimming Light Accessory.
|
|
3
|
-
|
|
4
|
-
This module provides a dimming light accessory for HomeKit integration.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import logging
|
|
8
|
-
|
|
9
|
-
from bubus import EventBus
|
|
10
|
-
from pyhap.accessory import Accessory
|
|
11
|
-
from pyhap.accessory_driver import AccessoryDriver
|
|
12
|
-
from pyhap.const import CATEGORY_LIGHTBULB
|
|
13
|
-
|
|
14
|
-
from xp.models.config.conson_module_config import ConsonModuleConfig
|
|
15
|
-
from xp.models.homekit.homekit_config import HomekitAccessoryConfig
|
|
16
|
-
from xp.models.protocol.conbus_protocol import (
|
|
17
|
-
DimmingLightGetBrightnessEvent,
|
|
18
|
-
DimmingLightGetOnEvent,
|
|
19
|
-
DimmingLightSetBrightnessEvent,
|
|
20
|
-
DimmingLightSetOnEvent,
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class DimmingLight(Accessory):
|
|
25
|
-
"""
|
|
26
|
-
HomeKit dimming light accessory.
|
|
27
|
-
|
|
28
|
-
Attributes:
|
|
29
|
-
category: HomeKit category (CATEGORY_LIGHTBULB).
|
|
30
|
-
event_bus: Event bus for inter-service communication.
|
|
31
|
-
logger: Logger instance.
|
|
32
|
-
identifier: Unique identifier for the accessory.
|
|
33
|
-
accessory: Accessory configuration.
|
|
34
|
-
module: Module configuration.
|
|
35
|
-
is_on: Current on/off state.
|
|
36
|
-
brightness: Current brightness level (0-100).
|
|
37
|
-
char_on: On characteristic.
|
|
38
|
-
char_brightness: Brightness characteristic.
|
|
39
|
-
"""
|
|
40
|
-
|
|
41
|
-
category = CATEGORY_LIGHTBULB
|
|
42
|
-
event_bus: EventBus
|
|
43
|
-
|
|
44
|
-
def __init__(
|
|
45
|
-
self,
|
|
46
|
-
driver: AccessoryDriver,
|
|
47
|
-
module: ConsonModuleConfig,
|
|
48
|
-
accessory: HomekitAccessoryConfig,
|
|
49
|
-
event_bus: EventBus,
|
|
50
|
-
):
|
|
51
|
-
"""
|
|
52
|
-
Initialize the dimming light accessory.
|
|
53
|
-
|
|
54
|
-
Args:
|
|
55
|
-
driver: HAP accessory driver.
|
|
56
|
-
module: Module configuration.
|
|
57
|
-
accessory: Accessory configuration.
|
|
58
|
-
event_bus: Event bus for inter-service communication.
|
|
59
|
-
"""
|
|
60
|
-
super().__init__(driver, accessory.description)
|
|
61
|
-
|
|
62
|
-
self.logger = logging.getLogger(__name__)
|
|
63
|
-
|
|
64
|
-
identifier = f"{module.serial_number}.{accessory.output_number:02d}"
|
|
65
|
-
version = accessory.id
|
|
66
|
-
manufacturer = "Conson"
|
|
67
|
-
model = "XP33LED_Lightdimmer"
|
|
68
|
-
|
|
69
|
-
self.identifier = identifier
|
|
70
|
-
self.accessory = accessory
|
|
71
|
-
self.module = module
|
|
72
|
-
self.event_bus = event_bus
|
|
73
|
-
|
|
74
|
-
self.is_on: bool = True
|
|
75
|
-
self.brightness: int = 0
|
|
76
|
-
|
|
77
|
-
self.logger.info(
|
|
78
|
-
"Creating DimmingLight { serial_number : %s, output_number: %s }",
|
|
79
|
-
module.serial_number,
|
|
80
|
-
accessory.output_number,
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
serv_light = self.add_preload_service(
|
|
84
|
-
"Lightbulb",
|
|
85
|
-
[
|
|
86
|
-
# The names here refer to the Characteristic name defined
|
|
87
|
-
# in characteristic.json
|
|
88
|
-
"Brightness"
|
|
89
|
-
],
|
|
90
|
-
)
|
|
91
|
-
self.set_info_service(version, manufacturer, model, identifier)
|
|
92
|
-
|
|
93
|
-
self.char_on = serv_light.configure_char(
|
|
94
|
-
"On", getter_callback=self.get_on, setter_callback=self.set_on, value=False
|
|
95
|
-
)
|
|
96
|
-
self.char_brightness = serv_light.configure_char(
|
|
97
|
-
"Brightness",
|
|
98
|
-
value=100,
|
|
99
|
-
getter_callback=self.get_brightness,
|
|
100
|
-
setter_callback=self.set_brightness,
|
|
101
|
-
)
|
|
102
|
-
self.logger.debug(f"char_on properties: {self.char_on.properties}")
|
|
103
|
-
self.logger.debug(
|
|
104
|
-
f"char_brightness properties: {self.char_brightness.properties}"
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
def set_on(self, value: bool) -> None:
|
|
108
|
-
"""
|
|
109
|
-
Set the on/off state of the dimming light.
|
|
110
|
-
|
|
111
|
-
Args:
|
|
112
|
-
value: True to turn on, False to turn off.
|
|
113
|
-
"""
|
|
114
|
-
# Emit set event
|
|
115
|
-
self.logger.debug(f"set_on {value}")
|
|
116
|
-
|
|
117
|
-
if value != self.is_on:
|
|
118
|
-
self.is_on = value
|
|
119
|
-
self.event_bus.dispatch(
|
|
120
|
-
DimmingLightSetOnEvent(
|
|
121
|
-
serial_number=self.accessory.serial_number,
|
|
122
|
-
output_number=self.accessory.output_number,
|
|
123
|
-
module=self.module,
|
|
124
|
-
accessory=self.accessory,
|
|
125
|
-
value=value,
|
|
126
|
-
brightness=self.brightness,
|
|
127
|
-
)
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
def get_on(self) -> bool:
|
|
131
|
-
"""
|
|
132
|
-
Get the on/off state of the dimming light.
|
|
133
|
-
|
|
134
|
-
Returns:
|
|
135
|
-
True if on, False if off.
|
|
136
|
-
"""
|
|
137
|
-
# Emit event and get response
|
|
138
|
-
self.logger.debug("get_on")
|
|
139
|
-
|
|
140
|
-
# Dispatch event from HAP thread (thread-safe)
|
|
141
|
-
self.event_bus.dispatch(
|
|
142
|
-
DimmingLightGetOnEvent(
|
|
143
|
-
serial_number=self.accessory.serial_number,
|
|
144
|
-
output_number=self.accessory.output_number,
|
|
145
|
-
module=self.module,
|
|
146
|
-
accessory=self.accessory,
|
|
147
|
-
)
|
|
148
|
-
)
|
|
149
|
-
return self.is_on
|
|
150
|
-
|
|
151
|
-
def set_brightness(self, value: int) -> None:
|
|
152
|
-
"""
|
|
153
|
-
Set the brightness level of the dimming light.
|
|
154
|
-
|
|
155
|
-
Args:
|
|
156
|
-
value: Brightness level (0-100).
|
|
157
|
-
"""
|
|
158
|
-
self.logger.debug(f"set_brightness {value}")
|
|
159
|
-
self.brightness = value
|
|
160
|
-
|
|
161
|
-
self.event_bus.dispatch(
|
|
162
|
-
DimmingLightSetBrightnessEvent(
|
|
163
|
-
serial_number=self.accessory.serial_number,
|
|
164
|
-
output_number=self.accessory.output_number,
|
|
165
|
-
module=self.module,
|
|
166
|
-
accessory=self.accessory,
|
|
167
|
-
brightness=value,
|
|
168
|
-
)
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
def get_brightness(self) -> int:
|
|
172
|
-
"""
|
|
173
|
-
Get the brightness level of the dimming light.
|
|
174
|
-
|
|
175
|
-
Returns:
|
|
176
|
-
Current brightness level (0-100).
|
|
177
|
-
"""
|
|
178
|
-
self.logger.debug("get_brightness")
|
|
179
|
-
|
|
180
|
-
# Dispatch event from HAP thread (thread-safe)
|
|
181
|
-
self.event_bus.dispatch(
|
|
182
|
-
DimmingLightGetBrightnessEvent(
|
|
183
|
-
serial_number=self.accessory.serial_number,
|
|
184
|
-
output_number=self.accessory.output_number,
|
|
185
|
-
module=self.module,
|
|
186
|
-
accessory=self.accessory,
|
|
187
|
-
)
|
|
188
|
-
)
|
|
189
|
-
return self.brightness
|