-
Volkor Barbarian Warrior authoredVolkor Barbarian Warrior authored
node_list.rs 6.95 KiB
use chrono::DateTime;
use chrono_humanize::HumanTime;
use egui_extras::{Column, TableBuilder};
use figment::providers::Data;
use log::{error, info};
use longitude::{DistanceUnit, Location};
use meshtastic::protobufs::{channel::Role, HardwareModel, Position};
use crate::{db::Database, CONFIG};
// The list of nodes
// This lists all the info about each node.
// Sortable by any column, allow hiding columns?
// Nice signal strength bar (also allow clicking to view signal strength history?)
// Filters:
// - Direct Connection (direct neighbours / nodes with 0 hops away)
// - Active (seen in the last `n` minutes)
// - Inactive (not seen in the last `n` minutes)
// - Location (has/doesn't have gps location)
/// Calculates the distance betwen 2 coordinate points.
pub(crate) fn calc_distance_to_node(away: Position) -> Option<f64> {
let home_pos = Database::read_home_node().expect("Failed to find Home Node").position.unwrap();
let distance = Location {
// Home Base
latitude: home_pos.latitude_i as f64 * 1e-7,
longitude: home_pos.longitude_i as f64 * 1e-7,
}.distance(&Location {
// Away
latitude: away.latitude_i as f64 * 1e-7,
longitude: away.longitude_i as f64 * 1e-7,
});
// convert to the right distance depending on settings
match CONFIG.gui.units.as_ref() {
"Centimeters" => Some(distance.in_unit(DistanceUnit::Centimeters)),
"Meters" => Some(distance.in_unit(DistanceUnit::Meters)),
"Kilometers" => Some(distance.in_unit(DistanceUnit::Kilometers)),
"Inches" => Some(distance.in_unit(DistanceUnit::Inches)),
"Feet" => Some(distance.in_unit(DistanceUnit::Feet)),
"Yards" => Some(distance.in_unit(DistanceUnit::Yards)),
"Miles" => Some(distance.in_unit(DistanceUnit::Miles)),
_ => Some(distance.in_unit(DistanceUnit::Kilometers)), // if you can't type, force km.
}
}
/// UI Elements for the Node List
pub fn nodelist_ui(ui: &mut egui::Ui) {
// Get nodes from DB
let mut nodes = Database::read_node_list().unwrap_or_else(|e| {
error!("Failed to read nodes: {}", e);
// Return a default value or handle the error as needed
Vec::new() // Example default value, replace with appropriate handling
});
info!("Loaded {} nodes from DB", nodes.len());
// let mut nodes = Database::read_node_list().expect("Failed to read nodes");
// Sort nodes by `last_heard` in descending order
nodes.sort_by(|a, b| b.last_heard.cmp(&a.last_heard));
TableBuilder::new(ui)
// TODO for the table:
// - Clickable Table Sorting
// - Add more info
// - Auto hide/show scrollbar on smaller devices
// - Add to a panel that we can move around / shitty windowing.
.column(Column::remainder()) // node id
.column(Column::remainder().clip(true)) // node name
.column(Column::remainder()) // snr
.column(Column::remainder()) // dist
.column(Column::remainder()) // hops
.column(Column::remainder().clip(true)) // hw model
.column(Column::remainder()) // role
.column(Column::remainder()) // battery
.column(Column::remainder().clip(true)) // last heard
.striped(true)
.header(15.0, |mut header| {
header.col(|ui| {
ui.heading("Node ID");
});
header.col(|ui| {
ui.heading("Name");
});
header.col(|ui| {
ui.heading("SNR");
});
header.col(|ui| {
// Get distance units
let unit = match CONFIG.gui.units.as_ref() {
"Centimeters" => "cm",
"Meters" => "m",
"Kilometers" => "km",
"Inches" => "in",
"Feet" => "ft",
"Yards" => "yd",
"Miles" => "mi",
_ => "km", // Default to km if no match found.
};
ui.heading(format!("Distance ({})", unit));
});
header.col(|ui| {
ui.heading("Hops Away");
});
header.col(|ui| {
ui.heading("HW Model");
});
header.col(|ui| {
ui.heading("Role");
});
header.col(|ui| {
ui.heading("Battery");
});
header.col(|ui| {
ui.heading("Last Heard");
});
})
.body(|mut body| {
for node in nodes {
let user = node.user.unwrap();
let device = node.device_metrics.unwrap();
// Convert the last heard to a time, then make it nice to read.
let now = chrono::Local::now().to_utc();
let humanised = if node.last_heard == 0 {
String::from("Unknown")
} else {
let last_heard = DateTime::from_timestamp(node.last_heard.into(), 0).unwrap();
let duration = last_heard - now;
HumanTime::from(duration).to_string()
};
body.row(15.0, |mut row| {
row.col(|ui| {
ui.label(user.id);
});
row.col(|ui| {
ui.label(format!("{} ({})", user.long_name, user.short_name));
});
row.col(|ui| {
ui.label(node.snr.to_string());
});
row.col(|ui| {
if let Some(distance) = calc_distance_to_node(node.position.unwrap()) {
if distance.is_nan() {
ui.label("-");
} else {
ui.label(format!("{:.2}", distance));
}
} else {
ui.label("-");
}
});
row.col(|ui| {
ui.label(node.hops_away.to_string());
});
row.col(|ui| {
// Convert the i32 to a HardwareModel object, then convert to string
ui.label(HardwareModel::from_i32(user.hw_model).unwrap_or_default().as_str_name());
});
row.col(|ui| {
// Convert the i32 to a Role object, convert to string.
ui.label(Role::from_i32(user.role).unwrap_or_default().as_str_name());
});
row.col(|ui| {
ui.label(device.battery_level.to_string());
});
row.col(|ui| {
ui.label(humanised);
});
});
}
});
}