conson-xp 1.51.0__py3-none-any.whl → 1.52.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: conson-xp
3
- Version: 1.51.0
3
+ Version: 1.52.0
4
4
  Summary: XP Protocol Communication Tools
5
5
  Author-Email: ldvchosal <ldvchosal@github.com>
6
6
  License: MIT License
@@ -450,6 +450,7 @@ xp telegram version
450
450
 
451
451
 
452
452
  xp term
453
+ xp term homekit
453
454
  xp term protocol
454
455
  xp term state
455
456
 
@@ -1,8 +1,8 @@
1
- conson_xp-1.51.0.dist-info/METADATA,sha256=8rmZ2V68vB3gZpx_sScMBXxKXqAei3GeqOOUHGe92mo,11432
2
- conson_xp-1.51.0.dist-info/WHEEL,sha256=tsUv_t7BDeJeRHaSrczbGeuK-TtDpGsWi_JfpzD255I,90
3
- conson_xp-1.51.0.dist-info/entry_points.txt,sha256=1OcdIcDM1hz3ljCXgybaPUh1IOFEwkaTgLIW9u9zGeg,50
4
- conson_xp-1.51.0.dist-info/licenses/LICENSE,sha256=rxj6woMM-r3YCyGq_UHFtbh7kHTAJgrccH6O-33IDE4,1419
5
- xp/__init__.py,sha256=IbYggn-GNpmY-NkggNp_7SLbVaF6zPk0wENVaZeOIeE,182
1
+ conson_xp-1.52.0.dist-info/METADATA,sha256=eOYsythIuuDYadskAlya_jpbvYD2Wu-6MymZA0encfg,11448
2
+ conson_xp-1.52.0.dist-info/WHEEL,sha256=tsUv_t7BDeJeRHaSrczbGeuK-TtDpGsWi_JfpzD255I,90
3
+ conson_xp-1.52.0.dist-info/entry_points.txt,sha256=1OcdIcDM1hz3ljCXgybaPUh1IOFEwkaTgLIW9u9zGeg,50
4
+ conson_xp-1.52.0.dist-info/licenses/LICENSE,sha256=rxj6woMM-r3YCyGq_UHFtbh7kHTAJgrccH6O-33IDE4,1419
5
+ xp/__init__.py,sha256=wi3ok8DMPwweHsqxB5KwuT1FJynHp6v2d2Vt06prmpw,182
6
6
  xp/cli/__init__.py,sha256=QjnKB1KaI2aIyKlzrnvCwfbBuUj8HNgwNMvNJVQofbI,81
7
7
  xp/cli/__main__.py,sha256=l2iKwMdat5rTGd3JWs-uGksnYYDDffp_Npz05QdKEeU,117
8
8
  xp/cli/commands/__init__.py,sha256=G7A1KFRSV0CEeDTqr_khu-K9_sc01CTI2KSfkFcaBRM,4949
@@ -43,7 +43,7 @@ xp/cli/commands/telegram/telegram_parse_commands.py,sha256=xCDRRFgj41RtStvwROfi-
43
43
  xp/cli/commands/telegram/telegram_version_commands.py,sha256=hAMjSAa7zfMNfNFln63sKeNPcmW89bISkcVs6BgsqOg,1558
44
44
  xp/cli/commands/term/__init__.py,sha256=1NNST_8YJfj5LCujQISwQflK6LyEn7mDmZpMpvI9d-o,116
45
45
  xp/cli/commands/term/term.py,sha256=gjvsv2OE-F_KNWQrWi04fXQ5cGo0l8P-Ortbb5KTA-A,309
46
- xp/cli/commands/term/term_commands.py,sha256=ypJo9GL0eNwllgJJux2agJkcco6k9HMM-5f3aTbiQ2c,1216
46
+ xp/cli/commands/term/term_commands.py,sha256=Z3pq_xzP0j5YfYFOwASaZUxXDkikSV_E4cFjMb69LUU,1796
47
47
  xp/cli/main.py,sha256=Wbtji5ddW3IEoAfecHrEPk8W_w1bGD20B-NqAWfI_F4,1968
