Skip to content

Commit

Permalink
Improve multi-gateway support
Browse files Browse the repository at this point in the history
By adding a received by section in the node details screen, and limiting the RSSI/SNR graphs to values of the selected gateway.
  • Loading branch information
jurriaan committed Jun 6, 2024
1 parent 58def56 commit 554b4df
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 23 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions src/dto/gateway_packet_info.rs
Original file line number Diff line number Diff line change
@@ -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('!', "")
}
}
2 changes: 2 additions & 0 deletions src/dto/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down
17 changes: 10 additions & 7 deletions src/template.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -40,6 +41,8 @@ pub(crate) struct PacketTemplate {
pub(crate) struct NodeDetailsTemplate {
pub node: NodeSelectResult,
pub plots: Vec<PlotData>,
pub gateway_packet_info: Vec<GatewayPacketInfo>,
pub selected_node: Option<String>,
}

fn logo() -> String {
Expand Down
70 changes: 57 additions & 13 deletions src/web_interface.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand All @@ -13,7 +12,9 @@ use crate::{
config::get_config,
demoji,
static_file::{Asset, StaticFile},
stringify, DatabaseError,
stringify,
template::into_response,
DatabaseError,
},
};
use askama::Template;
Expand Down Expand Up @@ -553,9 +554,15 @@ async fn node_positions_geojson(
Ok(([(header::CONTENT_TYPE, "application/geo+json")], res))
}

#[derive(Deserialize)]
struct NodeDetailsQueryParams {
gateway: Option<String>,
}

async fn node_details(
pool: State<SqlitePool>,
Path((node_id,)): Path<(String,)>,
query: axum::extract::Query<NodeDetailsQueryParams>,
headers: HeaderMap,
) -> axum::response::Result<impl IntoResponse> {
let node_id = i64::from_str_radix(&node_id, 16).map_err(stringify)? as i64;
Expand Down Expand Up @@ -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<GatewayPacketInfo> = 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)> {
Expand All @@ -653,11 +696,12 @@ async fn create_plots(
node_id: i64,
min_time: i64,
time_zone_offset: FixedOffset,
gateway_id: &String,
) -> axum::response::Result<Vec<PlotData>> {
let mut entries_map : HashMap<String, Vec<(DateTime<FixedOffset>, 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)
Expand All @@ -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
Expand Down
16 changes: 16 additions & 0 deletions static/application.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
16 changes: 16 additions & 0 deletions templates/_node_details.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,22 @@ <h1 style="font-weight: normal;">
{%- endif -%}
{%- endif -%}

{% if let Some(selected_node) = selected_node %}
<section class="received-by-gateway">
<h2>Received by</h2>
{% if gateway_packet_info.len() > 1 %}
<p><small>Select a gateway to see the RSSI/SNR of received packets from this node.</small></p>
{% endif %}
<ul class="gateway-selector">
{% for packet_info in gateway_packet_info %}
<li class="{% if packet_info.gateway_id|as_ref == selected_node %}selected{% endif %}" hx-get="/node/{{ node.node_id|hex }}/details.html?gateway={{packet_info.gateway_id}}" hx-target="#sidebar-details" hx-swap="innerHTML">
<span class="node-name fetch" data-node-id="{{ packet_info.raw_gateway_id() }}">{{ packet_info.gateway_id }}</span> ({{packet_info.num_packets}} packets)
</li>
{% endfor %}
</ul>
</section>
{%- endif -%}

{% for plot in plots %}
<h2>{{plot.label}}</h2>
{{ plot.svg|safe }}
Expand Down
3 changes: 2 additions & 1 deletion templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{% block body %}
<script type="text/hyperscript">
def updateNodes()
tell <.node-name.fetch/> in <.tab-content-container />
tell <.node-name.fetch/> in <aside#sidebar />
set selector to `#node-list-node-${you@data-node-id} .node-name`
get the first <${selector}/> in body then
if it exists set your innerHTML to its innerHTML
Expand Down Expand Up @@ -79,6 +79,7 @@ <h1>Messages</h1>
on htmx:afterSwap debounced at 50ms
hide #sidebar-main
show #sidebar-details
updateNodes()
">
</div>
</aside>
Expand Down

0 comments on commit 554b4df

Please sign in to comment.