more ota status stuff in python and more way to communicate status in c

This commit is contained in:
Laura Klünder 2023-12-01 14:58:47 +01:00
parent 6eaf087f71
commit 434e1edcc0
6 changed files with 87 additions and 10 deletions

View file

@ -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, from c3nav.mesh.messages import (MESH_BROADCAST_ADDRESS, MESH_NONE_ADDRESS, MESH_ROOT_ADDRESS, OTA_CHUNK_SIZE,
MeshMessage, MeshMessageType) MeshMessage, MeshMessageType)
from c3nav.mesh.models import MeshNode, MeshUplink, NodeMessage, OTAUpdate, OTAUpdateRecipient, OTARecipientStatus 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 from c3nav.routing.rangelocator import RangeLocator
@ -597,9 +597,16 @@ class MeshUIConsumer(AsyncJsonWebsocketConsumer):
if content.get("subscribe", None) == "ranging": if content.get("subscribe", None) == "ranging":
await self.channel_layer.group_add("mesh_msg_received", self.channel_name) await self.channel_layer.group_add("mesh_msg_received", self.channel_name)
self.msg_received_filter = {"msg_type": MeshMessageType.LOCATE_RANGE_RESULTS.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": if content.get("subscribe", None) == "ota":
await self.channel_layer.group_add("mesh_msg_received", self.channel_name) 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: if "send_msg" in content:
msg_to_send = self.scope["session"].pop("mesh_msg_%s" % content["send_msg"], None) msg_to_send = self.scope["session"].pop("mesh_msg_%s" % content["send_msg"], None)
if not msg_to_send: if not msg_to_send:
@ -619,6 +626,22 @@ class MeshUIConsumer(AsyncJsonWebsocketConsumer):
**msg_to_send["msg_data"], **msg_to_send["msg_data"],
}).send(sender=self.channel_name) }).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): async def mesh_log_entry(self, data):
await self.send_json(data) await self.send_json(data)

View file