48
48
  xp/cli/utils/__init__.py,sha256=gTGIj60Uai0iE7sr9_TtEpl04fD7krtTzbbigXUsUVU,46
49
49
  xp/cli/utils/click_tree.py,sha256=sr4l9RWTCnASkdvkJKnRRxWSQPlF1DbFdBNu9gL7Ekc,1693
@@ -84,7 +84,7 @@ xp/models/config/__init__.py,sha256=gEZnX9eE3DjFtLtF32riEjJQLypqQRbyPauBI4Cowbs,
84
84
  xp/models/config/conson_module_config.py,sha256=t1G0LnNNMnjs3ahhz4-Z_5SlEv2FCrcRq13OmvZ2pvA,3009
85
85
  xp/models/homekit/__init__.py,sha256=5HDSOClCu0ArK3IICn3_LDMMLBAzLjBxUUSF73bxSSk,34
86
86
  xp/models/homekit/homekit_accessory.py,sha256=ANjDWlFxeNTstl7lKdmf6vMOC0wc005vpiD6awRcptA,1052
87
- xp/models/homekit/homekit_config.py,sha256=OMq0eayAJ6NRr8PXANvQzgEYGW9RN_ycyEmnTlTlHrQ,2938
87
+ xp/models/homekit/homekit_config.py,sha256=pgZOnocue60LjV8ce46MyJ3mo5CqLix6TmT64qxPOks,3267
88
88
  xp/models/log_entry.py,sha256=tAiNwouCP2d4jKiHJY9a-2iAi8LWTpG-TZsOPDIstlA,4423
89
89
  xp/models/protocol/__init__.py,sha256=TJ_CJKchA-xgQiv5vCo_ndBBZjrcaTmjT74bR0T-5Cw,38
90
90
  xp/models/protocol/conbus_protocol.py,sha256=hF78N5xvBzMiyWoKd8i_avA8kJ1As_9Pplkw1GMqKzk,9145
@@ -105,7 +105,8 @@ xp/models/telegram/system_telegram.py,sha256=064AlFi_WghoYlVDUtIIbvwtZyUEZj7_auK
105
105
  xp/models/telegram/telegram.py,sha256=-kNloBlwMJ5w1-FAMSLzBPnyOGUNEBG3SG2d0eTi2PY,847
106
106
  xp/models/telegram/telegram_type.py,sha256=IjGEosbs7IDqYT7ktn-FcKS-kAJ4eXW-KJGkkoAGysw,428
107
107
  xp/models/telegram/timeparam_type.py,sha256=z5EQ32SQjDi7zKshtkvDzqaMfPMUeXCWKEGI5VgvBvU,1142
108
- xp/models/term/__init__.py,sha256=aFvzGZHr_dI6USb8MJuYLSLMvxi_ZWMVtokHDt8428s,263
108
+ xp/models/term/__init__.py,sha256=VVZsEyXBEr-TnBlrFFifZ6PjJHUl2kwnRUZx_kC2Ljg,343
109
+ xp/models/term/accessory_state.py,sha256=GcMtCxOeHpc3IPDO1F9j2I6rdhaNV75iQ2md--XY6jo,1650
109
110
  xp/models/term/connection_state.py,sha256=oYcst01uH35kO541jGuXMqvJ2iduiHYryUsMK0d89pQ,1807
110
111
  xp/models/term/module_state.py,sha256=i7u8y_B5ScMRULQb_kMSD_wwKzbrLHlkECsTgNS46PQ,939
111
112
  xp/models/term/protocol_keys_config.py,sha256=tSlkxEwgQuVRYLTaUNd569osQsNCdb9ED4InNgX9rKo,1223
