From 473e60aed0b8603efee3e0e9cb0630e37af7a921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Kl=C3=BCnder?= Date: Tue, 3 Oct 2023 17:51:49 +0200 Subject: [PATCH] more mesh node stuff --- .../control/templates/control/mesh_nodes.html | 21 +++---- src/c3nav/mesh/consumers.py | 55 +++++++++++++++---- .../migrations/0005_meshnode_last_signin.py | 18 ++++++ .../0006_rename_route_meshnode_uplink.py | 25 +++++++++ src/c3nav/mesh/models.py | 9 +-- 5 files changed, 100 insertions(+), 28 deletions(-) create mode 100644 src/c3nav/mesh/migrations/0005_meshnode_last_signin.py create mode 100644 src/c3nav/mesh/migrations/0006_rename_route_meshnode_uplink.py diff --git a/src/c3nav/control/templates/control/mesh_nodes.html b/src/c3nav/control/templates/control/mesh_nodes.html index 2f7eda76..2964819f 100644 --- a/src/c3nav/control/templates/control/mesh_nodes.html +++ b/src/c3nav/control/templates/control/mesh_nodes.html @@ -7,22 +7,14 @@ - - - + + {% for node in nodes %} - - - - + + {% endfor %}
{% trans 'Node' %}{% trans 'Status' %} {% trans 'Chip' %} {% trans 'Firmware' %} {% trans 'Last msg' %}{% trans 'Parent' %}{% trans 'Route' %}{% trans 'Last signin' %}{% trans 'Uplink' %}
- {% if node.route %} - {% trans "online" %} - {% else %} - {% trans "offline" %} - {% endif %} - {{ node }} {{ node.last_messages.CONFIG_FIRMWARE.parsed.get_chip_display }} @@ -37,9 +29,12 @@ {{ timesince }} ago {% endblocktrans %} {{ node.parent }}{{ node.route }}{{ node.last_messages.CONFIG_FIRMWARE.data }} + {% blocktrans trimmed with timesince=node.last_signin|timesince %} + {{ timesince }} ago + {% endblocktrans %} + {{ node.uplink }}
diff --git a/src/c3nav/mesh/consumers.py b/src/c3nav/mesh/consumers.py index 987f021c..0c0cb6ee 100644 --- a/src/c3nav/mesh/consumers.py +++ b/src/c3nav/mesh/consumers.py @@ -2,6 +2,7 @@ import traceback from asgiref.sync import async_to_sync from channels.generic.websocket import WebsocketConsumer +from django.utils import timezone from c3nav.mesh import messages from c3nav.mesh.models import MeshNode, NodeMessage @@ -19,9 +20,11 @@ class MeshConsumer(WebsocketConsumer): def disconnect(self, close_code): print('disconnected!') if self.uplink_node is not None: - self.remove_route(self.uplink_node) - self.channel_layer.group_discard('route_%s' % self.node.address.replace(':', ''), self.channel_name) - self.channel_layer.group_discard('route_broadcast', self.channel_name) + # leave broadcast group + async_to_sync(self.channel_layer.group_add)('mesh_broadcast', self.channel_name) + + # remove all other destinations + self.remove_dst_nodes(self.dst_nodes) def send_msg(self, msg): print('Sending message:', msg) @@ -57,9 +60,15 @@ class MeshConsumer(WebsocketConsumer): layer=messages.NO_LAYER )) - # add signed in uplink node to broadcast route + # add signed in uplink node to broadcast group async_to_sync(self.channel_layer.group_add)('mesh_broadcast', self.channel_name) + # kick out other consumers talking to the same uplink + async_to_sync(self.channel_layer.group_send)(self.group_name_for_node(msg.src), { + "type": "mesh.uplink_consumer", + "name": self.channel_name, + }) + # add this node as a destination that this uplink handles (duh) self.add_dst_nodes((src_node.address, )) @@ -72,12 +81,17 @@ class MeshConsumer(WebsocketConsumer): self.log_received_message(src_node, msg) - def uplink_change(self, data): + def mesh_uplink_consumer(self, data): + # message handler: if we are not the given uplink, leave this group + if data["name"] != self.channel_name: + print('shutting down since we have been replaced') + self.close() + + def mesh_dst_node_uplink(self, data): # message handler: if we are not the given uplink, leave this group if data["uplink"] != self.uplink_node.address: - group = self.group_name_for_node(data["address"]) - print('leaving uplink group...') - async_to_sync(self.channel_layer.group_discard)(group, self.channel_name) + print('leaving node group...') + self.remove_dst_nodes((data["address"], )) def log_received_message(self, src_node: MeshNode, msg: messages.Message): NodeMessage.objects.create( @@ -88,7 +102,6 @@ class MeshConsumer(WebsocketConsumer): ) def add_dst_nodes(self, addresses): - # add ourselves to this one for address in addresses: # create group name for this address group = self.group_name_for_node(address) @@ -100,7 +113,7 @@ class MeshConsumer(WebsocketConsumer): # tell other consumers to leave the group async_to_sync(self.channel_layer.group_send)(group, { - "type": "uplink_change", + "type": "mesh.dst_node_uplink", "node": address, "uplink": self.uplink_node.address }) @@ -114,7 +127,27 @@ class MeshConsumer(WebsocketConsumer): ) # add the stuff to the db as well - MeshNode.objects.filter(address__in=addresses).update(route_id=self.uplink_node.address) + MeshNode.objects.filter(address__in=addresses).update( + uplink_id=self.uplink_node.address, + last_signin=timezone.now(), + ) + + def remove_dst_nodes(self, addresses): + for address in addresses: + # create group name for this address + group = self.group_name_for_node(address) + + # leave the group + if address in self.dst_nodes: + async_to_sync(self.channel_layer.group_discard)(group, self.channel_name) + self.dst_nodes.discard(address) + + # add the stuff to the db as well + # todo: can't do this because of race condition + #MeshNode.objects.filter(address__in=addresses, uplink_id=self.uplink_node.address).update( + # uplink_id=self.uplink_node.address, + # last_signin=timezone.now(), + #) def group_name_for_node(self, address): return 'mesh_%s' % address.replace(':', '-') diff --git a/src/c3nav/mesh/migrations/0005_meshnode_last_signin.py b/src/c3nav/mesh/migrations/0005_meshnode_last_signin.py new file mode 100644 index 00000000..b0135334 --- /dev/null +++ b/src/c3nav/mesh/migrations/0005_meshnode_last_signin.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.1 on 2023-10-03 15:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mesh', '0004_relay_vs_src_node_and_remove_firmware'), + ] + + operations = [ + migrations.AddField( + model_name='meshnode', + name='last_signin', + field=models.DateTimeField(null=True, verbose_name='last signin'), + ), + ] diff --git a/src/c3nav/mesh/migrations/0006_rename_route_meshnode_uplink.py b/src/c3nav/mesh/migrations/0006_rename_route_meshnode_uplink.py new file mode 100644 index 00000000..9e61ab65 --- /dev/null +++ b/src/c3nav/mesh/migrations/0006_rename_route_meshnode_uplink.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2.1 on 2023-10-03 15:31 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('mesh', '0005_meshnode_last_signin'), + ] + + operations = [ + migrations.RenameField( + model_name='meshnode', + old_name='route', + new_name='uplink', + ), + migrations.AlterField( + model_name='meshnode', + name='uplink', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='routed_nodes', + to='mesh.meshnode', verbose_name='uplink'), + ), + ] diff --git a/src/c3nav/mesh/models.py b/src/c3nav/mesh/models.py index 5365edcf..05841653 100644 --- a/src/c3nav/mesh/models.py +++ b/src/c3nav/mesh/models.py @@ -32,8 +32,8 @@ class MeshNodeQuerySet(models.QuerySet): try: for message in NodeMessage.objects.order_by('-datetime', '-pk').filter( message_type__in=self._prefetch_last_messages, - node__in=nodes.keys(), - ).distinct('message_type', 'node'): + src_node__in=nodes.keys(), + ).distinct('message_type', 'src_node'): nodes[message.node].last_messages[message.message_type] = message except NotSupportedError: pass @@ -73,8 +73,9 @@ class MeshNode(models.Model): address = models.CharField(_('mac address'), max_length=17, primary_key=True) name = models.CharField(_('name'), max_length=32, null=True, blank=True) first_seen = models.DateTimeField(_('first seen'), auto_now_add=True) - route = models.ForeignKey('MeshNode', models.PROTECT, null=True, - related_name='routed_nodes', verbose_name=_('route')) + uplink = models.ForeignKey('MeshNode', models.PROTECT, null=True, + related_name='routed_nodes', verbose_name=_('uplink')) + last_signin = models.DateTimeField(_('last signin'), null=True) objects = models.Manager.from_queryset(MeshNodeQuerySet)() def __str__(self):