more mesh node stuff

This commit is contained in:
Laura Klünder 2023-10-03 17:51:49 +02:00
parent 4d3f54bbe8
commit 473e60aed0
5 changed files with 100 additions and 28 deletions

View file

@ -7,22 +7,14 @@
<table> <table>
<tr> <tr>
<th>{% trans 'Node' %}</th> <th>{% trans 'Node' %}</th>
<th>{% trans 'Status' %}</th>
<th>{% trans 'Chip' %}</th> <th>{% trans 'Chip' %}</th>
<th>{% trans 'Firmware' %}</th> <th>{% trans 'Firmware' %}</th>
<th>{% trans 'Last msg' %}</th> <th>{% trans 'Last msg' %}</th>
<th>{% trans 'Parent' %}</th> <th>{% trans 'Last signin' %}</th>
<th>{% trans 'Route' %}</th> <th>{% trans 'Uplink' %}</th>
</tr> </tr>
{% for node in nodes %} {% for node in nodes %}
<tr> <tr>
<td>
{% if node.route %}
<span style="color: green;">{% trans "online" %}</span>
{% else %}
<span style="color: red;">{% trans "offline" %}</span>
{% endif %}
</td>
<td>{{ node }}</td> <td>{{ node }}</td>
<td> <td>
{{ node.last_messages.CONFIG_FIRMWARE.parsed.get_chip_display }} {{ node.last_messages.CONFIG_FIRMWARE.parsed.get_chip_display }}
@ -37,9 +29,12 @@
{{ timesince }} ago {{ timesince }} ago
{% endblocktrans %} {% endblocktrans %}
</td> </td>
<td>{{ node.parent }}</td> <td>
<td>{{ node.route }}</td> {% blocktrans trimmed with timesince=node.last_signin|timesince %}
<td>{{ node.last_messages.CONFIG_FIRMWARE.data }}</td> {{ timesince }} ago
{% endblocktrans %}
</td>
<td>{{ node.uplink }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>

View file

@ -2,6 +2,7 @@ import traceback
from asgiref.sync import async_to_sync from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer from channels.generic.websocket import WebsocketConsumer
from django.utils import timezone
from c3nav.mesh import messages from c3nav.mesh import messages
from c3nav.mesh.models import MeshNode, NodeMessage from c3nav.mesh.models import MeshNode, NodeMessage
@ -19,9 +20,11 @@ class MeshConsumer(WebsocketConsumer):
def disconnect(self, close_code): def disconnect(self, close_code):
print('disconnected!') print('disconnected!')
if self.uplink_node is not None: if self.uplink_node is not None:
self.remove_route(self.uplink_node) # leave broadcast group
self.channel_layer.group_discard('route_%s' % self.node.address.replace(':', ''), self.channel_name) async_to_sync(self.channel_layer.group_add)('mesh_broadcast', self.channel_name)
self.channel_layer.group_discard('route_broadcast', self.channel_name)
# remove all other destinations
self.remove_dst_nodes(self.dst_nodes)
def send_msg(self, msg): def send_msg(self, msg):
print('Sending message:', msg) print('Sending message:', msg)
@ -57,9 +60,15 @@ class MeshConsumer(WebsocketConsumer):
layer=messages.NO_LAYER 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) 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) # add this node as a destination that this uplink handles (duh)
self.add_dst_nodes((src_node.address, )) self.add_dst_nodes((src_node.address, ))
@ -72,12 +81,17 @@ class MeshConsumer(WebsocketConsumer):
self.log_received_message(src_node, msg) 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 # message handler: if we are not the given uplink, leave this group
if data["uplink"] != self.uplink_node.address: if data["uplink"] != self.uplink_node.address:
group = self.group_name_for_node(data["address"]) print('leaving node group...')
print('leaving uplink group...') self.remove_dst_nodes((data["address"], ))
async_to_sync(self.channel_layer.group_discard)(group, self.channel_name)
def log_received_message(self, src_node: MeshNode, msg: messages.Message): def log_received_message(self, src_node: MeshNode, msg: messages.Message):
NodeMessage.objects.create( NodeMessage.objects.create(
@ -88,7 +102,6 @@ class MeshConsumer(WebsocketConsumer):
) )
def add_dst_nodes(self, addresses): def add_dst_nodes(self, addresses):
# add ourselves to this one
for address in addresses: for address in addresses:
# create group name for this address # create group name for this address
group = self.group_name_for_node(address) group = self.group_name_for_node(address)
@ -100,7 +113,7 @@ class MeshConsumer(WebsocketConsumer):
# tell other consumers to leave the group # tell other consumers to leave the group
async_to_sync(self.channel_layer.group_send)(group, { async_to_sync(self.channel_layer.group_send)(group, {
"type": "uplink_change", "type": "mesh.dst_node_uplink",
"node": address, "node": address,
"uplink": self.uplink_node.address "uplink": self.uplink_node.address
}) })
@ -114,7 +127,27 @@ class MeshConsumer(WebsocketConsumer):
) )
# add the stuff to the db as well # 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): def group_name_for_node(self, address):
return 'mesh_%s' % address.replace(':', '-') return 'mesh_%s' % address.replace(':', '-')

View file

@ -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'),
),
]

View file

@ -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'),
),
]

View file

@ -32,8 +32,8 @@ class MeshNodeQuerySet(models.QuerySet):
try: try:
for message in NodeMessage.objects.order_by('-datetime', '-pk').filter( for message in NodeMessage.objects.order_by('-datetime', '-pk').filter(
message_type__in=self._prefetch_last_messages, message_type__in=self._prefetch_last_messages,
node__in=nodes.keys(), src_node__in=nodes.keys(),
).distinct('message_type', 'node'): ).distinct('message_type', 'src_node'):
nodes[message.node].last_messages[message.message_type] = message nodes[message.node].last_messages[message.message_type] = message
except NotSupportedError: except NotSupportedError:
pass pass
@ -73,8 +73,9 @@ class MeshNode(models.Model):
address = models.CharField(_('mac address'), max_length=17, primary_key=True) address = models.CharField(_('mac address'), max_length=17, primary_key=True)
name = models.CharField(_('name'), max_length=32, null=True, blank=True) name = models.CharField(_('name'), max_length=32, null=True, blank=True)
first_seen = models.DateTimeField(_('first seen'), auto_now_add=True) first_seen = models.DateTimeField(_('first seen'), auto_now_add=True)
route = models.ForeignKey('MeshNode', models.PROTECT, null=True, uplink = models.ForeignKey('MeshNode', models.PROTECT, null=True,
related_name='routed_nodes', verbose_name=_('route')) related_name='routed_nodes', verbose_name=_('uplink'))
last_signin = models.DateTimeField(_('last signin'), null=True)
objects = models.Manager.from_queryset(MeshNodeQuerySet)() objects = models.Manager.from_queryset(MeshNodeQuerySet)()
def __str__(self): def __str__(self):