@@ -145,7 +146,7 @@ xp/services/conbus/write_config_service.py,sha256=BCfmLNPRDpwSwRMRYJvx2FXA8IZsdg
145
146
  xp/services/homekit/__init__.py,sha256=xAMKmln_AmEFdOOJGKWYi96seRlKDQpKx3-hm7XbdIo,36
146
147
  xp/services/homekit/homekit_cache_service.py,sha256=z1TB6icEqd1paoilVTewuFL0lXVCQbvrOJkJvvQECJY,11060
147
148
  xp/services/homekit/homekit_conbus_service.py,sha256=XPKv7Mit1rn7XLaQZcKmlMMUlyj-o0J2z8XBH3NaEIM,3390
148
- xp/services/homekit/homekit_config_validator.py,sha256=bLDOY8dbXvqvrDLtn4PijZA38qNmQWcQFWIY6hjLrl0,10875
149
+ xp/services/homekit/homekit_config_validator.py,sha256=jf09jHIFbZg7YpDbGsGHT1p4a1vpUED2xR6iZN19cfM,10875
149
150
  xp/services/homekit/homekit_conson_validator.py,sha256=tmUxBzytX9FbUWTR1XdbAi_qb_whAdGPSaml98Czszg,3858
150
151
  xp/services/homekit/homekit_dimminglight.py,sha256=EzfGhy3zZkbFPfN72Dh_eSb5mJQOpxGi6ZwnyEOSHxU,5819
151
152
  xp/services/homekit/homekit_dimminglight_service.py,sha256=0Ve6cXtY7v7JCv7gibOBWjPfCU7KK4Lk6GjIu9_GhyE,5282
@@ -184,9 +185,13 @@ xp/services/telegram/telegram_output_service.py,sha256=9deqtcPndRqJ-3XQUWlJhXaVc
184
185
  xp/services/telegram/telegram_service.py,sha256=jPu0Xrh3IpvqPLyuQT5Vf8HHw00vBingONHdxf_9TkI,13315
185
186
  xp/services/telegram/telegram_version_service.py,sha256=oXnZ_K7OQ7xD-GEj3zDYp52KlkqVuHpO4bf7gMlC_w4,10574
186
187
  xp/services/term/__init__.py,sha256=BIeOK042bMR-0l6MA80wdW5VuHlpWOXtRER9IG5ilQA,245
188
+ xp/services/term/homekit_accessory_driver.py,sha256=vCkeXHwIBwUIuUGoTdflbIOm8EQhT33pX8j4Smhr1co,5759
189
+ xp/services/term/homekit_service.py,sha256=QZnAzgOVIZ8vlfa1ehIp4Q1zZyIAj2f0SB1W8wdmemo,21895
187
190
  xp/services/term/protocol_monitor_service.py,sha256=5YBI0Nu7B7gMhaTbUhL6k9LSRfnCIj6CwrCYHiMHavA,10067
188
- xp/services/term/state_monitor_service.py,sha256=4CmeHf5k9mf67AleOB7byM7_g5WwbAOnmgxl6I2vBwg,17116
191
+ xp/services/term/state_monitor_service.py,sha256=EK9tNBfamAIV0z0EMsXDYWC-rXv6l6k_bHsC8xyEFSo,17116
189
192
  xp/term/__init__.py,sha256=Xg2DhBeI3xQJLfc7_BPWI1por-rUXemyer5OtOt9Cus,51
193
+ xp/term/homekit.py,sha256=xN2BiMHHF3tPR96ixg_qO5y6fDNTkjEzau3YDTNuk1U,3711
194
+ xp/term/homekit.tcss,sha256=qeR_OV8D_9Mxb-aPNz-MH0ZJOsdCk-fJ-zv6CQV5ihw,1382
190
195
  xp/term/protocol.py,sha256=6MX3mduLei-AgLGaIe8lfOSu4Hi0y3KGePFFM2ssstc,3475
