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
-
Quick Links:  Search for a node  - Conversations - See everything  - Mesh Graph LG - MS  - Stats

+
Quick Links:  Search for a node  - Conversations - See everything  - Mesh Graph LG - MS  - Nodes - Stats

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 | format_timestamp}} ✉️ - {{packet.from_node.long_name or (packet.from_node_id | node_id_to_hex) }} - {{packet.payload}} -
+ {{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 %} + + + + + + + + + + + + + + + + + {% for node in nodes %} + + + + + + + + + + + + + {% endfor %} + +
Node IDLong NameShort NameHW ModelFirmwareRoleLast LatitudeLast LongitudeChannelLast Update
{{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" }}
+ {% 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):