diff --git a/Cargo.lock b/Cargo.lock index da74446..1d7cc87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3111,9 +3111,9 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" -version = "0.6.11" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c52728401e1dc672a56e81e593e912aa54c78f40246869f78359a2bf24d29d" +checksum = "41ff33f391015ecab21cd092389215eb265ef9496a9a07b6bee7d3529831deda" dependencies = [ "memchr", ] diff --git a/src/dto/gateway_packet_info.rs b/src/dto/gateway_packet_info.rs new file mode 100644 index 0000000..67da720 --- /dev/null +++ b/src/dto/gateway_packet_info.rs @@ -0,0 +1,13 @@ +use sqlx::FromRow; + +#[derive(Clone, Debug, FromRow)] +pub struct GatewayPacketInfo { + pub gateway_id: String, + pub num_packets: i64, +} + +impl GatewayPacketInfo { + pub fn raw_gateway_id(&self) -> String { + self.gateway_id.replace('!', "") + } +} diff --git a/src/dto/mod.rs b/src/dto/mod.rs index 1df9834..d7fa578 100644 --- a/src/dto/mod.rs +++ b/src/dto/mod.rs @@ -1,5 +1,6 @@ pub mod device_metrics_select_result; pub mod environment_metrics_select_result; +pub mod gateway_packet_info; pub mod mesh_packet; pub mod neighbor_select_result; pub mod node_select_result; @@ -15,6 +16,7 @@ pub mod waypoint_select_result; pub use device_metrics_select_result::DeviceMetricsSelectResult; pub use environment_metrics_select_result::EnvironmentMetricsSelectResult; +pub use gateway_packet_info::GatewayPacketInfo; pub use mesh_packet::MeshPacket; pub use neighbor_select_result::NeighborSelectResult; pub use node_select_result::NodeSelectResult; diff --git a/src/template.rs b/src/template.rs index 833b0c1..22536b2 100644 --- a/src/template.rs +++ b/src/template.rs @@ -1,10 +1,11 @@ -use crate::dto::mesh_packet::MeshPacket as MeshPacketDto; -use crate::dto::mesh_packet::Payload; -use crate::dto::NodeSelectResult; -use crate::dto::PlotData; -use crate::dto::StatsSelectResult; -use crate::proto::meshtastic::config::device_config::Role; -use crate::util::capitalize; +use crate::{ + dto::{ + mesh_packet::{MeshPacket as MeshPacketDto, Payload}, + GatewayPacketInfo, NodeSelectResult, PlotData, StatsSelectResult, + }, + proto::meshtastic::config::device_config::Role, + util::capitalize, +}; use askama::Template; use chrono::prelude::*; use chrono::TimeDelta; @@ -40,6 +41,8 @@ pub(crate) struct PacketTemplate { pub(crate) struct NodeDetailsTemplate { pub node: NodeSelectResult, pub plots: Vec, + pub gateway_packet_info: Vec, + pub selected_node: Option, } fn logo() -> String { diff --git a/src/web_interface.rs b/src/web_interface.rs index 6ffd72f..ea56d8e 100644 --- a/src/web_interface.rs +++ b/src/web_interface.rs @@ -1,9 +1,8 @@ -use crate::util::template::into_response; use crate::{ dto::{ mesh_packet::Payload, DeviceMetricsSelectResult, EnvironmentMetricsSelectResult, - MeshPacket as MeshPacketDto, NeighborSelectResult, NodeSelectResult, PlotData, - PositionSelectResult, PowerMetricsSelectResult, RoutingDto, StatsSelectResult, + GatewayPacketInfo, MeshPacket as MeshPacketDto, NeighborSelectResult, NodeSelectResult, + PlotData, PositionSelectResult, PowerMetricsSelectResult, RoutingDto, StatsSelectResult, TracerouteDto, WaypointSelectResult, }, proto::meshtastic::{routing, PortNum, RouteDiscovery, Routing}, @@ -13,7 +12,9 @@ use crate::{ config::get_config, demoji, static_file::{Asset, StaticFile}, - stringify, DatabaseError, + stringify, + template::into_response, + DatabaseError, }, }; use askama::Template; @@ -553,9 +554,15 @@ async fn node_positions_geojson( Ok(([(header::CONTENT_TYPE, "application/geo+json")], res)) } +#[derive(Deserialize)] +struct NodeDetailsQueryParams { + gateway: Option, +} + async fn node_details( pool: State, Path((node_id,)): Path<(String,)>, + query: axum::extract::Query, headers: HeaderMap, ) -> axum::response::Result { let node_id = i64::from_str_radix(&node_id, 16).map_err(stringify)? as i64; @@ -624,12 +631,48 @@ async fn node_details( FixedOffset::west_opt(0).unwrap() }; - Ok(into_response(&NodeDetailsTemplate { - node, - plots: create_plots(pool, node_id, min_time_nanos, offset) - .await - .unwrap_or_default(), - })) + let gateway_packet_info: Vec = sqlx::query_as!( + GatewayPacketInfo, + r#" + SELECT gateway_id as "gateway_id!", COUNT(*) as num_packets FROM mesh_packets WHERE from_id = ?1 AND rx_time IS NOT NULL AND rx_time > ?2 AND gateway_id IS NOT NULL GROUP BY 1 ORDER BY num_packets DESC LIMIT 50 + "#, + node_id, min_time_nanos + ) + .fetch_all(&*pool) + .await + .map_err(DatabaseError)?; + + let max_gateway = gateway_packet_info.first(); + + if let Some(max_gateway) = max_gateway { + // A bit too complicated way to find the gateway in the list.. + let selected_node = query + .gateway + .as_ref() + .and_then(|selected| { + gateway_packet_info + .iter() + .find(|g| &g.gateway_id == selected) + .map(|g| g.gateway_id.clone()) + }) + .unwrap_or(max_gateway.gateway_id.clone()); + + Ok(into_response(&NodeDetailsTemplate { + node, + plots: create_plots(pool, node_id, min_time_nanos, offset, &selected_node) + .await + .unwrap_or_default(), + gateway_packet_info, + selected_node: Some(selected_node), + })) + } else { + Ok(into_response(&NodeDetailsTemplate { + node, + plots: Vec::new(), + gateway_packet_info, + selected_node: None, + })) + } } fn plot_labels() -> &'static Vec<(&'static str, &'static str)> { @@ -653,11 +696,12 @@ async fn create_plots( node_id: i64, min_time: i64, time_zone_offset: FixedOffset, + gateway_id: &String, ) -> axum::response::Result> { let mut entries_map : HashMap, f64)>>= sqlx::query!( r#" - SELECT * FROM (SELECT 'R' as plot_type, rx_time as "time!", CAST(rx_rssi AS REAL) AS "value!" FROM mesh_packets WHERE from_id = ?1 AND rx_time IS NOT NULL AND rx_time > ?2 AND rx_rssi != 0 ORDER BY "time!" DESC LIMIT 100) - UNION ALL SELECT * FROM (SELECT 'S' as plot_type, rx_time as "time!", rx_snr AS "value!" FROM mesh_packets WHERE from_id = ?1 AND rx_time IS NOT NULL AND rx_time > ?2 AND rx_snr != 0 ORDER BY "time!" DESC LIMIT 100) + SELECT * FROM (SELECT 'R' as plot_type, rx_time as "time!", CAST(rx_rssi AS REAL) AS "value!" FROM mesh_packets WHERE from_id = ?1 AND rx_time IS NOT NULL AND rx_time > ?2 AND rx_rssi != 0 AND gateway_id = ?3 ORDER BY "time!" DESC LIMIT 100) + UNION ALL SELECT * FROM (SELECT 'S' as plot_type, rx_time as "time!", rx_snr AS "value!" FROM mesh_packets WHERE from_id = ?1 AND rx_time IS NOT NULL AND rx_time > ?2 AND rx_snr != 0 AND gateway_id = ?3 ORDER BY "time!" DESC LIMIT 100) UNION ALL SELECT * FROM (SELECT 'C' as plot_type, time as "time!", channel_utilization AS "value!" FROM device_metrics WHERE node_id = ?1 AND time IS NOT NULL AND time > ?2 AND channel_utilization > 0 ORDER BY "time!" DESC LIMIT 100) UNION ALL SELECT * FROM (SELECT 'A' as plot_type, time as "time!", air_util_tx AS "value!" FROM device_metrics WHERE node_id = ?1 AND time IS NOT NULL AND time > ?2 AND air_util_tx > 0 ORDER BY "time!" DESC LIMIT 100) UNION ALL SELECT * FROM (SELECT 'V' as plot_type, time as "time!", voltage AS "value!" FROM device_metrics WHERE node_id = ?1 AND time IS NOT NULL AND time > ?2 AND voltage > 0 ORDER BY "time!" DESC LIMIT 100) @@ -666,7 +710,7 @@ async fn create_plots( UNION ALL SELECT * FROM (SELECT 'B' as plot_type, time as "time!", barometric_pressure AS "value!" FROM environment_metrics WHERE node_id = ?1 AND time IS NOT NULL AND time > ?2 AND barometric_pressure > 0 ORDER BY "time!" DESC LIMIT 100) ORDER BY plot_type, "time!" DESC; "#, - node_id, min_time + node_id, min_time, gateway_id ) .fetch_all(&*pool) .await diff --git a/static/application.css b/static/application.css index 3936133..51287ff 100644 --- a/static/application.css +++ b/static/application.css @@ -349,4 +349,20 @@ button.about { .packet-list li { padding: .4em .5em; } +} + +ul.gateway-selector { + cursor: pointer; +} + +ul.gateway-selector li.selected { + text-decoration: underline; +} + +.received-by-gateway h2 { + margin-bottom: 0; +} + +.received-by-gateway p { + margin: 0; } \ No newline at end of file diff --git a/templates/_node_details.html b/templates/_node_details.html index 4cb1a3c..bd6567a 100644 --- a/templates/_node_details.html +++ b/templates/_node_details.html @@ -27,6 +27,22 @@

{%- endif -%} {%- endif -%} +{% if let Some(selected_node) = selected_node %} +
+

Received by

+{% if gateway_packet_info.len() > 1 %} +

Select a gateway to see the RSSI/SNR of received packets from this node.

+{% endif %} +
    + {% for packet_info in gateway_packet_info %} +
  • + {{ packet_info.gateway_id }} ({{packet_info.num_packets}} packets) +
  • + {% endfor %} +
+
+{%- endif -%} + {% for plot in plots %}

{{plot.label}}

{{ plot.svg|safe }} diff --git a/templates/index.html b/templates/index.html index d12a2db..474a8c2 100644 --- a/templates/index.html +++ b/templates/index.html @@ -2,7 +2,7 @@ {% block body %}