191
196
  xp/term/protocol.tcss,sha256=r_KfxrbpycGHLVXqZc6INBBcUJME0hLrAZkF1oqnab4,2126
192
197
  xp/term/state.py,sha256=FBpYV_bWYJh9o17qcMx6sHgUARQS-uNOtUt6G7Vs1n8,3274
@@ -195,13 +200,14 @@ xp/term/widgets/__init__.py,sha256=ftWmN_fmjxy2E8Qfm-YSRmzKfgL0KTBCTpgvYWCPbUY,2
195
200
  xp/term/widgets/help_menu.py,sha256=KLkdIXfhARLFNEs2lv1u0sYBz9LzOCcDLxbMGzc7e5Y,1812
196
201
  xp/term/widgets/modules_list.py,sha256=qAG-n0nK0YdNE9v4C3-sHgxLvF1i1FR7v_GArdaoUQw,7831
197
202
  xp/term/widgets/protocol_log.py,sha256=E68QmSMpOFrvrPTo_gOQVfyiDqY5c_y8fkNKnQw6Vwo,2650
198
- xp/term/widgets/status_footer.py,sha256=YYAT0431p6jmrzzpVgaPhu7yGkRroWGv4e99t2XlkHI,3297
203
+ xp/term/widgets/room_list.py,sha256=3q3otusnQn4qFRbTY0-QbpMP3vPmywM0izYRA_KjXn0,7871
204
+ xp/term/widgets/status_footer.py,sha256=biV3EzfVSgm1T7Ofi88LXsTFCkD5mI_6Cpe-RpuOSxA,3429
199
205
  xp/utils/__init__.py,sha256=_avMF_UOkfR3tNeDIPqQ5odmbq5raKkaq1rZ9Cn1CJs,332
200
206
  xp/utils/checksum.py,sha256=Px1S3dFGA-_plavBxrq3IqmprNlgtNDunE3whg6Otwg,1722
201
- xp/utils/dependencies.py,sha256=I8Z89_iTCeR-7adpae3GVQUGZ-1gY6wg0OGKmdJGg3w,24536
207
+ xp/utils/dependencies.py,sha256=McCgnBrg0Jw98_bFQv3uPG0bYkGKHC-mFJPmILlvv5k,25754
202
208
  xp/utils/event_helper.py,sha256=zD0K3TPfGEThU9vUNlDtglTai3Cmm30727iwjDZy6Dk,1007
203
209
  xp/utils/logging.py,sha256=wJ1d-yg97NiZUrt2F8iDMcmnHVwC-PErcI-7dpyiRDc,3777
204
210
  xp/utils/serialization.py,sha256=TS1OwpTOemSvXsCGw3js4JkYYFEqkzrPe8V9QYQefdw,4684
205
211
  xp/utils/state_machine.py,sha256=W9AY4ntRZnFeHAa5d43hm37j53uJPlqkRvWTPiBhJ_0,2464
206
212
  xp/utils/time_utils.py,sha256=K17godWpL18VEypbTlvNOEDG6R3huYnf29yjkcnwRpU,3796
207
- conson_xp-1.51.0.dist-info/RECORD,,
213
+ conson_xp-1.52.0.dist-info/RECORD,,
xp/__init__.py CHANGED
@@ -4,7 +4,7 @@ XP CLI tool for remote console bus operations.
4
4
  conson-xp package.
