18
18
from aiohttp import ClientError , ClientSession
19
19
from aiohttp .client_exceptions import InvalidURL
20
20
from chip .clusters import Attribute , Objects as Clusters , Types
21
+ from chip .discovery import FilterType
21
22
from chip .exceptions import ChipStackError
22
23
from chip .interaction_model import Status
23
24
24
- from matter_server .common .errors import NodeCommissionFailed , NodeNotExists , UpdateError
25
+ from matter_server .common .errors import UpdateError
25
26
from matter_server .common .helpers .util import dataclass_from_dict
26
27
27
- if TYPE_CHECKING :
28
- from matter_server .server .device_controller import MatterDeviceController
29
-
30
28
if TYPE_CHECKING :
31
29
from asyncio .subprocess import Process
32
30
31
+ from chip .native import PyChipError
32
+
33
+ from matter_server .server .sdk import ChipDeviceControllerWrapper
34
+
33
35
LOGGER = logging .getLogger (__name__ )
34
36
35
37
DEFAULT_UPDATES_PATH : Final [Path ] = Path ("updates" )
36
38
39
+ DEFAULT_OTA_PROVIDER_NODE_ID = 999900
40
+
37
41
# From Matter SDK src/app/ota_image_tool.py
38
42
CHECHKSUM_TYPES : Final [dict [int , str ]] = {
39
43
1 : "sha256" ,
@@ -85,8 +89,9 @@ class ExternalOtaProvider:
85
89
86
90
ENDPOINT_ID : Final [int ] = 0
87
91
88
- def __init__ (self , ota_provider_dir : Path ) -> None :
92
+ def __init__ (self , vendor_id : int , ota_provider_dir : Path ) -> None :
89
93
"""Initialize the OTA provider."""
94
+ self ._vendor_id : int = vendor_id
90
95
self ._ota_provider_dir : Path = ota_provider_dir
91
96
self ._ota_provider_image_list_file : Path = ota_provider_dir / "updates.json"
92
97
self ._ota_provider_image_list : OtaProviderImageList | None = None
@@ -158,40 +163,45 @@ def get_node_id(self) -> int | None:
158
163
159
164
return self ._get_ota_provider_image_list ().otaProviderNodeId
160
165
161
- async def _initialize (self , device_controller : MatterDeviceController ) -> None :
166
+ async def _initialize (
167
+ self , chip_device_controller : ChipDeviceControllerWrapper
168
+ ) -> None :
162
169
"""Commissions the OTA Provider."""
163
170
164
- if device_controller .chip_controller is None :
165
- raise RuntimeError ("Device Controller not initialized." )
166
-
167
171
# The OTA Provider has not been commissioned yet, let's do it now.
168
172
LOGGER .info ("Commissioning the built-in OTA Provider App." )
169
- try :
170
- ota_provider_node = await device_controller .commission_on_network (
171
- self .get_passcode (),
172
- # TODO: Filtering by long discriminator seems broken
173
- # filter_type=FilterType.LONG_DISCRIMINATOR,
174
- # filter=ota_provider.get_descriminator(),
173
+
174
+ res : PyChipError = await chip_device_controller .commission_on_network (
175
+ DEFAULT_OTA_PROVIDER_NODE_ID ,
176
+ self .get_passcode (),
177
+ # TODO: Filtering by long discriminator seems broken
178
+ disc_filter_type = FilterType .LONG_DISCRIMINATOR ,
179
+ disc_filter = self .get_descriminator (),
180
+ )
181
+ if not res .is_success :
182
+ await self .stop ()
183
+ raise UpdateError (
184
+ f"Failed to commission OTA Provider App: SDK Error { res .code } "
175
185
)
176
- ota_provider_node_id = ota_provider_node .node_id
177
- except NodeCommissionFailed :
178
- LOGGER .error ("Failed to commission OTA Provider App!" )
179
- return
180
186
181
187
LOGGER .info (
182
188
"OTA Provider App commissioned with node id %d." ,
183
- ota_provider_node_id ,
189
+ DEFAULT_OTA_PROVIDER_NODE_ID ,
184
190
)
185
191
186
192
# Adjust ACL of OTA Requestor such that Node peer-to-peer communication
187
193
# is allowed.
188
194
try :
189
- read_result = await device_controller .chip_controller .ReadAttribute (
190
- ota_provider_node_id , [(0 , Clusters .AccessControl .Attributes .Acl )]
195
+ read_result = cast (
196
+ Attribute .AsyncReadTransaction .ReadResponse ,
197
+ await chip_device_controller .read_attribute (
198
+ DEFAULT_OTA_PROVIDER_NODE_ID ,
199
+ [(0 , Clusters .AccessControl .Attributes .Acl )],
200
+ ),
191
201
)
192
202
acl_list = cast (
193
203
list ,
194
- read_result [0 ][Clusters .AccessControl ][
204
+ read_result . attributes [0 ][Clusters .AccessControl ][
195
205
Clusters .AccessControl .Attributes .Acl
196
206
],
197
207
)
@@ -216,8 +226,8 @@ async def _initialize(self, device_controller: MatterDeviceController) -> None:
216
226
# And write. This is persistent, so only need to be done after we commissioned
217
227
# the OTA Provider App.
218
228
write_result : Attribute .AttributeWriteResult = (
219
- await device_controller . chip_controller . WriteAttribute (
220
- ota_provider_node_id ,
229
+ await chip_device_controller . write_attribute (
230
+ DEFAULT_OTA_PROVIDER_NODE_ID ,
221
231
[(0 , Clusters .AccessControl .Attributes .Acl (acl_list ))],
222
232
)
223
233
)
@@ -226,22 +236,17 @@ async def _initialize(self, device_controller: MatterDeviceController) -> None:
226
236
"Failed writing adjusted OTA Provider App ACL: Status %s." ,
227
237
str (write_result [0 ].Status ),
228
238
)
229
- await device_controller . remove_node ( ota_provider_node_id )
239
+ await self . stop ( )
230
240
raise UpdateError ("Error while setting up OTA Provider." )
231
241
except ChipStackError as ex :
232
242
logging .exception ("Failed adjusting OTA Provider App ACL." , exc_info = ex )
233
- await device_controller . remove_node ( ota_provider_node_id )
243
+ await self . stop ( )
234
244
raise UpdateError ("Error while setting up OTA Provider." ) from ex
235
245
236
- self .set_node_id (ota_provider_node_id )
237
-
238
246
async def start_update (
239
- self , device_controller : MatterDeviceController , node_id : int
247
+ self , chip_device_controller : ChipDeviceControllerWrapper , node_id : int
240
248
) -> None :
241
- """Start the OTA Provider."""
242
-
243
- if device_controller .chip_controller is None :
244
- raise RuntimeError ("Device Controller not initialized." )
249
+ """Start the OTA Provider and trigger the update."""
245
250
246
251
self ._ota_target_node_id = node_id
247
252
@@ -291,39 +296,29 @@ def _write_ota_provider_image_list_json(
291
296
292
297
# Wait for OTA provider to be ready
293
298
# TODO: Detect when OTA provider is ready
294
- await asyncio .sleep (2 )
299
+ await asyncio .sleep (3 )
295
300
296
301
# Handle if user deleted the OTA Provider node.
297
302
ota_provider_node_id = self .get_node_id ()
298
- if ota_provider_node_id is not None :
299
- try :
300
- device_controller .get_node (ota_provider_node_id )
301
- except NodeNotExists :
302
- LOGGER .warning (
303
- "OTA Provider node id %d not known by device controller! Resetting..." ,
304
- ota_provider_node_id ,
305
- )
306
- await self ._reset ()
307
- ota_provider_node_id = None
308
303
309
304
# Commission and prepare OTA Provider if not initialized yet.
310
305
# Use "ota_provider_node_id" to indicate if OTA Provider is setup or not.
311
306
try :
312
307
if ota_provider_node_id is None :
313
308
LOGGER .info ("Initializing OTA Provider" )
314
- await self ._initialize (device_controller )
309
+ await self ._initialize (chip_device_controller )
315
310
finally :
316
311
self ._ota_target_node_id = None
317
312
318
313
# Notify update node about the availability of the OTA Provider. It will query
319
314
# the OTA provider and start the update.
320
315
try :
321
- await device_controller . chip_controller . SendCommand (
322
- nodeid = node_id ,
323
- endpoint = 0 ,
324
- payload = Clusters .OtaSoftwareUpdateRequestor .Commands .AnnounceOTAProvider (
325
- providerNodeID = ota_provider_node_id ,
326
- vendorID = device_controller . server . vendor_id ,
316
+ await chip_device_controller . send_command (
317
+ node_id ,
318
+ endpoint_id = 0 ,
319
+ command = Clusters .OtaSoftwareUpdateRequestor .Commands .AnnounceOTAProvider (
320
+ providerNodeID = DEFAULT_OTA_PROVIDER_NODE_ID ,
321
+ vendorID = self . _vendor_id ,
327
322
announcementReason = Clusters .OtaSoftwareUpdateRequestor .Enums .AnnouncementReasonEnum .kUpdateAvailable ,
328
323
endpoint = ExternalOtaProvider .ENDPOINT_ID ,
329
324
),
0 commit comments