conson-xp 1.52.0__py3-none-any.whl → 2.0.1__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.
Files changed (39) hide show
  1. {conson_xp-1.52.0.dist-info → conson_xp-2.0.1.dist-info}/METADATA +1 -11
  2. {conson_xp-1.52.0.dist-info → conson_xp-2.0.1.dist-info}/RECORD +20 -39
  3. xp/__init__.py +1 -1
  4. xp/cli/commands/__init__.py +0 -4
  5. xp/cli/commands/term/term_commands.py +1 -1
  6. xp/cli/main.py +23 -7
  7. xp/models/conbus/conbus_client_config.py +2 -0
  8. xp/models/protocol/conbus_protocol.py +30 -25
  9. xp/models/term/accessory_state.py +1 -1
  10. xp/services/protocol/__init__.py +2 -3
  11. xp/services/protocol/conbus_event_protocol.py +6 -6
  12. xp/services/term/homekit_accessory_driver.py +5 -2
  13. xp/services/term/homekit_service.py +118 -11
  14. xp/term/homekit.py +140 -8
  15. xp/term/homekit.tcss +4 -4
  16. xp/term/widgets/room_list.py +61 -3
  17. xp/utils/dependencies.py +24 -154
  18. xp/cli/commands/homekit/__init__.py +0 -3
  19. xp/cli/commands/homekit/homekit.py +0 -120
  20. xp/cli/commands/homekit/homekit_start_commands.py +0 -44
  21. xp/services/homekit/__init__.py +0 -1
  22. xp/services/homekit/homekit_cache_service.py +0 -313
  23. xp/services/homekit/homekit_conbus_service.py +0 -99
  24. xp/services/homekit/homekit_config_validator.py +0 -327
  25. xp/services/homekit/homekit_conson_validator.py +0 -130
  26. xp/services/homekit/homekit_dimminglight.py +0 -189
  27. xp/services/homekit/homekit_dimminglight_service.py +0 -155
  28. xp/services/homekit/homekit_hap_service.py +0 -351
  29. xp/services/homekit/homekit_lightbulb.py +0 -125
  30. xp/services/homekit/homekit_lightbulb_service.py +0 -91
  31. xp/services/homekit/homekit_module_service.py +0 -60
  32. xp/services/homekit/homekit_outlet.py +0 -175
  33. xp/services/homekit/homekit_outlet_service.py +0 -127
  34. xp/services/homekit/homekit_service.py +0 -371
  35. xp/services/protocol/protocol_factory.py +0 -84
  36. xp/services/protocol/telegram_protocol.py +0 -270
  37. {conson_xp-1.52.0.dist-info → conson_xp-2.0.1.dist-info}/WHEEL +0 -0
  38. {conson_xp-1.52.0.dist-info → conson_xp-2.0.1.dist-info}/entry_points.txt +0 -0
  39. {conson_xp-1.52.0.dist-info → conson_xp-2.0.1.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