7
7
from datetime import datetime
8
8
from functools import partial
9
9
import logging
10
- import pathlib
11
- from typing import TYPE_CHECKING , Any , Callable , Deque , Final , Type , TypeVar , cast
10
+ from typing import TYPE_CHECKING , Any , Callable , Deque , Type , TypeVar , cast
12
11
13
12
from chip .ChipDeviceCtrl import CommissionableNode
14
13
from chip .clusters import Attribute , Objects as Clusters
15
14
from chip .clusters .ClusterObjects import ALL_CLUSTERS , Cluster
16
15
from chip .exceptions import ChipStackError
17
16
18
- from matter_server .server .helpers .paa_certificates import fetch_certificates
19
-
20
17
from ..common .const import SCHEMA_VERSION
21
18
from ..common .errors import (
22
19
NodeCommissionFailed ,
31
28
dataclass_from_dict ,
32
29
)
33
30
from ..common .models import APICommand , EventType , MatterNodeData
31
+ from .const import PAA_ROOT_CERTS_DIR
32
+ from .helpers .paa_certificates import fetch_certificates
34
33
35
34
if TYPE_CHECKING :
36
- from .server import MatterServer
37
35
from chip .ChipDeviceCtrl import ChipDeviceController
38
36
37
+ from .server import MatterServer
38
+
39
39
_T = TypeVar ("_T" )
40
40
41
41
DATA_KEY_NODES = "nodes"
42
42
DATA_KEY_LAST_NODE_ID = "last_node_id"
43
43
44
44
LOGGER = logging .getLogger (__name__ )
45
45
46
- # the paa-root-certs path is hardcoded in the sdk at this time
47
- # and always uses the development subfolder
48
- # regardless of anything you pass into instantiating the controller
49
- # revisit this once matter 1.1 is released
50
- PAA_ROOT_CERTS_DIR : Final [pathlib .Path ] = (
51
- pathlib .Path (__file__ )
52
- .parent .resolve ()
53
- .parent .resolve ()
54
- .parent .resolve ()
55
- .joinpath ("credentials/development/paa-root-certs" )
56
- )
57
-
58
46
59
47
class MatterDeviceController :
60
48
"""Class that manages the Matter devices."""
@@ -67,10 +55,6 @@ def __init__(
67
55
):
68
56
"""Initialize the device controller."""
69
57
self .server = server
70
- # Instantiate the underlying ChipDeviceController instance on the Fabric
71
- if not PAA_ROOT_CERTS_DIR .is_dir ():
72
- raise RuntimeError ("PAA certificates directory not found" )
73
-
74
58
# we keep the last events in memory so we can include them in the diagnostics dump
75
59
self .event_history : Deque [Attribute .EventReadResult ] = deque (maxlen = 25 )
76
60
self ._subscriptions : dict [int , Attribute .SubscriptionTransaction ] = {}
@@ -84,7 +68,8 @@ async def initialize(self) -> None:
84
68
"""Async initialize of controller."""
85
69
# (re)fetch all PAA certificates once at startup
86
70
# NOTE: this must be done before initializing the controller
87
- await fetch_certificates (PAA_ROOT_CERTS_DIR )
71
+ await fetch_certificates ()
72
+ # Instantiate the underlying ChipDeviceController instance on the Fabric
88
73
self .chip_controller = self .server .stack .fabric_admin .NewController (
89
74
paaTrustStorePath = str (PAA_ROOT_CERTS_DIR )
90
75
)
@@ -117,6 +102,9 @@ async def start(self) -> None:
117
102
118
103
async def stop (self ) -> None :
119
104
"""Handle logic on server stop."""
105
+ if self .chip_controller is None :
106
+ raise RuntimeError ("Device Controller not initialized." )
107
+
120
108
# unsubscribe all node subscriptions
121
109
for sub in self ._subscriptions .values ():
122
110
await self ._call_sdk (sub .Shutdown )
@@ -147,9 +135,12 @@ async def commission_with_code(self, code: str) -> MatterNodeData:
147
135
148
136
Returns full NodeInfo once complete.
149
137
"""
138
+ if self .chip_controller is None :
139
+ raise RuntimeError ("Device Controller not initialized." )
140
+
150
141
# perform a quick delta sync of certificates to make sure
151
142
# we have the latest paa root certs
152
- await fetch_certificates (PAA_ROOT_CERTS_DIR )
143
+ await fetch_certificates ()
153
144
node_id = self ._get_next_node_id ()
154
145
155
146
success = await self ._call_sdk (
@@ -184,11 +175,14 @@ async def commission_on_network(
184
175
a string or None depending on the actual type of selected filter.
185
176
Returns full NodeInfo once complete.
186
177
"""
178
+ if self .chip_controller is None :
179
+ raise RuntimeError ("Device Controller not initialized." )
180
+
187
181
# perform a quick delta sync of certificates to make sure
188
182
# we have the latest paa root certs
189
183
# NOTE: Its not very clear if the newly fetched certificates can be used without
190
184
# restarting the device controller
191
- await fetch_certificates (PAA_ROOT_CERTS_DIR )
185
+ await fetch_certificates ()
192
186
193
187
node_id = self ._get_next_node_id ()
194
188
@@ -214,6 +208,9 @@ async def commission_on_network(
214
208
@api_command (APICommand .SET_WIFI_CREDENTIALS )
215
209
async def set_wifi_credentials (self , ssid : str , credentials : str ) -> None :
216
210
"""Set WiFi credentials for commissioning to a (new) device."""
211
+ if self .chip_controller is None :
212
+ raise RuntimeError ("Device Controller not initialized." )
213
+
217
214
await self ._call_sdk (
218
215
self .chip_controller .SetWiFiCredentials ,
219
216
ssid = ssid ,
@@ -225,6 +222,9 @@ async def set_wifi_credentials(self, ssid: str, credentials: str) -> None:
225
222
@api_command (APICommand .SET_THREAD_DATASET )
226
223
async def set_thread_operational_dataset (self , dataset : str ) -> None :
227
224
"""Set Thread Operational dataset in the stack."""
225
+ if self .chip_controller is None :
226
+ raise RuntimeError ("Device Controller not initialized." )
227
+
228
228
await self ._call_sdk (
229
229
self .chip_controller .SetThreadOperationalDataset ,
230
230
threadOperationalDataset = bytes .fromhex (dataset ),
@@ -246,6 +246,9 @@ async def open_commissioning_window(
246
246
247
247
Returns code to use as discriminator.
248
248
"""
249
+ if self .chip_controller is None :
250
+ raise RuntimeError ("Device Controller not initialized." )
251
+
249
252
if discriminator is None :
250
253
discriminator = 3840 # TODO generate random one
251
254
@@ -264,6 +267,8 @@ async def discover_commissionable_nodes(
264
267
self ,
265
268
) -> CommissionableNode | list [CommissionableNode ] | None :
266
269
"""Discover Commissionable Nodes (discovered on BLE or mDNS)."""
270
+ if self .chip_controller is None :
271
+ raise RuntimeError ("Device Controller not initialized." )
267
272
268
273
result = await self ._call_sdk (
269
274
self .chip_controller .DiscoverCommissionableNodes ,
@@ -273,6 +278,9 @@ async def discover_commissionable_nodes(
273
278
@api_command (APICommand .INTERVIEW_NODE )
274
279
async def interview_node (self , node_id : int ) -> None :
275
280
"""Interview a node."""
281
+ if self .chip_controller is None :
282
+ raise RuntimeError ("Device Controller not initialized." )
283
+
276
284
LOGGER .debug ("Interviewing node: %s" , node_id )
277
285
try :
278
286
await self ._call_sdk (self .chip_controller .ResolveNode , nodeid = node_id )
@@ -328,6 +336,9 @@ async def send_device_command(
328
336
interaction_timeout_ms : int | None = None ,
329
337
) -> Any :
330
338
"""Send a command to a Matter node/device."""
339
+ if self .chip_controller is None :
340
+ raise RuntimeError ("Device Controller not initialized." )
341
+
331
342
cluster_cls : Cluster = ALL_CLUSTERS [cluster_id ]
332
343
command_cls = getattr (cluster_cls .Commands , command_name )
333
344
command = dataclass_from_dict (command_cls , payload )
@@ -343,6 +354,9 @@ async def send_device_command(
343
354
@api_command (APICommand .REMOVE_NODE )
344
355
async def remove_node (self , node_id : int ) -> None :
345
356
"""Remove a Matter node/device from the fabric."""
357
+ if self .chip_controller is None :
358
+ raise RuntimeError ("Device Controller not initialized." )
359
+
346
360
if node_id not in self ._nodes :
347
361
raise NodeNotExists (
348
362
f"Node { node_id } does not exist or has not been interviewed."
@@ -378,6 +392,9 @@ async def subscribe_node(self, node_id: int) -> None:
378
392
379
393
Note that by using the listen command at server level, you will receive all node events.
380
394
"""
395
+ if self .chip_controller is None :
396
+ raise RuntimeError ("Device Controller not initialized." )
397
+
381
398
if node_id not in self ._nodes :
382
399
raise NodeNotExists (
383
400
f"Node { node_id } does not exist or has not been interviewed."
0 commit comments