From 434e1edcc0357330c0bc6406c1dc4b022e6e16dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Fri, 1 Dec 2023 14:58:47 +0100 Subject: [PATCH] more ota status stuff in python and more way to communicate status in c --- src/c3nav/mesh/consumers.py | 27 +++++++++++++++++-- src/c3nav/mesh/messages.py | 22 ++++++++++++++- src/c3nav/mesh/models.py | 19 ++++++++++++- .../mesh/fragment_mesh_websocket.html | 18 ++++++++++--- src/c3nav/mesh/templates/mesh/ota_detail.html | 10 ++++--- src/c3nav/mesh/utils.py | 1 + 6 files changed, 87 insertions(+), 10 deletions(-) diff --git a/src/c3nav/mesh/consumers.py b/src/c3nav/mesh/consumers.py index 82bac82f..202e2067 100644 --- a/src/c3nav/mesh/consumers.py +++ b/src/c3nav/mesh/consumers.py @@ -18,7 +18,7 @@ from c3nav.mesh import messages from c3nav.mesh.messages import (MESH_BROADCAST_ADDRESS, MESH_NONE_ADDRESS, MESH_ROOT_ADDRESS, OTA_CHUNK_SIZE, MeshMessage, MeshMessageType) from c3nav.mesh.models import MeshNode, MeshUplink, NodeMessage, OTAUpdate, OTAUpdateRecipient, OTARecipientStatus -from c3nav.mesh.utils import MESH_ALL_UPLINKS_GROUP, UPLINK_PING, get_mesh_uplink_group +from c3nav.mesh.utils import MESH_ALL_UPLINKS_GROUP, UPLINK_PING, get_mesh_uplink_group, MESH_ALL_OTA_GROUP from c3nav.routing.rangelocator import RangeLocator @@ -597,9 +597,16 @@ class MeshUIConsumer(AsyncJsonWebsocketConsumer): if content.get("subscribe", None) == "ranging": await self.channel_layer.group_add("mesh_msg_received", self.channel_name) self.msg_received_filter = {"msg_type": MeshMessageType.LOCATE_RANGE_RESULTS.name} + await self.dump_newest_messages(MeshMessageType.LOCATE_RANGE_RESULTS) if content.get("subscribe", None) == "ota": await self.channel_layer.group_add("mesh_msg_received", self.channel_name) - self.msg_received_filter = {"msg_type": MeshMessageType.OTA_STATUS.name} + update_id = content.get("update_id", 0) + self.msg_received_filter = {"msg_type": MeshMessageType.OTA_STATUS.name, + "update_id": update_id} + await self.channel_layer.group_add(MESH_ALL_OTA_GROUP, self.channel_name) + await self.dump_newest_messages(MeshMessageType.OTA_STATUS) + for recipient in await self._qs_as_list(OTAUpdateRecipient.objects.filter(update_id=update_id)): + await self.send_json(recipient.get_status_json()) if "send_msg" in content: msg_to_send = self.scope["session"].pop("mesh_msg_%s" % content["send_msg"], None) if not msg_to_send: @@ -619,6 +626,22 @@ class MeshUIConsumer(AsyncJsonWebsocketConsumer): **msg_to_send["msg_data"], }).send(sender=self.channel_name) + async def dump_newest_messages(self, msg_type: MeshMessageType, include_ota=False): + for msg in await self._get_last_messages(msg_type): + await self.mesh_msg_received({ + "type": "mesh.msg_received", + "msg": msg.data + }) + + @database_sync_to_async + def _get_last_messages(self, msg_type): + results = (node.last_messages[msg_type] for node in MeshNode.objects.prefetch_last_messages(msg_type)) + return [msg for msg in results if msg] + + @database_sync_to_async + def _qs_as_list(self, qs): + return list(qs) + async def mesh_log_entry(self, data): await self.send_json(data) diff --git a/src/c3nav/mesh/messages.py b/src/c3nav/mesh/messages.py index 6c6b6fce..14cdf643 100644 --- a/src/c3nav/mesh/messages.py +++ b/src/c3nav/mesh/messages.py @@ -53,7 +53,7 @@ class MeshMessageType(EnumSchemaByNameMixin, IntEnum): OTA_REQUEST_FRAGMENTS = 0x25 OTA_SETTING = 0x26 OTA_APPLY = 0x27 - OTA_ABORT = 0x29 + OTA_ABORT = 0x28 LOCATE_REQUEST_RANGE = 0x30 LOCATE_RANGE_RESULTS = 0x31 @@ -269,6 +269,25 @@ class ConfigUplinkMessage(MeshMessage, msg_type=MeshMessageType.CONFIG_UPLINK): port: int = field(metadata={"format": SimpleFormat('H')}) + +@unique +class OTADeviceStatus(EnumSchemaByNameMixin, IntEnum): + """ ota status, the ones >= 0x10 denote a permanent failure """ + NONE = 0x00 + + STARTED = 0x01 + APPLIED = 0x02 + + START_FAILED = 0x10 + WRITE_FAILED = 0x12 + APPLY_FAILED = 0x13 + ROLLED_BACK = 0x14 + + @property + def pretty_name(self): + return self.name.replace('_', ' ').lower() + + @dataclass class OTAStatusMessage(MeshMessage, msg_type=MeshMessageType.OTA_STATUS): """ report OTA status """ @@ -277,6 +296,7 @@ class OTAStatusMessage(MeshMessage, msg_type=MeshMessageType.OTA_STATUS): highest_chunk: int = field(metadata={"format": SimpleFormat('H')}) auto_apply: bool = field(metadata={"format": BoolFormat()}) auto_reboot: bool = field(metadata={"format": BoolFormat()}) + status: OTADeviceStatus = field(metadata={"format": EnumFormat('B')}) @dataclass diff --git a/src/c3nav/mesh/models.py b/src/c3nav/mesh/models.py index 169f609c..8f847dc8 100644 --- a/src/c3nav/mesh/models.py +++ b/src/c3nav/mesh/models.py @@ -7,6 +7,8 @@ from functools import cached_property from operator import attrgetter from typing import Any, Mapping, Optional, Self +import channels +from asgiref.sync import async_to_sync from django.contrib.auth import get_user_model from django.core.validators import RegexValidator from django.db import NotSupportedError, models @@ -21,7 +23,7 @@ from c3nav.mesh.dataformats import BoardType, ChipType, FirmwareImage from c3nav.mesh.messages import ConfigFirmwareMessage, ConfigHardwareMessage from c3nav.mesh.messages import MeshMessage as MeshMessage from c3nav.mesh.messages import MeshMessageType -from c3nav.mesh.utils import UPLINK_TIMEOUT +from c3nav.mesh.utils import UPLINK_TIMEOUT, MESH_ALL_OTA_GROUP from c3nav.routing.rangelocator import RangeLocator FirmwareLookup = namedtuple('FirmwareLookup', ('sha256_hash', 'chip', 'project_name', 'version', 'idf_version')) @@ -512,3 +514,18 @@ class OTAUpdateRecipient(models.Model): UniqueConstraint(fields=["node"], condition=Q(status=OTARecipientStatus.RUNNING), name='only_one_active_ota'), ) + + async def send_status(self): + """ + use this for OTA stuffs + """ + channels.layers.get_channel_layer().group_send(MESH_ALL_OTA_GROUP, self.get_status_json()) + + def get_status_json(self): + return { + "type": "mesh.ota_recipient_status", + "node": self.node_id, + "update": self.update_id, + "status": self.status, + } + diff --git a/src/c3nav/mesh/templates/mesh/fragment_mesh_websocket.html b/src/c3nav/mesh/templates/mesh/fragment_mesh_websocket.html index 0dc11ce5..34ca5ff3 100644 --- a/src/c3nav/mesh/templates/mesh/fragment_mesh_websocket.html +++ b/src/c3nav/mesh/templates/mesh/fragment_mesh_websocket.html @@ -13,7 +13,10 @@ {% endif %} {% if nodes_xyz %} const nodes_xyz = JSON.parse(document.getElementById('nodes-xyz').textContent); - const nodes_distances = {} + const nodes_distances = {}; +{% endif %} +{% if update %} + const update_id = {{ update.pk }}; {% endif %} function connect() { @@ -26,7 +29,7 @@ function connect() { {% elif ranging_form %} ws.send(JSON.stringify({"subscribe": "ranging"})); {% elif update %} - ws.send(JSON.stringify({"subscribe": "ota-{{ update.pk }}"})); + ws.send(JSON.stringify({"subscribe": "ota", "update_id": update_id})); {% else %} ws.send(JSON.stringify({"subscribe": "log"})); {% endif %} @@ -100,6 +103,13 @@ function connect() { {% endif %} break; + case 'mesh.ota_recipient_status': + {% if update %} + var row = document.querySelector(`[id="ota-recipient-${data.update}-${data.node}"]`); + if (!row) break; + row.querySelector('.status').innerText = data.status; + {% endif %} + case 'mesh.msg_received': {% if ranging_form %} var cell, key, src_node, peer_node; @@ -158,10 +168,12 @@ function connect() { } {% endif %} {% if update %} - var row = document.querySelector(`#ota-recipients-${data.msg.update_id} [id="recipient-${data.msg.src}"]`); + var row = document.querySelector(`[id="ota-recipient-${data.msg.update_id}-${data.msg.src}"]`); if (!row) break; row.querySelector('.received_bytes').innerText = data.msg.received_bytes; row.querySelector('progress').value = data.msg.received_bytes; + row.querySelector('.auto-apply span').innerText = data.msg.auto_apply ? 'yes' : 'no'; + row.querySelector('.auto-reboot span').innerText = data.msg.auto_reboot ? 'yes' : 'no'; {% endif %} {% if send_uuid and msg_type == "MESH_ROUTE_REQUEST" %} if (data.msg.route) { diff --git a/src/c3nav/mesh/templates/mesh/ota_detail.html b/src/c3nav/mesh/templates/mesh/ota_detail.html index 2d9a92fb..0538205c 100644 --- a/src/c3nav/mesh/templates/mesh/ota_detail.html +++ b/src/c3nav/mesh/templates/mesh/ota_detail.html @@ -18,16 +18,20 @@ Created: {{ update.created }}
- +
{% for recipient in update.recipients.all %} - + - +
{% trans 'Node' %} {% trans 'Status' %} {% trans 'Progress' %}
{% mesh_node recipient.node %}{{ recipient.get_status_display }} +
+ auto apply:
+ auto reboot:
+
?? of {{ update.build.binary.size }} bytes
diff --git a/src/c3nav/mesh/utils.py b/src/c3nav/mesh/utils.py index bd232905..8a21fdbe 100644 --- a/src/c3nav/mesh/utils.py +++ b/src/c3nav/mesh/utils.py @@ -6,6 +6,7 @@ def get_mesh_uplink_group(address): MESH_ALL_UPLINKS_GROUP = "mesh_uplink_all" +MESH_ALL_OTA_GROUP = "mesh_ota_all" UPLINK_PING = 5 UPLINK_TIMEOUT = UPLINK_PING+5