From 03430309801267ca39d047c23ee73166a3495df0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Thu, 5 Oct 2023 05:02:01 +0200 Subject: [PATCH] mesh route results --- .../control/fragment_mesh_websocket.html | 56 +++++++++++++++++-- .../control/mesh_message_sending.html | 51 +++++++++++++---- src/c3nav/control/views/mesh.py | 11 ++-- src/c3nav/mesh/consumers.py | 36 +++++++++--- src/c3nav/mesh/forms.py | 47 +++++++++++----- src/c3nav/mesh/messages.py | 6 +- src/c3nav/mesh/utils.py | 10 ++++ 7 files changed, 170 insertions(+), 47 deletions(-) diff --git a/src/c3nav/control/templates/control/fragment_mesh_websocket.html b/src/c3nav/control/templates/control/fragment_mesh_websocket.html index 0cd36683..6d266887 100644 --- a/src/c3nav/control/templates/control/fragment_mesh_websocket.html +++ b/src/c3nav/control/templates/control/fragment_mesh_websocket.html @@ -24,10 +24,10 @@ function connect() { window.setTimeout(connect, 500); } ws.onmessage = (event) => { - var data = JSON.parse(event.data), line, text; + var data = JSON.parse(event.data), line, text, cell, link_tag; switch(data.type) { case 'mesh.log_entry': - line = document.createElement("tr"), cell, link_tag; + line = document.createElement("tr"); cell = document.createElement("td"); cell.innerText = data.timestamp; @@ -37,7 +37,7 @@ function connect() { cell.innerText = data.channel; if (data.uplink) { cell.append(document.createElement("br")); - link_tag = document.createElement("a") + link_tag = document.createElement("a"); link_tag.href = "/control/mesh/" + data.uplink; link_tag.innerText = data.uplink; if (node_names[data.uplink]) { @@ -48,7 +48,7 @@ function connect() { line.appendChild(cell); cell = document.createElement("td"); - link_tag = document.createElement("a") + link_tag = document.createElement("a"); link_tag.href = "/control/mesh/" + data.node; link_tag.innerText = data.node; if (node_names[data.node]) { @@ -73,6 +73,54 @@ function connect() { document.getElementById("sending-status-"+data.recipient).appendChild(line); {% endif %} break; + + case 'mesh.msg_received': + {% if send_uuid and msg_type == "MESH_ROUTE_REQUEST" %} + if (data.msg.route) { + link_tag = document.createElement("a"); + link_tag.href = "/control/mesh/" + data.msg.src; + link_tag.innerText = data.msg.src; + if (node_names[data.msg.src]) { + link_tag.innerText += " ("+node_names[data.msg.src]+")"; + } + if (data.msg.route === "00:00:00:00:00:00") { + line = document.createElement("li"); + line.appendChild(link_tag); + document.getElementById("no-routes").appendChild(line); + } else { + line = document.createElement("tr"); + + cell = document.createElement("td"); + cell.appendChild(link_tag); + line.appendChild(cell); + + cell = document.createElement("td"); + link_tag = document.createElement("a"); + link_tag.href = "/control/mesh/" + data.msg.route; + link_tag.innerText = data.msg.route; + if (node_names[data.msg.route]) { + link_tag.innerText += " ("+node_names[data.msg.route]+")"; + } + cell.append(link_tag); + line.appendChild(cell); + + document.getElementById("route-responses").appendChild(line); + } + } else { + for (var i=0;iGo back

- - - - - - {% for address, name in recipients %} - - - - - {% endfor %} -
RecipientStatus
{{ address }}{% if name %} ({{ name }}){% endif %}
+
+
+

Sending progress

+ + + + + + {% for address, name in recipients %} + + + + + {% endfor %} +
RecipientStatus
{{ address }}{% if name %} ({{ name }}){% endif %}
+
+ {% if msg_type == "MESH_ROUTE_REQUEST" %} +
+

Routes

+ + + + + + + + +
{% trans 'Node' %}{% trans 'Route' %}
+
+
+

No Route

+
    +
    +
    +

    Trace

    +
      +
      + {% endif %} +
      {% include "control/fragment_mesh_websocket.html" %} {% endblock %} diff --git a/src/c3nav/control/views/mesh.py b/src/c3nav/control/views/mesh.py index c548bc54..b72e6a36 100644 --- a/src/c3nav/control/views/mesh.py +++ b/src/c3nav/control/views/mesh.py @@ -14,6 +14,7 @@ from c3nav.control.views.base import ControlPanelMixin from c3nav.mesh.forms import MeshMessageForm, MeshNodeForm from c3nav.mesh.messages import MeshMessageType from c3nav.mesh.models import MeshNode, NodeMessage +from c3nav.mesh.utils import get_node_names class MeshNodeListView(ControlPanelMixin, ListView): @@ -137,9 +138,7 @@ class MeshMessageSendingView(ControlPanelMixin, TemplateView): data = self.request.session["mesh_msg_%s" % uuid] except KeyError: raise Http404 - node_names = { - node.address: node.name for node in MeshNode.objects.all() - } + node_names = get_node_names() return { **super().get_context_data(), "node_names": node_names, @@ -156,7 +155,5 @@ class MeshLogView(ControlPanelMixin, TemplateView): def get_context_data(self, **kwargs): return { **super().get_context_data(), - "node_names": { - node.address: node.name for node in MeshNode.objects.all() - } - } \ No newline at end of file + "node_names": get_node_names(), + } diff --git a/src/c3nav/mesh/consumers.py b/src/c3nav/mesh/consumers.py index 0ce46b6c..03deb4e4 100644 --- a/src/c3nav/mesh/consumers.py +++ b/src/c3nav/mesh/consumers.py @@ -6,7 +6,7 @@ from django.utils import timezone from c3nav.mesh.utils import get_mesh_comm_group from c3nav.mesh import messages -from c3nav.mesh.messages import MeshMessage, BROADCAST_ADDRESS +from c3nav.mesh.messages import MeshMessage, MESH_BROADCAST_ADDRESS, MeshMessageType from c3nav.mesh.models import MeshNode, NodeMessage @@ -23,7 +23,9 @@ class MeshConsumer(WebsocketConsumer): self.log_text(self.uplink_node, "mesh websocket disconnected") if self.uplink_node is not None: # leave broadcast group - async_to_sync(self.channel_layer.group_add)(get_mesh_comm_group(BROADCAST_ADDRESS), self.channel_name) + async_to_sync(self.channel_layer.group_discard)( + get_mesh_comm_group(MESH_BROADCAST_ADDRESS), self.channel_name + ) # remove all other destinations self.remove_dst_nodes(self.dst_nodes) @@ -51,7 +53,7 @@ class MeshConsumer(WebsocketConsumer): traceback.print_exc() return - if msg.dst != messages.ROOT_ADDRESS and msg.dst != messages.PARENT_ADDRESS: + if msg.dst != messages.MESH_ROOT_ADDRESS and msg.dst != messages.MESH_PARENT_ADDRESS: print('Received message for forwarding:', msg) # todo: this message isn't for us, forward it return @@ -67,13 +69,15 @@ class MeshConsumer(WebsocketConsumer): # inform signed in uplink node about its layer self.send_msg(messages.MeshLayerAnnounceMessage( - src=messages.ROOT_ADDRESS, + src=messages.MESH_ROOT_ADDRESS, dst=msg.src, layer=messages.NO_LAYER )) # add signed in uplink node to broadcast group - async_to_sync(self.channel_layer.group_add)('mesh_broadcast', self.channel_name) + async_to_sync(self.channel_layer.group_add)( + get_mesh_comm_group(MESH_BROADCAST_ADDRESS), self.channel_name + ) # kick out other consumers talking to the same uplink async_to_sync(self.channel_layer.group_send)(get_mesh_comm_group(msg.src), { @@ -179,7 +183,7 @@ class MeshConsumer(WebsocketConsumer): # tell the node to dump its current information self.send_msg( messages.ConfigDumpMessage( - src=messages.ROOT_ADDRESS, + src=messages.MESH_ROOT_ADDRESS, dst=address, ) ) @@ -230,10 +234,15 @@ class MeshUIConsumer(JsonWebsocketConsumer): if not msg_to_send: return self.scope["session"].save() + async_to_sync(self.channel_layer.group_add)("mesh_msg_sent", self.channel_name) self.msg_sent_filter = {"sender": self.channel_name} + + if msg_to_send["msg_data"]["msg_id"] == MeshMessageType.MESH_ROUTE_REQUEST: + async_to_sync(self.channel_layer.group_add)("mesh_msg_received", self.channel_name) + self.msg_received_filter = {"request_id": msg_to_send["msg_data"]["request_id"]} + for recipient in msg_to_send["recipients"]: - print('send to', recipient) MeshMessage.fromjson({ 'dst': recipient, **msg_to_send["msg_data"], @@ -243,7 +252,6 @@ class MeshUIConsumer(JsonWebsocketConsumer): self.send_json(data) def mesh_msg_sent(self, data): - print('got data', data) for key, value in self.msg_sent_filter.items(): if isinstance(value, list): if data.get(key, None) not in value: @@ -253,6 +261,18 @@ class MeshUIConsumer(JsonWebsocketConsumer): return self.send_json(data) + def mesh_msg_received(self, data): + print('got received', data) + for key, filter_value in self.msg_received_filter.items(): + value = data.get(key, data["msg"].get(key, None)) + if isinstance(filter_value, list): + if value not in filter_value: + return + else: + if value != filter_value: + return + self.send_json(data) + def disconnect(self, code): async_to_sync(self.channel_layer.group_discard)("mesh_log", self.channel_name) async_to_sync(self.channel_layer.group_discard)("mesh_msg_sent", self.channel_name) diff --git a/src/c3nav/mesh/forms.py b/src/c3nav/mesh/forms.py index 23f0d1e9..64c6ee2c 100644 --- a/src/c3nav/mesh/forms.py +++ b/src/c3nav/mesh/forms.py @@ -1,4 +1,6 @@ +import time from dataclasses import fields as dataclass_fields +from functools import cached_property from django import forms from django.core.exceptions import ValidationError @@ -6,7 +8,7 @@ from django.http import Http404 from django.utils.translation import gettext_lazy as _ from c3nav.mesh.dataformats import LedConfig -from c3nav.mesh.messages import MeshMessageType, MeshMessage, ROOT_ADDRESS +from c3nav.mesh.messages import MeshMessageType, MeshMessage, MESH_ROOT_ADDRESS, MESH_BROADCAST_ADDRESS from c3nav.mesh.models import MeshNode @@ -25,23 +27,26 @@ class MeshMessageForm(forms.Form): super().__init__(*args, initial=initial, **kwargs) recipient_root_choices = { - 'ff:ff:ff:ff:ff:ff': _('broadcast') + MESH_BROADCAST_ADDRESS: _('broadcast') } - recipient_node_choices = { + node_choices = { node.address: str(node) for node in MeshNode.objects.all() } - self.recipient_choices = { + self.node_choices_flat = { **recipient_root_choices, - **recipient_node_choices, + **node_choices, } + self.node_choices = tuple(node_choices.items()) + self.node_choices_with_broadcast = ( + *recipient_root_choices.items(), + (_('nodes'), self.node_choices), + ) + if self.recipient is None: - self.fields['recipients'].choices = ( - *recipient_root_choices.items(), - (_('nodes'), tuple(recipient_node_choices.items())) - ) + self.fields['recipients'].choices = self.node_choices_with_broadcast else: - if self.recipient not in self.recipient_choices: - raise Http404 + if self.recipient not in self.node_choices_flat: + raise Http404('unknown recipient') self.fields.pop('recipients') # noinspection PyMethodOverriding @@ -56,7 +61,7 @@ class MeshMessageForm(forms.Form): return cls.msg_types[msg_type] def get_recipient_display(self): - return self.recipient_choices[self.recipient] + return self.node_choices_flat[self.recipient] def get_cleaned_msg_data(self): msg_data = self.cleaned_data.copy() @@ -69,7 +74,7 @@ class MeshMessageForm(forms.Form): return { 'msg_id': self.msg_type, - 'src': ROOT_ADDRESS, + 'src': MESH_ROOT_ADDRESS, **self.get_cleaned_msg_data(), } @@ -87,6 +92,22 @@ class MeshMessageForm(forms.Form): }).send() +class MeshRouteRequestForm(MeshMessageForm): + msg_type = MeshMessageType.MESH_ROUTE_REQUEST + + address = forms.ChoiceField(choices=()) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields["address"].choices = self.node_choices + + def get_msg_data(self): + return { + **super().get_msg_data(), + "request_id": int(time.time()*100000) % 2**32, + } + + class ConfigUplinkMessageForm(MeshMessageForm): msg_type = MeshMessageType.CONFIG_UPLINK diff --git a/src/c3nav/mesh/messages.py b/src/c3nav/mesh/messages.py index e0427df9..f33c6245 100644 --- a/src/c3nav/mesh/messages.py +++ b/src/c3nav/mesh/messages.py @@ -11,9 +11,9 @@ from c3nav.mesh.utils import get_mesh_comm_group, indent_c from c3nav.mesh.dataformats import (BoolFormat, FixedStrFormat, HexFormat, LedConfig, LedConfigFormat, MacAddressesListFormat, MacAddressFormat, SimpleFormat, VarStrFormat) -ROOT_ADDRESS = '00:00:00:00:00:00' -PARENT_ADDRESS = '00:00:00:ff:ff:ff' -BROADCAST_ADDRESS = 'ff:ff:ff:ff:ff:ff' +MESH_ROOT_ADDRESS = '00:00:00:00:00:00' +MESH_PARENT_ADDRESS = '00:00:00:ff:ff:ff' +MESH_BROADCAST_ADDRESS = 'ff:ff:ff:ff:ff:ff' NO_LAYER = 0xFF @unique diff --git a/src/c3nav/mesh/utils.py b/src/c3nav/mesh/utils.py index 1408f6a5..393561ed 100644 --- a/src/c3nav/mesh/utils.py +++ b/src/c3nav/mesh/utils.py @@ -4,3 +4,13 @@ def get_mesh_comm_group(address): def indent_c(code): return " "+code.replace("\n", "\n ") + + +def get_node_names(): + from c3nav.mesh.models import MeshNode + return { + **{node.address: node.name for node in MeshNode.objects.all()}, + 'ff:ff:ff:ff:ff:ff': "broadcast", + '00:00:00:ff:ff:ff': "direct parent", + '00:00:00:00:00:00': "root", + }