mirror of
https://github.com/pablorevilla-meshtastic/meshview.git
synced 2026-08-06 17:02:48 +02:00
Graph traceroutes.
This commit is contained in:
@@ -51,3 +51,13 @@ class PacketSeen(Base):
|
||||
rx_rssi: Mapped[int] = mapped_column(nullable=True)
|
||||
topic: Mapped[str]
|
||||
import_time: Mapped[datetime]
|
||||
|
||||
|
||||
class Traceroute(Base):
|
||||
__tablename__ = "traceroute"
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
packet_id = mapped_column(ForeignKey("packet.id"))
|
||||
gateway_node_id: Mapped[int] = mapped_column(BigInteger)
|
||||
done: Mapped[bool]
|
||||
route: Mapped[bytes]
|
||||
|
||||
|
||||
+22
-1
@@ -6,7 +6,7 @@ from meshtastic.portnums_pb2 import PortNum
|
||||
from meshtastic.mesh_pb2 import User, HardwareModel
|
||||
from meshview import database
|
||||
from meshview import decode_payload
|
||||
from meshview.models import Packet, PacketSeen, Node
|
||||
from meshview.models import Packet, PacketSeen, Node, Traceroute
|
||||
from meshview import notify
|
||||
|
||||
|
||||
@@ -99,6 +99,18 @@ async def process_envelope(topic, env):
|
||||
node.last_long = position.longitude_i
|
||||
session.add(node)
|
||||
|
||||
if env.packet.decoded.portnum == PortNum.TRACEROUTE_APP:
|
||||
if env.packet.decoded.want_response:
|
||||
packet_id = env.packet.id
|
||||
else:
|
||||
packet_id = env.packet.decoded.request_id
|
||||
session.add(Traceroute(
|
||||
packet_id=packet_id,
|
||||
route=env.packet.decoded.payload,
|
||||
done=not env.packet.decoded.want_response,
|
||||
gateway_node_id=int(env.gateway_id[1:], 16),
|
||||
))
|
||||
|
||||
await session.commit()
|
||||
if new_packet:
|
||||
await packet.awaitable_attrs.to_node
|
||||
@@ -188,3 +200,12 @@ async def has_packets(node_id, portnum):
|
||||
select(Packet.id).where(Packet.from_node_id == node_id).limit(1)
|
||||
)).scalar()
|
||||
)
|
||||
|
||||
|
||||
async def get_traceroute(packet_id):
|
||||
async with database.async_session() as session:
|
||||
result = await session.execute(
|
||||
select(Traceroute)
|
||||
.where(Traceroute.packet_id == packet_id)
|
||||
)
|
||||
return result.scalars()
|
||||
|
||||
@@ -53,6 +53,9 @@
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% if packet.raw_mesh_packet.decoded.want_response %}
|
||||
<a href="/graph/traceroute/{{packet.id}}">graph</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<pre>{{packet.payload}}</pre>
|
||||
</dd>
|
||||
|
||||
@@ -7,6 +7,7 @@ from aiohttp_sse import sse_response
|
||||
import ssl
|
||||
import re
|
||||
|
||||
import pydot
|
||||
from pandas import DataFrame
|
||||
import seaborn as sns
|
||||
import matplotlib.pyplot as plt
|
||||
@@ -528,6 +529,90 @@ async def graph_neighbors(request):
|
||||
)
|
||||
|
||||
|
||||
@routes.get("/graph/traceroute/{packet_id}")
|
||||
async def graph_traceroute(request):
|
||||
packet_id = int(request.match_info['packet_id'])
|
||||
traceroutes = list(await store.get_traceroute(packet_id))
|
||||
|
||||
packet = await store.get_packet(packet_id)
|
||||
|
||||
node_ids = set()
|
||||
for tr in traceroutes:
|
||||
route = decode_payload.decode_payload(PortNum.TRACEROUTE_APP, tr.route)
|
||||
node_ids.add(tr.gateway_node_id)
|
||||
for node_id in route.route:
|
||||
node_ids.add(node_id)
|
||||
node_ids.add(packet.from_node_id)
|
||||
node_ids.add(packet.to_node_id)
|
||||
|
||||
nodes = {}
|
||||
async with asyncio.TaskGroup() as tg:
|
||||
for node_id in node_ids:
|
||||
nodes[node_id] = tg.create_task(store.get_node(node_id))
|
||||
|
||||
graph = pydot.Dot('traceroute', graph_type="digraph")
|
||||
|
||||
paths = set()
|
||||
node_color = {}
|
||||
mqtt_nodes = set()
|
||||
saw_reply = set()
|
||||
dest = None
|
||||
for tr in traceroutes:
|
||||
if tr.done:
|
||||
saw_reply.add(tr.gateway_node_id)
|
||||
if tr.done and dest:
|
||||
continue
|
||||
route = decode_payload.decode_payload(PortNum.TRACEROUTE_APP, tr.route)
|
||||
path = [packet.from_node_id]
|
||||
path.extend(route.route)
|
||||
if tr.done:
|
||||
dest = packet.to_node_id
|
||||
path.append(packet.to_node_id)
|
||||
elif path[-1] != tr.gateway_node_id:
|
||||
# It seems some nodes add them self to the list before uplinking
|
||||
path.append(tr.gateway_node_id)
|
||||
mqtt_nodes.add(tr.gateway_node_id)
|
||||
node_color[path[-1]] = '#' + hex(hash(tuple(path)))[3:9]
|
||||
paths.add(tuple(path))
|
||||
|
||||
used_nodes = set()
|
||||
for path in paths:
|
||||
used_nodes.update(path)
|
||||
|
||||
for node_id in used_nodes:
|
||||
node = await nodes[node_id]
|
||||
if not node:
|
||||
node_name = node_id_to_hex(node_id)
|
||||
else:
|
||||
node_name = f'[{node.short_name}] {node.long_name} - {node_id_to_hex(node_id)}'
|
||||
style = 'dashed'
|
||||
if node_id == dest:
|
||||
style = 'filled'
|
||||
elif node_id in mqtt_nodes:
|
||||
style = 'solid'
|
||||
|
||||
if node_id in saw_reply:
|
||||
style += ', diagonals'
|
||||
|
||||
graph.add_node(pydot.Node(
|
||||
str(node_id),
|
||||
label=node_name,
|
||||
shape='box',
|
||||
color=node_color.get(node_id, 'black'),
|
||||
style=style,
|
||||
))
|
||||
|
||||
for path in paths:
|
||||
color = '#' + hex(hash(tuple(path)))[3:9]
|
||||
for src, dest in zip(path, path[1:]):
|
||||
graph.add_edge(pydot.Edge(src, dest, color=color))
|
||||
|
||||
return web.Response(
|
||||
body=graph.create_svg(),
|
||||
content_type="image/svg+xml",
|
||||
)
|
||||
|
||||
|
||||
async def run_server(bind, port, tls_cert):
|
||||
app = web.Application()
|
||||
app.add_routes(routes)
|
||||
|
||||
@@ -9,3 +9,4 @@ Jinja2
|
||||
aiohttp-sse
|
||||
asyncpg
|
||||
seaborn
|
||||
pydot
|
||||
|
||||
Reference in New Issue
Block a user