5
5
  """
6
6
 
7
- __version__ = "1.51.0"
7
+ __version__ = "1.52.0"
8
8
  __manufacturer__ = "salchichon"
9
9
  __model__ = "xp.cli"
10
10
  __serial__ = "2025.09.23.000"
@@ -48,3 +48,26 @@ def state_monitor(ctx: Context) -> None:
48
48
 
49
49
  # Resolve StateMonitorApp from container and run
50
50
  ctx.obj.get("container").get_container().resolve(StateMonitorApp).run()
51
+
52
+
53
+ @term.command("homekit")
54
+ @click.pass_context
55
+ def homekit_monitor(ctx: Context) -> None:
56
+ r"""
57
+ Start TUI for HomeKit accessory monitoring.
58
+
59
+ Displays HomeKit rooms and accessories with real-time state updates
60
+ in an interactive terminal interface. Press action keys (a-z) to
61
+ toggle accessories.
62
+
63
+ Args:
64
+ ctx: Click context object.
65
+
66
+ Examples:
67
+ \b
68
+ xp term homekit
69
+ """
70
+ from xp.term.homekit import HomekitApp
71
+
72
+ # Resolve HomekitApp from container and run
73
+ ctx.obj.get("container").get_container().resolve(HomekitApp).run()
@@ -16,10 +16,14 @@ class NetworkConfig(BaseModel):
16
16
  Attributes:
17
17
  ip: IP address for the network connection.
18
18
  port: Port number for the network connection.
19
+ pincode: HomeKit pairing code (format: XXX-XX-XXX).
20
+ accessory_state_file: Path to file for persisting accessory state.
19
21
  """
20
22
 
21
23
  ip: Union[IPvAnyAddress, IPv4Address, IPv6Address, str] = "127.0.0.1"
22
24
  port: int = 51826
25
+ pincode: str = "031-45-154"
26
+ accessory_state_file: str = "./accessory.state"
23
27
 
24
28
 
25
29
  class RoomConfig(BaseModel):
@@ -61,6 +65,7 @@ class HomekitAccessoryConfig(BaseModel):
61
65
  service: Service type for the accessory.
62
66
  on_action: on code for the accessory.
63
67
  off_action: off code for the accessory.
68
+ toggle_action: Optional toggle action code for the accessory.
64
69
  hap_accessory: Optional HAP accessory identifier.
