conson-xp 1.51.1__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.
- {conson_xp-1.51.1.dist-info → conson_xp-1.52.0.dist-info}/METADATA +1 -1
- {conson_xp-1.51.1.dist-info → conson_xp-1.52.0.dist-info}/RECORD +11 -10
- xp/__init__.py +1 -1
- xp/models/homekit/homekit_config.py +4 -0
- xp/services/term/homekit_accessory_driver.py +168 -0
- xp/services/term/homekit_service.py +70 -0
- xp/term/homekit.py +6 -6
- xp/utils/dependencies.py +10 -0
- {conson_xp-1.51.1.dist-info → conson_xp-1.52.0.dist-info}/WHEEL +0 -0
- {conson_xp-1.51.1.dist-info → conson_xp-1.52.0.dist-info}/entry_points.txt +0 -0
- {conson_xp-1.51.1.dist-info → conson_xp-1.52.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
conson_xp-1.
|
|
2
|
-
conson_xp-1.
|
|
3
|
-
conson_xp-1.
|
|
4
|
-
conson_xp-1.
|
|
5
|
-
xp/__init__.py,sha256=
|
|
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
|
|
@@ -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=
|
|
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
|
|
@@ -185,11 +185,12 @@ xp/services/telegram/telegram_output_service.py,sha256=9deqtcPndRqJ-3XQUWlJhXaVc
|
|
|
185
185
|
xp/services/telegram/telegram_service.py,sha256=jPu0Xrh3IpvqPLyuQT5Vf8HHw00vBingONHdxf_9TkI,13315
|
|
186
186
|
xp/services/telegram/telegram_version_service.py,sha256=oXnZ_K7OQ7xD-GEj3zDYp52KlkqVuHpO4bf7gMlC_w4,10574
|
|
187
187
|
xp/services/term/__init__.py,sha256=BIeOK042bMR-0l6MA80wdW5VuHlpWOXtRER9IG5ilQA,245
|
|
188
|
-
xp/services/term/
|
|
188
|
+
xp/services/term/homekit_accessory_driver.py,sha256=vCkeXHwIBwUIuUGoTdflbIOm8EQhT33pX8j4Smhr1co,5759
|
|
189
|
+
xp/services/term/homekit_service.py,sha256=QZnAzgOVIZ8vlfa1ehIp4Q1zZyIAj2f0SB1W8wdmemo,21895
|
|
189
190
|
xp/services/term/protocol_monitor_service.py,sha256=5YBI0Nu7B7gMhaTbUhL6k9LSRfnCIj6CwrCYHiMHavA,10067
|
|
190
191
|
xp/services/term/state_monitor_service.py,sha256=EK9tNBfamAIV0z0EMsXDYWC-rXv6l6k_bHsC8xyEFSo,17116
|
|
191
192
|
xp/term/__init__.py,sha256=Xg2DhBeI3xQJLfc7_BPWI1por-rUXemyer5OtOt9Cus,51
|
|
192
|
-
xp/term/homekit.py,sha256=
|
|
193
|
+
xp/term/homekit.py,sha256=xN2BiMHHF3tPR96ixg_qO5y6fDNTkjEzau3YDTNuk1U,3711
|
|
193
194
|
xp/term/homekit.tcss,sha256=qeR_OV8D_9Mxb-aPNz-MH0ZJOsdCk-fJ-zv6CQV5ihw,1382
|
|
194
195
|
xp/term/protocol.py,sha256=6MX3mduLei-AgLGaIe8lfOSu4Hi0y3KGePFFM2ssstc,3475
|
|
195
196
|
xp/term/protocol.tcss,sha256=r_KfxrbpycGHLVXqZc6INBBcUJME0hLrAZkF1oqnab4,2126
|
|
@@ -203,10 +204,10 @@ xp/term/widgets/room_list.py,sha256=3q3otusnQn4qFRbTY0-QbpMP3vPmywM0izYRA_KjXn0,
|
|
|
203
204
|
xp/term/widgets/status_footer.py,sha256=biV3EzfVSgm1T7Ofi88LXsTFCkD5mI_6Cpe-RpuOSxA,3429
|
|
204
205
|
xp/utils/__init__.py,sha256=_avMF_UOkfR3tNeDIPqQ5odmbq5raKkaq1rZ9Cn1CJs,332
|
|
205
206
|
xp/utils/checksum.py,sha256=Px1S3dFGA-_plavBxrq3IqmprNlgtNDunE3whg6Otwg,1722
|
|
206
|
-
xp/utils/dependencies.py,sha256=
|
|
207
|
+
xp/utils/dependencies.py,sha256=McCgnBrg0Jw98_bFQv3uPG0bYkGKHC-mFJPmILlvv5k,25754
|
|
207
208
|
xp/utils/event_helper.py,sha256=zD0K3TPfGEThU9vUNlDtglTai3Cmm30727iwjDZy6Dk,1007
|
|
208
209
|
xp/utils/logging.py,sha256=wJ1d-yg97NiZUrt2F8iDMcmnHVwC-PErcI-7dpyiRDc,3777
|
|
209
210
|
xp/utils/serialization.py,sha256=TS1OwpTOemSvXsCGw3js4JkYYFEqkzrPe8V9QYQefdw,4684
|
|
210
211
|
xp/utils/state_machine.py,sha256=W9AY4ntRZnFeHAa5d43hm37j53uJPlqkRvWTPiBhJ_0,2464
|
|
211
212
|
xp/utils/time_utils.py,sha256=K17godWpL18VEypbTlvNOEDG6R3huYnf29yjkcnwRpU,3796
|
|
212
|
-
conson_xp-1.
|
|
213
|
+
conson_xp-1.52.0.dist-info/RECORD,,
|
xp/__init__.py
CHANGED
|
@@ -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):
|
|
@@ -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}")
|
|
@@ -18,6 +18,7 @@ from xp.models.term.connection_state import ConnectionState
|
|
|
18
18
|
from xp.services.protocol.conbus_event_protocol import ConbusEventProtocol
|
|
19
19
|
from xp.services.telegram.telegram_output_service import TelegramOutputService
|
|
20
20
|
from xp.services.telegram.telegram_service import TelegramService
|
|
21
|
+
from xp.services.term.homekit_accessory_driver import HomekitAccessoryDriver
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
class HomekitService:
|
|
@@ -50,6 +51,7 @@ class HomekitService:
|
|
|
50
51
|
homekit_config: HomekitConfig,
|
|
51
52
|
conson_config: ConsonModuleListConfig,
|
|
52
53
|
telegram_service: TelegramService,
|
|
54
|
+
accessory_driver: HomekitAccessoryDriver,
|
|
53
55
|
) -> None:
|
|
54
56
|
"""
|
|
55
57
|
Initialize the HomeKit service.
|
|
@@ -59,12 +61,14 @@ class HomekitService:
|
|
|
59
61
|
homekit_config: HomekitConfig for accessory configuration.
|
|
60
62
|
conson_config: ConsonModuleListConfig for module configuration.
|
|
61
63
|
telegram_service: TelegramService for parsing telegrams.
|
|
64
|
+
accessory_driver: HomekitAccessoryDriver for pyhap integration.
|
|
62
65
|
"""
|
|
63
66
|
self.logger = logging.getLogger(__name__)
|
|
64
67
|
self._conbus_protocol = conbus_protocol
|
|
65
68
|
self._homekit_config = homekit_config
|
|
66
69
|
self._conson_config = conson_config
|
|
67
70
|
self._telegram_service = telegram_service
|
|
71
|
+
self._accessory_driver = accessory_driver
|
|
68
72
|
self._connection_state = ConnectionState.DISCONNECTED
|
|
69
73
|
self._state_machine = ConnectionState.create_state_machine()
|
|
70
74
|
|
|
@@ -74,6 +78,9 @@ class HomekitService:
|
|
|
74
78
|
# Action key to accessory ID mapping
|
|
75
79
|
self._action_map: Dict[str, str] = {}
|
|
76
80
|
|
|
81
|
+
# Set up HomeKit callback
|
|
82
|
+
self._accessory_driver.set_callback(self._on_homekit_set)
|
|
83
|
+
|
|
77
84
|
# Connect to protocol signals
|
|
78
85
|
self._connect_signals()
|
|
79
86
|
|
|
@@ -152,6 +159,27 @@ class HomekitService:
|
|
|
152
159
|
return accessory
|
|
153
160
|
return None
|
|
154
161
|
|
|
162
|
+
def _find_accessory_config_by_output(
|
|
163
|
+
self, serial_number: str, output: int
|
|
164
|
+
) -> Optional[HomekitAccessoryConfig]:
|
|
165
|
+
"""
|
|
166
|
+
Find accessory config by serial number and output.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
serial_number: Module serial number.
|
|
170
|
+
output: Output number (1-based).
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
HomekitAccessoryConfig if found, None otherwise.
|
|
174
|
+
"""
|
|
175
|
+
for accessory in self._homekit_config.accessories:
|
|
176
|
+
if (
|
|
177
|
+
accessory.serial_number == serial_number
|
|
178
|
+
and accessory.output_number == output - 1
|
|
179
|
+
):
|
|
180
|
+
return accessory
|
|
181
|
+
return None
|
|
182
|
+
|
|
155
183
|
def _connect_signals(self) -> None:
|
|
156
184
|
"""Connect to protocol signals."""
|
|
157
185
|
self._conbus_protocol.on_connection_made.connect(self._on_connection_made)
|
|
@@ -242,6 +270,34 @@ class HomekitService:
|
|
|
242
270
|
self.on_connection_state_changed.emit(self._connection_state)
|
|
243
271
|
self.on_status_message.emit("Disconnected")
|
|
244
272
|
|
|
273
|
+
async def start(self) -> None:
|
|
274
|
+
"""Start the service and AccessoryDriver."""
|
|
275
|
+
self.connect()
|
|
276
|
+
await self._accessory_driver.start()
|
|
277
|
+
|
|
278
|
+
async def stop(self) -> None:
|
|
279
|
+
"""Stop the AccessoryDriver and cleanup."""
|
|
280
|
+
await self._accessory_driver.stop()
|
|
281
|
+
self.cleanup()
|
|
282
|
+
|
|
283
|
+
def _on_homekit_set(self, accessory_name: str, is_on: bool) -> None:
|
|
284
|
+
"""
|
|
285
|
+
Handle HomeKit app toggle request.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
accessory_name: Accessory name from HomeKit.
|
|
289
|
+
is_on: True for on, False for off.
|
|
290
|
+
"""
|
|
291
|
+
config = self._find_accessory_config(accessory_name)
|
|
292
|
+
if config:
|
|
293
|
+
action = config.on_action if is_on else config.off_action
|
|
294
|
+
self._conbus_protocol.send_raw_telegram(action)
|
|
295
|
+
self.on_status_message.emit(
|
|
296
|
+
f"HomeKit: {accessory_name} {'ON' if is_on else 'OFF'}"
|
|
297
|
+
)
|
|
298
|
+
else:
|
|
299
|
+
self.logger.warning(f"No config found for accessory: {accessory_name}")
|
|
300
|
+
|
|
245
301
|
def toggle_connection(self) -> None:
|
|
246
302
|
"""
|
|
247
303
|
Toggle connection state between connected and disconnected.
|
|
@@ -406,6 +462,13 @@ class HomekitService:
|
|
|
406
462
|
# Update dimming state for dimmable modules
|
|
407
463
|
if state.is_dimmable():
|
|
408
464
|
state.dimming_state = "-" if not is_on else ""
|
|
465
|
+
|
|
466
|
+
# Sync to HomeKit
|
|
467
|
+
config = self._find_accessory_config_by_output(
|
|
468
|
+
serial_number, state.output
|
|
469
|
+
)
|
|
470
|
+
if config:
|
|
471
|
+
self._accessory_driver.update_state(config.name, is_on)
|
|
409
472
|
else:
|
|
410
473
|
state.output_state = "?"
|
|
411
474
|
|
|
@@ -463,6 +526,13 @@ class HomekitService:
|
|
|
463
526
|
if state.is_dimmable():
|
|
464
527
|
state.dimming_state = "-" if not is_on else ""
|
|
465
528
|
|
|
529
|
+
# Sync to HomeKit
|
|
530
|
+
config = self._find_accessory_config_by_output(
|
|
531
|
+
state.serial_number, state.output
|
|
532
|
+
)
|
|
533
|
+
if config:
|
|
534
|
+
self._accessory_driver.update_state(config.name, is_on)
|
|
535
|
+
|
|
466
536
|
state.last_update = datetime.now()
|
|
467
537
|
self.on_module_state_changed.emit(state)
|
|
468
538
|
|
xp/term/homekit.py
CHANGED
|
@@ -68,14 +68,14 @@ class HomekitApp(App[None]):
|
|
|
68
68
|
"""
|
|
69
69
|
Initialize app after UI is mounted.
|
|
70
70
|
|
|
71
|
-
Delays connection by 0.5s to let UI render first.
|
|
72
|
-
refresh every second to update elapsed times.
|
|
71
|
+
Delays connection by 0.5s to let UI render first. Starts the AccessoryDriver and
|
|
72
|
+
sets up automatic screen refresh every second to update elapsed times.
|
|
73
73
|
"""
|
|
74
74
|
import asyncio
|
|
75
75
|
|
|
76
76
|
# Delay connection to let UI render
|
|
77
77
|
await asyncio.sleep(0.5)
|
|
78
|
-
self.homekit_service.
|
|
78
|
+
await self.homekit_service.start()
|
|
79
79
|
|
|
80
80
|
# Set up periodic refresh to update elapsed times
|
|
81
81
|
self.set_interval(1.0, self._refresh_last_update_column)
|
|
@@ -111,6 +111,6 @@ class HomekitApp(App[None]):
|
|
|
111
111
|
"""Refresh all module data on 'r' key press."""
|
|
112
112
|
self.homekit_service.refresh_all()
|
|
113
113
|
|
|
114
|
-
def on_unmount(self) -> None:
|
|
115
|
-
"""
|
|
116
|
-
self.homekit_service.
|
|
114
|
+
async def on_unmount(self) -> None:
|
|
115
|
+
"""Stop AccessoryDriver and clean up service when app unmounts."""
|
|
116
|
+
await self.homekit_service.stop()
|
xp/utils/dependencies.py
CHANGED
|
@@ -77,6 +77,7 @@ from xp.services.telegram.telegram_discover_service import TelegramDiscoverServi
|
|
|
77
77
|
from xp.services.telegram.telegram_link_number_service import LinkNumberService
|
|
78
78
|
from xp.services.telegram.telegram_output_service import TelegramOutputService
|
|
79
79
|
from xp.services.telegram.telegram_service import TelegramService
|
|
80
|
+
from xp.services.term.homekit_accessory_driver import HomekitAccessoryDriver
|
|
80
81
|
from xp.services.term.homekit_service import HomekitService
|
|
81
82
|
from xp.services.term.protocol_monitor_service import ProtocolMonitorService
|
|
82
83
|
from xp.services.term.state_monitor_service import StateMonitorService
|
|
@@ -267,6 +268,14 @@ class ServiceContainer:
|
|
|
267
268
|
scope=punq.Scope.singleton,
|
|
268
269
|
)
|
|
269
270
|
|
|
271
|
+
self.container.register(
|
|
272
|
+
HomekitAccessoryDriver,
|
|
273
|
+
factory=lambda: HomekitAccessoryDriver(
|
|
274
|
+
homekit_config=self.container.resolve(HomekitConfig),
|
|
275
|
+
),
|
|
276
|
+
scope=punq.Scope.singleton,
|
|
277
|
+
)
|
|
278
|
+
|
|
270
279
|
self.container.register(
|
|
271
280
|
HomekitService,
|
|
272
281
|
factory=lambda: HomekitService(
|
|
@@ -274,6 +283,7 @@ class ServiceContainer:
|
|
|
274
283
|
homekit_config=self.container.resolve(HomekitConfig),
|
|
275
284
|
conson_config=self.container.resolve(ConsonModuleListConfig),
|
|
276
285
|
telegram_service=self.container.resolve(TelegramService),
|
|
286
|
+
accessory_driver=self.container.resolve(HomekitAccessoryDriver),
|
|
277
287
|
),
|
|
278
288
|
scope=punq.Scope.singleton,
|
|
279
289
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|