diff --git a/meshview/database.py b/meshview/database.py
index 5451274..a20e37b 100644
--- a/meshview/database.py
+++ b/meshview/database.py
@@ -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():
diff --git a/meshview/decode_payload.py b/meshview/decode_payload.py
index 99fe696..f8f9a20 100644
--- a/meshview/decode_payload.py
+++ b/meshview/decode_payload.py
@@ -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
}
diff --git a/meshview/models.py b/meshview/models.py
index c1c5b2a..3c0587f 100644
--- a/meshview/models.py
+++ b/meshview/models.py
@@ -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)
diff --git a/meshview/mqtt_reader.py b/meshview/mqtt_reader.py
index 1bac492..5453f5c 100644
--- a/meshview/mqtt_reader.py
+++ b/meshview/mqtt_reader.py
@@ -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==")
diff --git a/meshview/store.py b/meshview/store.py
index c7e3a9b..3c5dec4 100644
--- a/meshview/store.py
+++ b/meshview/store.py
@@ -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()
+
+
+
+
diff --git a/meshview/templates/base.html b/meshview/templates/base.html
index af948b6..0c40b77 100644
--- a/meshview/templates/base.html
+++ b/meshview/templates/base.html
@@ -34,7 +34,7 @@
Bay Area Mesh - http://bayme.sh
-
+
Loading...
diff --git a/meshview/templates/chat_packet.html b/meshview/templates/chat_packet.html
index 1c95df4..9a6d833 100644
--- a/meshview/templates/chat_packet.html
+++ b/meshview/templates/chat_packet.html
@@ -1,5 +1,6 @@
+ {{packet.import_time.strftime('%-I:%M:%S %p - %d-%m-%Y')}}
+ ✉️ {{packet.from_node.channel}}
+ {{packet.from_node.long_name or (packet.from_node_id | node_id_to_hex) }}
+ {{packet.payload}}
+
\ No newline at end of file
diff --git a/meshview/templates/net_packet.html b/meshview/templates/net_packet.html
index 7582a1f..fe958f7 100644
--- a/meshview/templates/net_packet.html
+++ b/meshview/templates/net_packet.html
@@ -5,7 +5,7 @@
- {{packet.import_time | format_timestamp}}
+ {{packet.import_time.strftime('%-I:%M:%S %p - %d-%m-%Y')}}
{{packet.payload}}
diff --git a/meshview/templates/nodelist.html b/meshview/templates/nodelist.html
new file mode 100644
index 0000000..f175b60
--- /dev/null
+++ b/meshview/templates/nodelist.html
@@ -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 %}
+
+ {% if nodes %}
+
+
+
+ | Node ID |
+ Long Name |
+ Short Name |
+ HW Model |
+ Firmware |
+ Role |
+ Last Latitude |
+ Last Longitude |
+ Channel |
+ Last Update |
+
+
+
+ {% for node in nodes %}
+
+ | {{node.node_id }} |
+ {{ node.long_name }} |
+ {{ node.short_name }} |
+ {{ node.hw_model }} |
+ {{ node.firmware }} |
+ {{ node.role if node.role else "N/A" }} |
+ {{ node.last_lat if node.last_lat else "N/A" }} |
+ {{ node.last_long if node.last_long else "N/A" }} |
+ {{ node.channel }} |
+ {{ node.last_update.strftime('%-I:%M:%S %p - %d-%m-%Y') if node.last_update else "N/A" }} |
+
+ {% endfor %}
+
+
+ {% else %}
+
No nodes found.
+ {% endif %}
+
+{% endblock %}
diff --git a/meshview/templates/packet.html b/meshview/templates/packet.html
index e430d32..a6f7ceb 100644
--- a/meshview/templates/packet.html
+++ b/meshview/templates/packet.html
@@ -34,8 +34,8 @@
- - import_time
- - {{packet.import_time | format_timestamp}}
+ - Import Time
+ - {{packet.import_time.strftime('%-I:%M:%S %p - %d-%m-%Y')}}
- packet
{{packet.data}}
- payload
diff --git a/meshview/templates/packet_details.html b/meshview/templates/packet_details.html
index 3de8ebc..d273f0a 100644
--- a/meshview/templates/packet_details.html
+++ b/meshview/templates/packet_details.html
@@ -12,8 +12,8 @@
- - import_time
- - {{seen.import_time|format_timestamp}}
+ - Import Time
+ - {{seen.import_time.strftime('%-I:%M:%S %p - %d-%m-%Y')}}
- rx_time
- {{seen.rx_time|format_timestamp}}
- hop_limit
diff --git a/meshview/web.py b/meshview/web.py
index bc8f06c..a77b559 100644
--- a/meshview/web.py
+++ b/meshview/web.py
@@ -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):