Skip to content

Commit 00564c0

Browse files
committed
Improve OTA Provider handling
Create log files for each OTA Provider run. Improve setup and commissioning of the OTA Provider.
1 parent e961c4e commit 00564c0

File tree

3 files changed

+105
-80
lines changed

3 files changed

+105
-80
lines changed

matter_server/server/device_controller.py

+84-70
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,83 @@ async def check_node_update(self, node_id: int) -> dict | None:
891891

892892
return await self._check_node_update(node_id)
893893

894+
async def _initialize_ota_provider(self, ota_provider: ExternalOtaProvider) -> None:
895+
"""Commissions the OTA Provider."""
896+
897+
if self.chip_controller is None:
898+
raise RuntimeError("Device Controller not initialized.")
899+
900+
# The OTA Provider has not been commissioned yet, let's do it now.
901+
LOGGER.info("Commissioning the built-in OTA Provider App.")
902+
try:
903+
ota_provider_node = await self.commission_on_network(
904+
ota_provider.get_passcode(),
905+
# TODO: Filtering by long discriminator seems broken
906+
# filter_type=FilterType.LONG_DISCRIMINATOR,
907+
# filter=ota_provider.get_descriminator(),
908+
)
909+
ota_provider_node_id = ota_provider_node.node_id
910+
except NodeCommissionFailed:
911+
LOGGER.error("Failed to commission OTA Provider App!")
912+
return
913+
914+
LOGGER.info(
915+
"OTA Provider App commissioned with node id %d.",
916+
ota_provider_node_id,
917+
)
918+
919+
# Adjust ACL of OTA Requestor such that Node peer-to-peer communication
920+
# is allowed.
921+
try:
922+
read_result = await self.chip_controller.ReadAttribute(
923+
ota_provider_node_id, [(0, Clusters.AccessControl.Attributes.Acl)]
924+
)
925+
acl_list = cast(
926+
list,
927+
read_result[0][Clusters.AccessControl][
928+
Clusters.AccessControl.Attributes.Acl
929+
],
930+
)
931+
932+
# Add new ACL entry...
933+
acl_list.append(
934+
Clusters.AccessControl.Structs.AccessControlEntryStruct(
935+
fabricIndex=1,
936+
privilege=Clusters.AccessControl.Enums.AccessControlEntryPrivilegeEnum.kOperate,
937+
authMode=Clusters.AccessControl.Enums.AccessControlEntryAuthModeEnum.kCase,
938+
subjects=Types.NullValue,
939+
targets=[
940+
Clusters.AccessControl.Structs.AccessControlTargetStruct(
941+
cluster=Clusters.OtaSoftwareUpdateProvider.id,
942+
endpoint=0,
943+
deviceType=Types.NullValue,
944+
)
945+
],
946+
)
947+
)
948+
949+
# And write. This is persistent, so only need to be done after we commissioned
950+
# the OTA Provider App.
951+
write_result: Attribute.AttributeWriteResult = (
952+
await self.chip_controller.WriteAttribute(
953+
ota_provider_node_id,
954+
[(0, Clusters.AccessControl.Attributes.Acl(acl_list))],
955+
)
956+
)
957+
if write_result[0].Status != Status.Success:
958+
logging.error(
959+
"Failed writing adjusted OTA Provider App ACL: Status %s.",
960+
str(write_result[0].Status),
961+
)
962+
await self.remove_node(ota_provider_node_id)
963+
raise UpdateError("Error while setting up OTA Provider.")
964+
except ChipStackError as ex:
965+
logging.exception("Failed adjusting OTA Provider App ACL.", exc_info=ex)
966+
await self.remove_node(ota_provider_node_id)
967+
raise UpdateError("Error while setting up OTA Provider.") from ex
968+
969+
ota_provider.set_node_id(ota_provider_node_id)
970+
894971
@api_command(APICommand.UPDATE_NODE)
895972
async def update_node(self, node_id: int, software_version: int) -> dict | None:
896973
"""
@@ -918,7 +995,9 @@ async def update_node(self, node_id: int, software_version: int) -> dict | None:
918995
await self._ota_provider.download_update(update)
919996

920997
ota_provider_node_id = self._ota_provider.get_node_id()
921-
if ota_provider_node_id not in self._nodes:
998+
if ota_provider_node_id is None:
999+
LOGGER.info("Initializing OTA Provider")
1000+
elif ota_provider_node_id not in self._nodes:
9221001
LOGGER.warning(
9231002
"OTA Provider node id %d no longer exists! Resetting...",
9241003
ota_provider_node_id,
@@ -928,82 +1007,17 @@ async def update_node(self, node_id: int, software_version: int) -> dict | None:
9281007

9291008
# Make sure any previous instances get stopped
9301009
await self._ota_provider.stop()
931-
self._ota_provider.start()
1010+
await self._ota_provider.start()
9321011

9331012
# Wait for OTA provider to be ready
9341013
# TODO: Detect when OTA provider is ready
9351014
await asyncio.sleep(2)
9361015

9371016
if not ota_provider_node_id:
938-
# The OTA Provider has not been commissioned yet, let's do it now.
939-
LOGGER.info("Commissioning the built-in OTA Provider App.")
940-
try:
941-
ota_provider_node = await self.commission_on_network(
942-
self._ota_provider.get_passcode(),
943-
# TODO: Filtering by long discriminator seems broken
944-
# filter_type=FilterType.LONG_DISCRIMINATOR,
945-
# filter=self._ota_provider.get_descriminator(),
946-
)
947-
ota_provider_node_id = ota_provider_node.node_id
948-
except NodeCommissionFailed:
949-
LOGGER.error("Failed to commission OTA Provider App!")
950-
return None
951-
LOGGER.info(
952-
"OTA Provider App commissioned with node id %d.",
953-
ota_provider_node_id,
954-
)
955-
956-
# Adjust ACL of OTA Requestor such that Node peer-to-peer communication
957-
# is allowed.
958-
try:
959-
read_result = await self.chip_controller.ReadAttribute(
960-
ota_provider_node_id, [(0, Clusters.AccessControl.Attributes.Acl)]
961-
)
962-
acl_list = cast(
963-
list,
964-
read_result[0][Clusters.AccessControl][
965-
Clusters.AccessControl.Attributes.Acl
966-
],
967-
)
968-
969-
# Add new ACL entry...
970-
acl_list.append(
971-
Clusters.AccessControl.Structs.AccessControlEntryStruct(
972-
fabricIndex=1,
973-
privilege=3,
974-
authMode=2,
975-
subjects=Types.NullValue,
976-
targets=[
977-
Clusters.AccessControl.Structs.AccessControlTargetStruct(
978-
cluster=41, endpoint=0, deviceType=Types.NullValue
979-
)
980-
],
981-
)
982-
)
983-
984-
# And write. This is persistent, so only need to be done after we commissioned
985-
# the OTA Provider App.
986-
write_result: Attribute.AttributeWriteResult = (
987-
await self.chip_controller.WriteAttribute(
988-
ota_provider_node_id,
989-
[(0, Clusters.AccessControl.Attributes.Acl(acl_list))],
990-
)
991-
)
992-
if write_result[0].Status != Status.Success:
993-
logging.error(
994-
"Failed writing adjusted OTA Provider App ACL: Status %s.",
995-
str(write_result[0].Status),
996-
)
997-
await self.remove_node(ota_provider_node_id)
998-
raise UpdateError("Error while setting up OTA Provider.")
999-
except ChipStackError as ex:
1000-
logging.exception("Failed adjusting OTA Provider App ACL.", exc_info=ex)
1001-
await self.remove_node(ota_provider_node_id)
1002-
raise UpdateError("Error while setting up OTA Provider.") from ex
1003-
1004-
self._ota_provider.set_node_id(ota_provider_node_id)
1017+
await self._initialize_ota_provider(self._ota_provider)
10051018

1006-
# Notify node about the new update!
1019+
# Notify update node about the availability of the OTA Provider. It will query
1020+
# the OTA provider and start the update.
10071021
try:
10081022
await self.chip_controller.SendCommand(
10091023
nodeid=node_id,

matter_server/server/ota/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
"minApplicableSoftwareVersion": 1,
1717
"maxApplicableSoftwareVersion": 1,
1818
"otaUrl": "https://github.com/agners/matter-linux-example-apps/releases/download/v1.3.0.0/chip-ota-requestor-app-x86-64.ota",
19-
}
19+
"releaseNotesUrl": "https://github.com/agners/matter-linux-example-apps/releases/tag/v1.3.0.0",
20+
},
2021
}
2122

2223

matter_server/server/ota/provider.py

+19-9
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import asyncio
44
from base64 import b64encode
55
from dataclasses import asdict, dataclass
6+
from datetime import UTC, datetime
67
import functools
78
import hashlib
89
import json
@@ -13,6 +14,7 @@
1314
from urllib.parse import unquote, urlparse
1415

1516
from aiohttp import ClientError, ClientSession
17+
from aiohttp.client_exceptions import InvalidURL
1618

1719
from matter_server.common.errors import UpdateError
1820
from matter_server.common.helpers.util import dataclass_from_dict
@@ -143,7 +145,9 @@ def set_node_id(self, node_id: int) -> None:
143145

144146
self._get_ota_provider_image_list().otaProviderNodeId = node_id
145147

146-
async def _start_ota_provider(self) -> None:
148+
async def start(self) -> None:
149+
"""Start the OTA Provider."""
150+
147151
def _write_ota_provider_image_list_json(
148152
ota_provider_image_list_file: Path,
149153
ota_provider_image_list: OtaProviderImageList,
@@ -174,16 +178,19 @@ def _write_ota_provider_image_list_json(
174178
str(self._ota_provider_image_list_file),
175179
]
176180

181+
timestamp = datetime.now(tz=UTC).strftime("%Y%m%d_%H%M%S")
182+
log_file_path = self._ota_provider_dir / f"ota_provider_{timestamp}.log"
183+
184+
log_file = await loop.run_in_executor(None, log_file_path.open, "w")
185+
177186
LOGGER.info("Starting OTA Provider")
178187
self._ota_provider_proc = await asyncio.create_subprocess_exec(
179-
*ota_provider_cmd
188+
*ota_provider_cmd, stdout=log_file, stderr=log_file
180189
)
181190

182-
def start(self) -> None:
183-
"""Start the OTA Provider."""
184-
185-
loop = asyncio.get_event_loop()
186-
self._ota_provider_task = loop.create_task(self._start_ota_provider())
191+
self._ota_provider_task = loop.create_task(
192+
self._ota_provider_proc.communicate()
193+
)
187194

188195
async def reset(self) -> None:
189196
"""Reset the OTA Provider App state."""
@@ -293,12 +300,15 @@ async def download_update(self, update_desc: dict) -> None:
293300
raise UpdateError("Checksum mismatch!")
294301

295302
LOGGER.info(
296-
"File '%s' downloaded to '%s'", file_name, DEFAULT_UPDATES_PATH
303+
"Update file '%s' downloaded to '%s'",
304+
file_name,
305+
DEFAULT_UPDATES_PATH,
297306
)
298307

299-
except (ClientError, TimeoutError) as err:
308+
except (InvalidURL, ClientError, TimeoutError) as err:
300309
LOGGER.error(
301310
"Fetching software version failed: error %s", err, exc_info=err
302311
)
312+
raise UpdateError("Fetching software version failed") from err
303313

304314
await self.add_update(update_desc, file_path)

0 commit comments

Comments
 (0)