65
70
  """
66
71
 
@@ -72,6 +77,7 @@ class HomekitAccessoryConfig(BaseModel):
72
77
  service: str
73
78
  on_action: str
74
79
  off_action: str
80
+ toggle_action: Optional[str] = None
75
81
  hap_accessory: Optional[int] = None
76
82
 
77
83
 
@@ -1,5 +1,6 @@
1
1
  """Terminal UI models."""
2
2
 
3
+ from xp.models.term.accessory_state import AccessoryState
3
4
  from xp.models.term.module_state import ModuleState
4
5
  from xp.models.term.protocol_keys_config import (
5
6
  ProtocolKeyConfig,
@@ -7,6 +8,7 @@ from xp.models.term.protocol_keys_config import (
7
8
  )
8
9
 
9
10
  __all__ = [
11
+ "AccessoryState",
10
12
  "ModuleState",
11
13
  "ProtocolKeyConfig",
12
14
  "ProtocolKeysConfig",
@@ -0,0 +1,50 @@
1
+ """Accessory state data model for Homekit TUI."""
2
+
3
+ from dataclasses import dataclass
4
+ from datetime import datetime
5
+ from typing import Optional
6
+
7
+
8
+ @dataclass
9
+ class AccessoryState:
10
+ """
11
+ State of a HomeKit accessory for TUI display.
12
+
13
+ Attributes:
14
+ room_name: Room containing the accessory (e.g., "Salon").
15
+ accessory_name: Accessory display name (e.g., "Variateur salon").
16
+ action: Action key (a-z) for toggle control.
17
+ output_state: Output state ("ON", "OFF", "?").
18
+ dimming_state: Dimming percentage for dimmable modules, "-" if OFF, empty otherwise.
19
+ module_name: Module identifier (e.g., "A12").
20
+ serial_number: Module serial number.
21
+ module_type: Module type (e.g., "XP24", "XP33LED").
22
+ error_status: Status code ("OK" or error like "E10").
23
+ output: Module output number (1-based for display).
24
+ sort: Sort accessories according to homekit.yml configuration.
25
+ last_update: Last communication timestamp. None if never updated.
26
+ toggle_action: Raw toggle action telegram (e.g., "E02L12I02").
27
+ """
28
+
29
+ room_name: str
30
+ accessory_name: str
31
+ action: str
32
+ output_state: str
33
+ dimming_state: str
34
+ module_name: str
35
+ serial_number: str
36
+ module_type: str
37
+ error_status: str
38
+ output: int
39
+ sort: int
40
+ last_update: Optional[datetime] = None
41
+ toggle_action: Optional[str] = None
42
+
43
+ def is_dimmable(self) -> bool:
44
+ """
45
+ Check if accessory is dimmable.
46
+
47
+ Returns:
48
+ True if module type is XP33LR or XP33LED, False otherwise.
49
+ """
50
+ return self.module_type in ("XP33LR", "XP33LED")
@@ -221,7 +221,7 @@ class CrossReferenceValidator:
221
221
  # Define output limits by module type
222
222
  output_limits = {
223
223
  "XP130": 0, # Example limits
224
- "XP20": 0,
224
+ "XP20": 8,
225
225
  "XP24": 4,
226
226
  "XP33": 3,
227
227
  "XP33LR": 3,
@@ -0,0 +1,168 @@
1
+ """HomeKit Accessory Driver for pyhap integration."""
2
+
3
+ import asyncio
4
+ import logging
5
+ from typing import Callable, Dict, Optional
6
+
7
+ from pyhap.accessory import Accessory, Bridge
8
+ from pyhap.accessory_driver import AccessoryDriver
9
+ from pyhap.const import CATEGORY_LIGHTBULB, CATEGORY_OUTLET
10
+
11
+ from xp.models.homekit.homekit_config import HomekitConfig
12
+
13
+
14
+ class XPAccessory(Accessory):
15
+ """Single accessory wrapping a Conbus output."""
16
+
17
+ def __init__(
18
+ self,
19
+ driver: "HomekitAccessoryDriver",
20
+ name: str,
21
+ service_type: str,
22
+ aid: int,
23
+ ) -> None:
24
+ """
25
+ Initialize the XP accessory.
26
+
27
+ Args:
28
+ driver: HomekitAccessoryDriver instance.
29
+ name: Accessory name (unique identifier and display name).
30
+ service_type: Service type ('light', 'outlet', 'dimminglight').
31
+ aid: Accessory ID for HomeKit.
32
+ """
33
+ super().__init__(driver._driver, name, aid=aid)
34
+ self._hk_driver = driver
35
+ self._accessory_id = name
36
+ self.logger = logging.getLogger(__name__)
37
+
38
+ if service_type == "dimminglight":
39
+ self.category = CATEGORY_LIGHTBULB
40
+ serv = self.add_preload_service("Lightbulb", chars=["On", "Brightness"])
41
+ # Note: Brightness setter_callback deferred to future update
42
+ elif service_type == "outlet":
43
+ self.category = CATEGORY_OUTLET
44
+ serv = self.add_preload_service("Outlet")
45
+ else:
46
+ self.category = CATEGORY_LIGHTBULB
47
+ serv = self.add_preload_service("Lightbulb")
48
+
49
+ self._char_on = serv.configure_char("On", setter_callback=self._set_on)
50
+
51
+ def _set_on(self, value: bool) -> None:
52
+ """
53
+ Handle HomeKit set on/off request.
54
+
55
+ Args:
56
+ value: True for on, False for off.
57
+ """
58
+ if self._hk_driver._on_set:
59
+ self._hk_driver._on_set(self._accessory_id, value)
60
+
61
+ def update_state(self, is_on: bool) -> None:
62
+ """
63
+ Update accessory state from Conbus event.
64
+
65
+ Args:
66
+ is_on: True if accessory is on, False otherwise.
67
+ """
68
+ self._char_on.set_value(is_on)
69
+
70
+
71
+ class HomekitAccessoryDriver:
72
+ """Wrapper around pyhap AccessoryDriver."""
73
+
74
+ def __init__(self, homekit_config: HomekitConfig) -> None:
75
+ """
76
+ Initialize the HomeKit accessory driver.
77
+
78
+ Args:
79
+ homekit_config: HomekitConfig with network and accessory settings.
80
+ """
81
+ self.logger = logging.getLogger(__name__)
82
+ self._homekit_config = homekit_config
83
+ self._driver: Optional[AccessoryDriver] = None
84
+ self._accessories: Dict[str, XPAccessory] = {}
85
+ self._on_set: Optional[Callable[[str, bool], None]] = None
86
+
87
+ def set_callback(self, on_set: Callable[[str, bool], None]) -> None:
88
+ """
89
+ Set callback for HomeKit set events.
90
+
91
+ Args:
92
+ on_set: Callback(accessory_name, is_on) called when HomeKit app toggles.
93
+ """
94
+ self._on_set = on_set
95
+
96
+ def _setup_bridge(self, config: HomekitConfig) -> None:
97
+ """
98
+ Set up HomeKit bridge with accessories.
99
+
100
+ Args:
101
+ config: HomekitConfig with accessory definitions.
102
+ """
103
+ assert self._driver is not None
104
+ bridge = Bridge(self._driver, config.bridge.name)
105
+ aid = 2 # Bridge is 1
106
+
107
+ for acc_config in config.accessories:
108
+ accessory = XPAccessory(
109
+ driver=self,
110
+ name=acc_config.name,
111
+ service_type=acc_config.service,
112
+ aid=aid,
113
+ )
114
+ bridge.add_accessory(accessory)
115
+ self._accessories[acc_config.name] = accessory
116
+ aid += 1
117
+
118
+ self._driver.add_accessory(bridge)
119
+
120
+ async def start(self) -> None:
121
+ """Start the AccessoryDriver (non-blocking)."""
122
+ try:
123
+ # Enable pyhap debug logging
124
+ pyhap_logger = logging.getLogger("pyhap")
125
+ pyhap_logger.setLevel(logging.DEBUG)
126
+
127
+ # Create driver with the running event loop
128
+ loop = asyncio.get_running_loop()
129
+ config = self._homekit_config
130
+ pincode = config.homekit.pincode.encode()
131
+ self.logger.info(
132
+ f"Starting HAP driver on {config.homekit.ip}:{config.homekit.port} with pincode {config.homekit.pincode}"
133
+ )
134
+ self._driver = AccessoryDriver(
135
+ loop=loop,
136
+ address=str(config.homekit.ip),
137
+ port=config.homekit.port,
138
+ pincode=pincode,
139
+ persist_file=config.homekit.accessory_state_file,
140
+ )
141
+ self._setup_bridge(config)
142
+ await self._driver.async_start()
143
+ self.logger.info("AccessoryDriver started successfully")
144
+ except Exception as e:
145
+ self.logger.error(f"Error starting AccessoryDriver: {e}", exc_info=True)
146
+
147
+ async def stop(self) -> None:
148
+ """Stop the AccessoryDriver."""
149
+ if not self._driver:
150
+ return
151
+ try:
152
+ await self._driver.async_stop()
153
+ self.logger.info("AccessoryDriver stopped successfully")
154
+ except Exception as e:
155
+ self.logger.error(f"Error stopping AccessoryDriver: {e}", exc_info=True)
156
+
157
+ def update_state(self, accessory_name: str, is_on: bool) -> None:
158
+ """
159
+ Update accessory state from Conbus event.
160
+
161
+ Args:
162
+ accessory_name: Accessory name to update.
163
+ is_on: True if accessory is on, False otherwise.
164
+ """
165
+ if acc := self._accessories.get(accessory_name):
166
+ acc.update_state(is_on)
167
+ else:
168
+ self.logger.warning(f"Unknown accessory name: {accessory_name}")