From 2e191fa62d6ec1f026285b60db3498425fae3fd3 Mon Sep 17 00:00:00 2001 From: Jordan Yates Date: Fri, 16 Jan 2026 14:07:55 +1000 Subject: [PATCH 1/3] tools: localhost: preserve application filtering Preserve the current filtering of applications when a new application ID is observed, instead of resetting it back to all visible. Signed-off-by: Jordan Yates --- src/infuse_iot/tools/localhost.py | 6 +++++- src/infuse_iot/tools/localhost/index.html | 21 +++++++++++++-------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/infuse_iot/tools/localhost.py b/src/infuse_iot/tools/localhost.py index 7a7bb74..08c5035 100644 --- a/src/infuse_iot/tools/localhost.py +++ b/src/infuse_iot/tools/localhost.py @@ -44,6 +44,7 @@ def add_parser(cls, parser): def __init__(self, args): self._data_lock = threading.Lock() self._columns: dict[str, dict] = {} + self._apps: set[str] = set() self._data: dict[int, dict] = {} self._port: int = args.port @@ -130,6 +131,7 @@ async def websocket_handler(self, request: BaseRequest): "columns": columns, "rows": [self._data[d] for d in devices], "tdfs": sorted(list(self._columns.keys())), + "apps": sorted(list(self._apps)), } self._data_lock.release() @@ -219,7 +221,9 @@ def recv_thread(self) -> None: if t.NAME not in self._data[source.infuse_id]: self._data[source.infuse_id][t.NAME] = {} if t.NAME in ["ANNOUNCE", "ANNOUNCE_V2"]: - self._data[source.infuse_id]["application"] = f"0x{t.application:08x}" + app_str = f"0x{t.application:08x}" + self._data[source.infuse_id]["application"] = app_str + self._apps.add(app_str) for field in t.iter_fields(nested_iter=False): if isinstance(field.val, structs.tdf_struct_mcuboot_img_sem_ver): diff --git a/src/infuse_iot/tools/localhost/index.html b/src/infuse_iot/tools/localhost/index.html index b2822cb..90f5916 100644 --- a/src/infuse_iot/tools/localhost/index.html +++ b/src/infuse_iot/tools/localhost/index.html @@ -127,7 +127,7 @@

Infuse-IoT TDF Viewer

a.every((element, index) => element === b[index]); ws.onmessage = function (event) { - const { columns, rows, tdfs } = JSON.parse(event.data); + const { columns, rows, tdfs, apps } = JSON.parse(event.data); if (!arraysEqual(currentTDFs, tdfs)) { // Merge new data with the existing table data @@ -150,13 +150,18 @@

Infuse-IoT TDF Viewer

disableData.map(row => [row.name, row.checked]) ); - const uniqueApplications = [ - ...new Set(rows.map(obj => obj.application).filter(app => app !== undefined).sort()) - ]; - if (!arraysEqual(currentApps, uniqueApplications)) { - versions = uniqueApplications.map((v, idx) => { return { id: `row-${idx}`, name: v, checked: true }; }) - shownAppTable.replaceData(versions); - currentApps = uniqueApplications + if (!arraysEqual(currentApps, apps)) { + // Merge new data with the existing table data + const currentRows = shownAppTable.getData(); + const updatedData = apps.map((str, index) => { + const existingRow = currentRows.find(row => row.name === str); + console.log(str, existingRow); + return existingRow + ? existingRow // Preserve the existing row if found + : { id: `row-${index}`, name: str, checked: true }; + }); + shownAppTable.replaceData(updatedData); + currentApps = apps filtering_update = true; } From 0d0333de11b65f937b9763b353a2992afb135b4d Mon Sep 17 00:00:00 2001 From: Jordan Yates Date: Fri, 16 Jan 2026 14:09:52 +1000 Subject: [PATCH 2/3] socket_comms: add invalid json to exception Add the failing json structure to the exception message when class reconstruction fails. Signed-off-by: Jordan Yates --- src/infuse_iot/socket_comms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/infuse_iot/socket_comms.py b/src/infuse_iot/socket_comms.py index 99513a8..764c91f 100644 --- a/src/infuse_iot/socket_comms.py +++ b/src/infuse_iot/socket_comms.py @@ -47,7 +47,7 @@ def from_json(cls, values: dict) -> Self: return cast(Self, ClientNotificationConnectionDropped.from_json(values)) elif values["type"] == cls.Type.KNOWN_DEVICES: return cast(Self, ClientNotificationObservedDevices.from_json(values)) - raise NotImplementedError + raise NotImplementedError(f"Unknown notification: {values}") class ClientNotificationEpacketReceived(ClientNotification): @@ -147,7 +147,7 @@ def from_json(cls, values: dict) -> Self: return cast(Self, GatewayRequestConnectionRelease.from_json(values)) elif values["type"] == cls.Type.KNOWN_DEVICES: return cast(Self, GatewayRequestObservedDevices.from_json(values)) - raise NotImplementedError + raise NotImplementedError(f"Unknown request: {values}") class GatewayRequestEpacketSend(GatewayRequest): From 75ed12680b301dd6c8ed1543dc89b4cf5a6e3992 Mon Sep 17 00:00:00 2001 From: Jordan Yates Date: Fri, 16 Jan 2026 14:10:27 +1000 Subject: [PATCH 3/3] tools: cloud: kv_state: filter task schedules by default Filter the task schedules from the displayed KV state by default, as they take up a lot of space and are rarely the key of interest. Signed-off-by: Jordan Yates --- src/infuse_iot/tools/cloud.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/infuse_iot/tools/cloud.py b/src/infuse_iot/tools/cloud.py index ec70857..ba2c6f7 100644 --- a/src/infuse_iot/tools/cloud.py +++ b/src/infuse_iot/tools/cloud.py @@ -169,9 +169,10 @@ def add_parser(cls, parser): info_parser = tool_parser.add_parser("info", help="General device information") info_parser.set_defaults(command_fn=cls.info) info_parser.add_argument("--id", type=str, required=True, help="Infuse-IoT device ID") - info_parser = tool_parser.add_parser("kv_state", help="Key-Value device state") - info_parser.set_defaults(command_fn=cls.kv_state) - info_parser.add_argument("--id", type=str, required=True, help="Infuse-IoT device ID") + kv_parser = tool_parser.add_parser("kv_state", help="Key-Value device state") + kv_parser.set_defaults(command_fn=cls.kv_state) + kv_parser.add_argument("--id", type=str, required=True, help="Infuse-IoT device ID") + kv_parser.add_argument("--schedules", action="store_true", help="Display task schedules") def run(self): with self.client() as client: @@ -242,6 +243,10 @@ def kv_state(self, client: Client): for element in kv_state: key = element.key_name if isinstance(element.key_name, str) else str(element.key_id) + # Don't display task schedules unless requested + if key == "TASK_SCHEDULES" and not self.args.schedules: + continue + if isinstance(element.data, Unset): table.append((key, "Not set")) else: