mirror of
https://github.com/pablorevilla-meshtastic/meshview.git
synced 2026-07-01 07:21:19 +02:00
Multiple changes to the code. Important to mentioned:
- We added another column to the nodes table to report on firmware version - Also changed the date and time format to be more readable. - Added a node list report - Modified the Mesh Graphs
This commit is contained in:
@@ -7,7 +7,8 @@ def init_database(database_connection_string):
|
||||
if not database_connection_string.startswith('sqlite'):
|
||||
kwargs['pool_size'] = 20
|
||||
kwargs['max_overflow'] = 50
|
||||
engine = create_async_engine(database_connection_string, echo=False, **kwargs)
|
||||
print (**kwargs)
|
||||
engine = create_async_engine(database_connection_string, echo=False, connect_args={"timeout": 15})
|
||||
async_session = async_sessionmaker(engine, expire_on_commit=False)
|
||||
|
||||
async def create_tables():
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from meshtastic.protobuf.mqtt_pb2 import MapReport
|
||||
from meshtastic.protobuf.portnums_pb2 import PortNum
|
||||
from meshtastic.protobuf.mesh_pb2 import (
|
||||
Position,
|
||||
@@ -24,6 +25,7 @@ DECODE_MAP = {
|
||||
PortNum.TRACEROUTE_APP: RouteDiscovery.FromString,
|
||||
PortNum.ROUTING_APP: Routing.FromString,
|
||||
PortNum.TEXT_MESSAGE_APP: text_message,
|
||||
PortNum.MAP_REPORT_APP: MapReport.FromString
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ class Node(Base):
|
||||
long_name: Mapped[str]
|
||||
short_name: Mapped[str]
|
||||
hw_model: Mapped[str]
|
||||
firmware: Mapped[str]
|
||||
role: Mapped[str] = mapped_column(nullable=True)
|
||||
last_lat: Mapped[int] = mapped_column(BigInteger, nullable=True)
|
||||
last_long: Mapped[int] = mapped_column(BigInteger, nullable=True)
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import base64
|
||||
import asyncio
|
||||
import random
|
||||
|
||||
import aiomqtt
|
||||
from google.protobuf.message import DecodeError
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
|
||||
from meshtastic.protobuf.mqtt_pb2 import ServiceEnvelope
|
||||
|
||||
KEY = base64.b64decode("1PG7OiApB1nwvP+rz05pAQ==")
|
||||
|
||||
+43
-2
@@ -2,7 +2,7 @@ import datetime
|
||||
|
||||
from sqlalchemy import select, func
|
||||
from sqlalchemy.orm import lazyload
|
||||
|
||||
from sqlalchemy import update
|
||||
from meshtastic.protobuf.config_pb2 import Config
|
||||
from meshtastic.protobuf.portnums_pb2 import PortNum
|
||||
from meshtastic.protobuf.mesh_pb2 import User, HardwareModel
|
||||
@@ -12,7 +12,34 @@ from meshview.models import Packet, PacketSeen, Node, Traceroute
|
||||
from meshview import notify
|
||||
|
||||
|
||||
|
||||
async def process_envelope(topic, env):
|
||||
|
||||
# Checking if the received packet is a MAP_REPORT
|
||||
# Update the node table with the firmware version
|
||||
if env.packet.decoded.portnum == PortNum.MAP_REPORT_APP:
|
||||
# Extract the node ID from the packet (renamed from 'id' to 'node_id' to avoid conflicts with Python's built-in id function)
|
||||
node_id = getattr(env.packet, "from")
|
||||
|
||||
# Decode the MAP report payload to extract the firmware version
|
||||
map_report = decode_payload.decode_payload(PortNum.MAP_REPORT_APP, env.packet.decoded.payload)
|
||||
|
||||
# Establish an asynchronous database session
|
||||
async with database.async_session() as session:
|
||||
# Construct an SQLAlchemy update statement
|
||||
stmt = (
|
||||
update(Node)
|
||||
.where(Node.node_id == node_id) # Ensure correct column reference
|
||||
.values(firmware=map_report.firmware_version) # Assign new firmware value
|
||||
)
|
||||
|
||||
# Execute the update statement asynchronously
|
||||
await session.execute(stmt)
|
||||
|
||||
# Commit the changes to the database
|
||||
await session.commit()
|
||||
|
||||
# This ignores any packet that does not have a ID
|
||||
if not env.packet.id:
|
||||
return
|
||||
|
||||
@@ -58,6 +85,8 @@ async def process_envelope(topic, env):
|
||||
)
|
||||
session.add(seen)
|
||||
|
||||
|
||||
|
||||
if env.packet.decoded.portnum == PortNum.NODEINFO_APP:
|
||||
user = decode_payload.decode_payload(
|
||||
PortNum.NODEINFO_APP, env.packet.decoded.payload
|
||||
@@ -89,7 +118,6 @@ async def process_envelope(topic, env):
|
||||
node.hw_model = hw_model
|
||||
node.role = role
|
||||
node.last_update =datetime.datetime.now()
|
||||
# if need to update time of last update it may be here
|
||||
|
||||
else:
|
||||
node = Node(
|
||||
@@ -481,3 +509,16 @@ async def get_nodes_mediumslow():
|
||||
return result.scalars()
|
||||
|
||||
|
||||
|
||||
async def get_nodes():
|
||||
async with database.async_session() as session:
|
||||
result = await session.execute(
|
||||
select(Node)
|
||||
.where(Node.last_update != "")
|
||||
.order_by(Node.long_name) # Sorting by long_name
|
||||
)
|
||||
return result.scalars()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</head>
|
||||
<body hx-indicator="#spinner">
|
||||
<br><div style="text-align:center"><strong>Bay Area Mesh - http://bayme.sh</strong></div>
|
||||
<div style="text-align:center">Quick Links: <a href="/">Search for a node </a> - <a href="/chat">Conversations</a> - <a href="/firehose">See <strong>everything</strong> </a> - Mesh Graph <a href="/graph/longfast">LG</a> - <a href="/graph/mediumslow">MS </a> - <a href="/stats">Stats </a></div><br>
|
||||
<div style="text-align:center">Quick Links: <a href="/">Search for a node </a> - <a href="/chat">Conversations</a> - <a href="/firehose">See <strong>everything</strong> </a> - Mesh Graph <a href="/graph/longfast">LG</a> - <a href="/graph/mediumslow">MS </a> - <a href="/nodelist">Nodes</a> - <a href="/stats">Stats </a></div><br>
|
||||
<div id="spinner" class="spinner-border secondary-primary htmx-indicator position-absolute top-50 start-50" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<div class="row chat-packet">
|
||||
<span class="col-3 timestamp">{{packet.import_time | format_timestamp}} <a href="/packet/{{packet.id}}">✉️</a></span>
|
||||
<span class="col-3 username"><a href="/packet_list/{{packet.from_node_id}}">{{packet.from_node.long_name or (packet.from_node_id | node_id_to_hex) }}</a></span>
|
||||
<span class="col message">{{packet.payload}}</span>
|
||||
</div>
|
||||
<span class="col-2 timestamp">{{packet.import_time.strftime('%-I:%M:%S %p - %d-%m-%Y')}} </span>
|
||||
<span class="col-1 timestamp"><a href="/packet/{{packet.id}}">✉️</a> {{packet.from_node.channel}}</span>
|
||||
<span class="col-2 username"><a href="/packet_list/{{packet.from_node_id}}">{{packet.from_node.long_name or (packet.from_node_id | node_id_to_hex) }}</a></span>
|
||||
<span class="col-6 message">{{packet.payload}}</span>
|
||||
</div>
|
||||
@@ -5,7 +5,7 @@
|
||||
</div>
|
||||
<div class="card-text text-start">
|
||||
<dl>
|
||||
<div>{{packet.import_time | format_timestamp}}</div>
|
||||
<div>{{packet.import_time.strftime('%-I:%M:%S %p - %d-%m-%Y')}}</div>
|
||||
<div>{{packet.payload}}</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block css %}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 10px;
|
||||
border: 1px solid #333;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #1f1f1f;
|
||||
color: white;
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: #181818;
|
||||
}
|
||||
|
||||
tr:nth-child(odd) {
|
||||
background-color: #222;
|
||||
}
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div>
|
||||
{% if nodes %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Node ID</th>
|
||||
<th>Long Name</th>
|
||||
<th>Short Name</th>
|
||||
<th>HW Model</th>
|
||||
<th>Firmware</th>
|
||||
<th>Role</th>
|
||||
<th>Last Latitude</th>
|
||||
<th>Last Longitude</th>
|
||||
<th>Channel</th>
|
||||
<th>Last Update</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for node in nodes %}
|
||||
<tr>
|
||||
<td> <a href="/packet_list/{{node.node_id }}">{{node.node_id }}</a></td>
|
||||
<td>{{ node.long_name }}</td>
|
||||
<td>{{ node.short_name }}</td>
|
||||
<td>{{ node.hw_model }}</td>
|
||||
<td>{{ node.firmware }}</td>
|
||||
<td>{{ node.role if node.role else "N/A" }}</td>
|
||||
<td>{{ node.last_lat if node.last_lat else "N/A" }}</td>
|
||||
<td>{{ node.last_long if node.last_long else "N/A" }}</td>
|
||||
<td>{{ node.channel }}</td>
|
||||
<td>{{ node.last_update.strftime('%-I:%M:%S %p - %d-%m-%Y') if node.last_update else "N/A" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>No nodes found.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -34,8 +34,8 @@
|
||||
</div>
|
||||
<div class="card-text text-start">
|
||||
<dl>
|
||||
<dt>import_time</dt>
|
||||
<dd>{{packet.import_time | format_timestamp}}</dd>
|
||||
<dt>Import Time</dt>
|
||||
<dd>{{packet.import_time.strftime('%-I:%M:%S %p - %d-%m-%Y')}}</dd>
|
||||
<dt>packet</dt>
|
||||
<dd><pre>{{packet.data}}</pre></dd>
|
||||
<dt>payload</dt>
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
<div class="card-body">
|
||||
<div class="card-text text-start">
|
||||
<dl>
|
||||
<dt>import_time</dt>
|
||||
<dd>{{seen.import_time|format_timestamp}}</dd>
|
||||
<dt>Import Time</dt>
|
||||
<dd>{{seen.import_time.strftime('%-I:%M:%S %p - %d-%m-%Y')}}</dd>
|
||||
<dt>rx_time</dt>
|
||||
<dd>{{seen.rx_time|format_timestamp}}</dd>
|
||||
<dt>hop_limit</dt>
|
||||
|
||||
+32
-11
@@ -474,14 +474,13 @@ async def packet_details(request):
|
||||
content_type="text/html",
|
||||
)
|
||||
|
||||
|
||||
@routes.get("/chat")
|
||||
async def chat(request):
|
||||
try:
|
||||
# Fetch packets for the given node ID and port number
|
||||
#print("Fetching packets...")
|
||||
packets = await store.get_packets(
|
||||
node_id=0xFFFFFFFF, portnum=PortNum.TEXT_MESSAGE_APP
|
||||
node_id=0xFFFFFFFF, portnum=PortNum.TEXT_MESSAGE_APP, limit=100
|
||||
)
|
||||
#print(f"Fetched {len(packets)} packets.")
|
||||
|
||||
@@ -625,9 +624,6 @@ async def graph_chutil(request):
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@routes.get("/graph/wind_speed/{node_id}")
|
||||
async def graph_wind_speed(request):
|
||||
return await graph_telemetry(
|
||||
@@ -1022,7 +1018,10 @@ async def graph_network(request):
|
||||
|
||||
#graph = pydot.Dot('network', graph_type="digraph", layout="sfdp", overlap="prism", quadtree="2", repulsiveforce="1.5", k="1", overlap_scaling="1.5", concentrate=True)
|
||||
#graph = pydot.Dot('network', graph_type="digraph", layout="sfdp", overlap="prism1000", overlap_scaling="-4", sep="1000", pack="true")
|
||||
graph = pydot.Dot('network', graph_type="digraph", layout="neato", overlap="false", model='subset', esep="+5")
|
||||
#graph = pydot.Dot('network', graph_type="digraph", layout="neato", overlap="false", model='subset', esep="+5")
|
||||
graph = pydot.Dot('network', graph_type="digraph", layout="sfdp", overlap="prism", esep="+10", nodesep="0.5",
|
||||
ranksep="1")
|
||||
|
||||
for node_id in used_nodes:
|
||||
node = await nodes[node_id]
|
||||
color = '#000000'
|
||||
@@ -1229,20 +1228,21 @@ async def graph_network_longfast(request):
|
||||
edges = new_edges
|
||||
|
||||
# Create graph
|
||||
graph = pydot.Dot('network', graph_type="digraph", layout="neato", overlap="false", model='subset', esep="+5")
|
||||
graph = pydot.Dot('network', graph_type="digraph", layout="sfdp", overlap="scale", model='subset', splines="true")
|
||||
for node_id in used_nodes:
|
||||
node = await nodes[node_id]
|
||||
color = '#000000'
|
||||
node_name = await get_node_name(node_id)
|
||||
if node and node.role in ('ROUTER', 'ROUTER_CLIENT', 'REPEATER'):
|
||||
color = '#0000FF'
|
||||
elif node and node.role == 'CLIENT_MUTE':
|
||||
color = '#00FF00'
|
||||
#elif node and node.role == 'CLIENT_MUTE':
|
||||
# color = '#00FF00'
|
||||
graph.add_node(pydot.Node(
|
||||
str(node_id),
|
||||
label=node_name,
|
||||
shape='box',
|
||||
color=color,
|
||||
fontsize="10", width="0", height="0",
|
||||
href=f"/graph/network?root={node_id}&depth={depth-1}",
|
||||
))
|
||||
|
||||
@@ -1276,8 +1276,9 @@ async def graph_network_longfast(request):
|
||||
str(dest),
|
||||
color=color,
|
||||
tooltip=f'{await get_node_name(src)} -> {await get_node_name(dest)}',
|
||||
penwidth=1.85,
|
||||
penwidth=.5,
|
||||
dir=edge_dir,
|
||||
arrowsize=".5",
|
||||
))
|
||||
|
||||
return web.Response(
|
||||
@@ -1407,7 +1408,8 @@ async def graph_network_mediumslow(request):
|
||||
edges = new_edges
|
||||
|
||||
# Create graph
|
||||
graph = pydot.Dot('network', graph_type="digraph", layout="neato", overlap="false", model='subset', esep="+5")
|
||||
graph = pydot.Dot('network', graph_type="digraph", layout="sfdp", overlap="scale", model='subset', esep="+5", splines="true", nodesep="2", ranksep="2")
|
||||
|
||||
for node_id in used_nodes:
|
||||
node = await nodes[node_id]
|
||||
color = '#000000'
|
||||
@@ -1467,6 +1469,25 @@ async def graph_network_mediumslow(request):
|
||||
print(f"Error in graph_network_longfast: {e}")
|
||||
return web.Response(status=500, text="Internal Server Error")
|
||||
|
||||
@routes.get("/nodelist")
|
||||
async def nodelist(request):
|
||||
try:
|
||||
nodes= await store.get_nodes()
|
||||
template = env.get_template("nodelist.html")
|
||||
return web.Response(
|
||||
text=template.render(nodes=nodes),
|
||||
content_type="text/html",
|
||||
)
|
||||
except Exception as e:
|
||||
|
||||
return web.Response(
|
||||
text="An error occurred while processing your request.",
|
||||
status=500,
|
||||
content_type="text/plain",
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
async def run_server(bind, port, tls_cert):
|
||||
|
||||
Reference in New Issue
Block a user