@ -53,7 +53,7 @@ class MeshMessageType(EnumSchemaByNameMixin, IntEnum):
OTA_REQUEST_FRAGMENTS = 0x25 OTA_REQUEST_FRAGMENTS = 0x25
OTA_SETTING = 0x26 OTA_SETTING = 0x26
OTA_APPLY = 0x27 OTA_APPLY = 0x27
OTA_ABORT = 0x29 OTA_ABORT = 0x28
LOCATE_REQUEST_RANGE = 0x30 LOCATE_REQUEST_RANGE = 0x30
LOCATE_RANGE_RESULTS = 0x31 LOCATE_RANGE_RESULTS = 0x31
@ -269,6 +269,25 @@ class ConfigUplinkMessage(MeshMessage, msg_type=MeshMessageType.CONFIG_UPLINK):
port: int = field(metadata={"format": SimpleFormat('H')}) 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 @dataclass
class OTAStatusMessage(MeshMessage, msg_type=MeshMessageType.OTA_STATUS): class OTAStatusMessage(MeshMessage, msg_type=MeshMessageType.OTA_STATUS):
""" report 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')}) highest_chunk: int = field(metadata={"format": SimpleFormat('H')})
auto_apply: bool = field(metadata={"format": BoolFormat()}) auto_apply: bool = field(metadata={"format": BoolFormat()})
auto_reboot: bool = field(metadata={"format": BoolFormat()}) auto_reboot: bool = field(metadata={"format": BoolFormat()})
status: OTADeviceStatus = field(metadata={"format": EnumFormat('B')})
@dataclass @dataclass

View file

@ -7,6 +7,8 @@ from functools import cached_property
from operator import attrgetter from operator import attrgetter
from typing import Any, Mapping, Optional, Self 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.contrib.auth import get_user_model
from django.core.validators import RegexValidator from django.core.validators import RegexValidator
from django.db import NotSupportedError, models 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 ConfigFirmwareMessage, ConfigHardwareMessage
from c3nav.mesh.messages import MeshMessage as MeshMessage from c3nav.mesh.messages import MeshMessage as MeshMessage
from c3nav.mesh.messages import MeshMessageType 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 from c3nav.routing.rangelocator import RangeLocator
FirmwareLookup = namedtuple('FirmwareLookup', ('sha256_hash', 'chip', 'project_name', 'version', 'idf_version')) 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), UniqueConstraint(fields=["node"], condition=Q(status=OTARecipientStatus.RUNNING),
name='only_one_active_ota'), 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,
}

View file

@ -13,7 +13,10 @@
{% endif %} {% endif %}
{% if nodes_xyz %} {% if nodes_xyz %}
const nodes_xyz = JSON.parse(document.getElementById('nodes-xyz').textContent); 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 %} {% endif %}
function connect() { function connect() {
@ -26,7 +29,7 @@ function connect() {
{% elif ranging_form %} {% elif ranging_form %}
ws.send(JSON.stringify({"subscribe": "ranging"})); ws.send(JSON.stringify({"subscribe": "ranging"}));
{% elif update %} {% elif update %}
ws.send(JSON.stringify({"subscribe": "ota-{{ update.pk }}"})); ws.send(JSON.stringify({"subscribe": "ota", "update_id": update_id}));
{% else %} {% else %}
ws.send(JSON.stringify({"subscribe": "log"})); ws.send(JSON.stringify({"subscribe": "log"}));
{% endif %} {% endif %}
@ -100,6 +103,13 @@ function connect() {
{% endif %} {% endif %}
break; 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': case 'mesh.msg_received':
{% if ranging_form %} {% if ranging_form %}
var cell, key, src_node, peer_node; var cell, key, src_node, peer_node;
@ -158,10 +168,12 @@ function connect() {
} }
{% endif %} {% endif %}
{% if update %} {% 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; if (!row) break;
row.querySelector('.received_bytes').innerText = data.msg.received_bytes; row.querySelector('.received_bytes').innerText = data.msg.received_bytes;
row.querySelector('progress').value = 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 %} {% endif %}
{% if send_uuid and msg_type == "MESH_ROUTE_REQUEST" %} {% if send_uuid and msg_type == "MESH_ROUTE_REQUEST" %}
if (data.msg.route) { if (data.msg.route) {

View file

@ -18,16 +18,20 @@
<strong>Created:</strong> {{ update.created }} <strong>Created:</strong> {{ update.created }}
</div> </div>
<div> <div>
<table id="ota-recipients-{{ update.pk }}"> <table>
<tr> <tr>
<th>{% trans 'Node' %}</th> <th>{% trans 'Node' %}</th>
<th>{% trans 'Status' %}</th> <th>{% trans 'Status' %}</th>
<th>{% trans 'Progress' %}</th> <th>{% trans 'Progress' %}</th>
</tr> </tr>
{% for recipient in update.recipients.all %} {% for recipient in update.recipients.all %}
<tr id="recipient-{{ recipient.node_id }}"> <tr id="ota-recipient-{{ update.pk }}-{{ recipient.node_id }}">
<td>{% mesh_node recipient.node %}</td> <td>{% mesh_node recipient.node %}</td>
<td>{{ recipient.get_status_display }}</td> <td>
<span class="status"></span><br>
<span class="auto-apply"><strong>auto apply:</strong> <span></span><br></span>
<span class="auto-reboot"><strong>auto reboot:</strong> <span></span><br></span>
</td>
<td> <td>
<span class="received_bytes">??</span> of {{ update.build.binary.size }} bytes <span class="received_bytes">??</span> of {{ update.build.binary.size }} bytes
<br> <br>

View file

@ -6,6 +6,7 @@ def get_mesh_uplink_group(address):
MESH_ALL_UPLINKS_GROUP = "mesh_uplink_all" MESH_ALL_UPLINKS_GROUP = "mesh_uplink_all"
MESH_ALL_OTA_GROUP = "mesh_ota_all"
UPLINK_PING = 5 UPLINK_PING = 5
UPLINK_TIMEOUT = UPLINK_PING+5 UPLINK_TIMEOUT = UPLINK_PING+5