diff --git a/Cargo.lock b/Cargo.lock
index 9dcb6883a8bdfb93df357a2659d1a04eb01b7d59..4fa384863d98fdaffe027264f6fbd1a632fd7555 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -186,6 +186,12 @@ version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
 
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
 [[package]]
 name = "android_system_properties"
 version = "0.1.5"
@@ -832,6 +838,29 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "chrono"
+version = "0.4.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "wasm-bindgen",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "chrono-humanize"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "799627e6b4d27827a814e837b9d8a504832086081806d45b1afa34dc982b023b"
+dependencies = [
+ "chrono",
+]
+
 [[package]]
 name = "clipboard-win"
 version = "5.4.0"
@@ -2026,12 +2055,6 @@ version = "1.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
 
-[[package]]
-name = "humantime"
-version = "2.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
-
 [[package]]
 name = "hyper"
 version = "0.14.32"
@@ -2070,6 +2093,29 @@ dependencies = [
  "tokio-rustls",
 ]
 
+[[package]]
+name = "iana-time-zone"
+version = "0.1.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "windows-core 0.52.0",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
 [[package]]
 name = "icu_collections"
 version = "1.5.0"
@@ -2550,6 +2596,16 @@ version = "0.4.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
 
+[[package]]
+name = "longitude"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "287771e9700a56b8ee1c12188e67e551daf66980b9fde697e9551d74db19cc0e"
+dependencies = [
+ "lazy_static",
+ "libm",
+]
+
 [[package]]
 name = "loop9"
 version = "0.1.5"
@@ -5493,6 +5549,15 @@ dependencies = [
  "windows-targets 0.53.0",
 ]
 
+[[package]]
+name = "windows-core"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
 [[package]]
 name = "windows-core"
 version = "0.57.0"
@@ -6094,15 +6159,17 @@ name = "yamm"
 version = "0.1.0"
 dependencies = [
  "bincode",
+ "chrono",
+ "chrono-humanize",
  "eframe",
  "egui",
  "egui_extras",
  "fern",
  "figment",
- "humantime",
  "image",
  "lazy_static",
  "log",
+ "longitude",
  "meshtastic",
  "rusqlite",
  "rusqlite_migration",
diff --git a/Cargo.toml b/Cargo.toml
index 185eca251a1f5ca36ef9f815de9d4b7209814fb9..54b538ce9fcf20b2587f0a1b71ec4b985fa3af66 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,7 +5,6 @@ edition = "2021"
 
 [dependencies]
 fern = "0.7.1"
-humantime = "2.1.0"
 log = "0.4.22"
 meshtastic = "0.1.6"
 tokio = "1.42.0"
@@ -17,10 +16,13 @@ lazy_static = "1.5.0"
 figment =  {version = "0.10.19", features = ["toml", "env"]}
 serde = "1.0.217"
 bincode = "1.3.3"
+chrono-humanize = "0.2.3"
 
 # gui
 eframe = { version = "0.30.0", features = ["wgpu"] }
 egui = "0.30.0"
 walkers = "0.33.0"
 egui_extras = { version = "0.30.0", features = ["all_loaders"] }
-image = { version = "0.25", features = ["jpeg", "png", "webp"] }
\ No newline at end of file
+image = { version = "0.25", features = ["jpeg", "png", "webp"] }
+chrono = "0.4.39"
+longitude = "0.2.1"
diff --git a/README.md b/README.md
index e59b673450313347ac402aaf2242826e0c318f5d..5faa14cfdccc4433f5d9001a1f87b81ef6daf601 100644
--- a/README.md
+++ b/README.md
@@ -30,6 +30,9 @@ Rough order of how much I want to add support.
 
 - [ ] Shows Historical Data as a nice graph
   - [ ] SNR (both individual neighbours and overall)
+    - RSSI is done per packet, instead of in nodeinfo. this seems... clunky.
+    - We're gonna have to log packets coming directly from neighbours, and store the rssi. it seems like that might be the only way to actually get neighbours?
+    - potentially show helpful guides on why each node is bad quality. (https://sensing-labs.com/f-a-q/a-good-radio-level/) ?
   - [ ] Messages per day?
   - [ ] Number of Nodes seen
   - [ ] Channel Util
@@ -41,6 +44,7 @@ Rough order of how much I want to add support.
   - [ ] Can we show the potential nodes that don't have GPS?
   - [ ] Can we use heard traceroutes to map out neighbours in the mesh?
     - Can we send out some traceroutes every few hours to map it if we don't? (some people don't like meshsense for this reason)
+- [ ] calculate distance to 'collector node'
 - [ ] Generates a nice public dashboard (static http, once every hour?)
 - Supports anything other than TCP Connection.
   - [ ] Bluetooth (how? wtf? i hate bluetooth!)
diff --git a/config.toml b/config.toml
index 5f3fd30bdad01fc5032667ee95cce755e29627e6..f9a5b735e53ecca5066c3d4966933e6bf62d3c70 100644
--- a/config.toml
+++ b/config.toml
@@ -17,4 +17,10 @@ serial_port = "Set this from whatever 'available ports' says."
 
 ## If connection_type is TCP or MQTT, use this ip & port combo to connect
 ip = "192.168.1.11"
-port = 4403
\ No newline at end of file
+port = 4403
+
+
+[gui]
+# Should we show units in metric, or idiot american?
+# Supported: Centimeters, Meters, Kilometers, Inches, Feet, Yards, Miles,
+units = "Kilometers"
\ No newline at end of file
diff --git a/db/migrations/0001_initial.sql b/db/migrations/0001_initial.sql
index 468616ba348d01bebd383542a8eea7416bb2ebe5..cb045e81b60c7a72ef5a6b2dfa16c96d56acbcef 100644
--- a/db/migrations/0001_initial.sql
+++ b/db/migrations/0001_initial.sql
@@ -342,4 +342,33 @@ CREATE TABLE DetectionSensorConfig (
 CREATE TABLE PaxcounterConfig (
     enabled INTEGER,
     paxcounter_update_interval INTEGER
+);
+
+-- MeshPacket tables
+-- This table stores every single MeshPacket sent over the radio.
+-- Since this can contain a Payload_variant, we need to handle all of the following types
+-- https://docs.rs/meshtastic/latest/meshtastic/protobufs/enum.PortNum.html
+CREATE TABLE MeshPacket (
+    primary_id INTEGER PRIMARY KEY AUTOINCREMENT,   -- internal db packet number
+    packet_from INTEGER,                            -- sending node number
+    packet_to INTEGER,                              -- the (immediate) destination
+    channel INTEGER,                                -- what channel was this sent on, however it's a device specific set
+    id INTEGER,                                     -- 'unique' id for the packet, only unique per sender, and for ~10 mins
+    rx_time INTEGER,                                -- unixtime packet was received, not sent over radio
+    rx_snr INTEGER,                                 -- radio quality @ time of rx, not sent over radio
+    hop_limt INTEGER,                               -- n hops allowed, 0 is direct neighbours only
+    want_ack INTEGER,                               -- does the sender want ack on arrival of destination?
+    priority INTEGER,                               -- how important it is in the network
+    rx_rssi INTEGER,                                -- rssi of packet, not sent over radio
+    via_mqtt INTEGER,                               -- did it pass over mqtt at any point?
+    hop_start INTEGER,                              -- what was the hop limit at tx?
+    pv_status INTEGER,                              -- NULL = No payload, 1 = decoded, 2 = encrypted
+    pv_portnum INTEGER,                             -- what type of data we're sending
+    pv_data BLOB,                                   -- the data we're sending.
+    pv_want_response INTEGER,                       -- ask the reipient to respond
+    pv_dest INTEGER,                                -- the addr of the destination
+    pv_source INTEGER,                              -- the original sender node, only used for reliable multihop pkt
+    pv_request_id INTEGER,                          -- used for routing or responses, original msg id
+    pv_reply_id INTEGER,                            -- is the payload a reply to a previous message?
+    pv_emoji INTEGER                                -- is the payload an emoji?
 );
\ No newline at end of file
diff --git a/src/config.rs b/src/config.rs
index 5edec01335ab8de88ae8f27c5a792aa5aba734b3..1ec16916530e651dca3a66119aae1288234f6f31 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,3 +1,4 @@
+use longitude::DistanceUnit;
 use serde::Deserialize;
 
 /// Holds the individual config segments.
@@ -7,6 +8,7 @@ pub struct AppConfig {
     pub database: DatabaseConfig,
     pub logging: LoggingConfig,
     pub meshconfig: MeshConfig,
+    pub gui: GuiConfig,
 }
 
 #[derive(Debug, Clone, Deserialize)]
@@ -46,6 +48,12 @@ pub enum ConnectionType {
 }
 
 
+/// Holds the config for the GUI
+#[derive(Debug, Clone, Deserialize)]
+pub struct GuiConfig {
+    pub units: String
+}
+
 // Defaults
 
 impl Default for ServerConfig {
@@ -82,4 +90,12 @@ impl Default for MeshConfig {
             port: 4403,
         }
     }
-}
\ No newline at end of file
+}
+
+impl Default for GuiConfig {
+    fn default() -> Self {
+        Self {
+            units: "Kilometers".to_string()
+        }
+    }
+}
diff --git a/src/db.rs b/src/db.rs
index 4851ec06b734d2972880a87dc60db6f4bac59269..0acdbed01a6f8a07f544c551d9842eb47e757531 100644
--- a/src/db.rs
+++ b/src/db.rs
@@ -9,7 +9,7 @@ use rusqlite_migration::{Migrations, M};
 use serde::{Deserialize, Serialize};
 use tracing::{debug, error, info};
 use lazy_static::lazy_static;
-use meshtastic::protobufs::{Channel, DeviceMetrics, MyNodeInfo, NodeInfo, Position, User};
+use meshtastic::protobufs::{Channel, Data, DeviceMetrics, MeshPacket, MyNodeInfo, NodeInfo, Position, User};
 
 use crate::CONFIG;
 
@@ -828,4 +828,175 @@ impl Database {
         nodes
     }
 
+    pub fn read_home_node() -> Result<NodeInfo, rusqlite::Error> {
+        let db = Database::open().expect("Failure to open database for reading");
+
+        let mut stmt = db.conn.prepare("SELECT num, user_id, user_lname, user_sname, 
+                                        hw_model, is_licensed, role, battery_level, 
+                                        voltage, latitude_i, longitude_i, altitude, 
+                                        location_source, gps_timestamp, altitude_source, 
+                                        pdop, hdop, vdop, gps_accuracy, ground_speed, 
+                                        ground_track, fix_quality, fix_type, sats_in_view, 
+                                        sensor_id, next_update, seq_number, precision_bits, 
+                                        snr, last_heard, channel_utilization, air_util_tx, 
+                                        channel, via_mqtt, hops_away 
+                                        FROM Node
+                                        WHERE num = (SELECT my_node_num FROM MyNodeInfo)")?;
+
+        
+
+        // If needed, handle the result using pattern matching or further processing
+        stmt.query_row([], |row| {
+            Ok(NodeInfo {
+                num: row.get(0)?,
+                user: Some(User {
+                    id: row.get(1).unwrap_or("NULL".to_owned()),
+                    long_name: row.get(2).unwrap_or("NULL".to_owned()),
+                    short_name: row.get(3).unwrap_or("NULL".to_owned()),
+                    macaddr: vec![],
+                    hw_model: row.get(4).unwrap_or_default(),
+                    is_licensed: row.get(5).unwrap_or(false),
+                    role: row.get(6).unwrap_or_default(),
+                }),
+                position: Some(Position {
+                    latitude_i: row.get(9).unwrap_or(0),
+                    longitude_i: row.get(10).unwrap_or(0),
+                    altitude: row.get(11).unwrap_or(0),
+                    time: 0,
+                    location_source: row.get(12).unwrap_or_default(),
+                    altitude_source: row.get(14).unwrap_or_default(),
+                    timestamp: row.get(13).unwrap_or(0),
+                    timestamp_millis_adjust: 0,
+                    altitude_hae: 0,
+                    altitude_geoidal_separation: 0,
+                    pdop: row.get(15).unwrap_or(0),
+                    hdop: row.get(16).unwrap_or(0),
+                    vdop: row.get(17).unwrap_or(0),
+                    gps_accuracy: row.get(18).unwrap_or(0),
+                    ground_speed: row.get(19).unwrap_or(0),
+                    ground_track: row.get(20).unwrap_or(0),
+                    fix_quality: row.get(21).unwrap_or(0),
+                    fix_type: row.get(22).unwrap_or(0),
+                    sats_in_view: row.get(23).unwrap_or(0),
+                    sensor_id: row.get(24).unwrap_or(0),
+                    next_update: row.get(25).unwrap_or(0),
+                    seq_number: row.get(26).unwrap_or(0),
+                    precision_bits: row.get(27).unwrap_or(0),
+                }),
+                snr: row.get(28)?,
+                last_heard: row.get(29)?,
+                device_metrics: Some(DeviceMetrics {
+                    battery_level: row.get(7).unwrap_or(101),
+                    voltage: row.get(8).unwrap_or(0.0),
+                    channel_utilization: row.get(30).unwrap_or(0.0),
+                    air_util_tx: row.get(31).unwrap_or(0.0),
+                }),
+                channel: row.get(32)?,
+                via_mqtt: row.get(33)?,
+                hops_away: row.get(34)?,
+            })
+        })
+    }
+
+    /// Check for the existence of a MeshPacket with the same data
+    pub fn check_meshpacket_dupe(pv_data: &Option<Vec<u8>>) -> Result<bool> {
+        let db = Database::open().expect("Failure to open database");
+        let mut stmt = db.conn.prepare("SELECT COUNT(1)
+                                        FROM MeshPacket
+                                        WHERE data = ?1")?;
+        let exists = stmt.query_row([&pv_data], |row| row.get::<_, i64>(0))? > 0;
+        Ok(exists)
+    }
+
+    /// Add a new meshpacket entry into the db.
+    pub fn add_meshpacket(packet: &MeshPacket) -> Result<bool> {
+        let db = Database::open().expect("Failure to open database");
+
+        // Initialise default pv's
+        let mut pv_status: Option<i32> = None;
+        let mut pv_portnum: Option<i32> = None;
+        let mut pv_data: Option<Vec<u8>> = None;
+        let mut pv_want_response: Option<bool> = None;
+        let mut pv_dest: Option<u32> = None;
+        let mut pv_source: Option<u32> = None;
+        let mut pv_request_id: Option<u32> = None;
+        let mut pv_reply_id: Option<u32> = None;
+        let mut pv_emoji: Option<u32> = None;
+
+        // Can we handle the payload variant? is it empty? encrypted? decoded?
+        if packet.payload_variant.is_none() {
+            // if None, use defaults we set above!
+        }
+
+        if packet.payload_variant.is_some() {
+            match packet.payload_variant.clone().unwrap() {
+                meshtastic::protobufs::mesh_packet::PayloadVariant::Decoded(data) => {
+                    // decoded packet, update pv values
+                    pv_status = Some(1);
+                    pv_portnum = Some(data.portnum);
+                    pv_data = Some(data.payload);
+                    pv_want_response = Some(data.want_response);
+                    pv_dest = Some(data.dest);
+                    pv_source = Some(data.source);
+                    pv_request_id = Some(data.request_id);
+                    pv_reply_id = Some(data.reply_id);
+                    pv_emoji = Some(data.emoji);
+                },
+                meshtastic::protobufs::mesh_packet::PayloadVariant::Encrypted(items) => {
+                    // encrypted packet, update some pv values
+                    pv_status = Some(2);
+                    pv_data = Some(items);
+                },
+            }
+        }
+
+        // WAIT! hold on! have we heard this packet before?
+        // Unfortunately there's no "original sender" thing that I can see, so fuck it
+        // check the db for duplicate pv_data's
+        if Self::check_meshpacket_dupe(&pv_data).unwrap_or(false) {
+            // Packet is a duplicate, lets skip it!
+            debug!("Skipping duplicate MeshPacket {}", packet.from);
+            Ok(false)
+        } else {
+            // Add MeshPacket
+            db.conn.execute(
+                "INSERT INTO MeshPacket (
+                    packet_from, packet_to, channel, id, 
+                    rx_time, rx_snr, hop_limt, want_ack, 
+                    priority, rx_rssi, via_mqtt, hop_start, 
+                    pv_status, pv_portnum, pv_data, pv_want_response, 
+                    pv_dest, pv_source, pv_request_id, pv_reply_id, 
+                    pv_emoji             
+                ) VALUES (
+                    ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, 
+                    ?15, ?16, ?17, ?18, ?19, ?20, ?21
+                )",
+                rusqlite::params![
+                    packet.from,
+                    packet.to,          // 4294967295 = 0xFFFFFFFF = Everyone / Unknown
+                    packet.channel,
+                    packet.id,
+                    packet.rx_time,
+                    packet.rx_snr,
+                    packet.hop_limit,
+                    packet.want_ack,
+                    packet.priority,
+                    packet.rx_rssi,
+                    packet.via_mqtt,
+                    packet.hop_start,
+                    pv_status,
+                    pv_portnum,
+                    pv_data,
+                    pv_want_response,
+                    pv_dest,
+                    pv_source,
+                    pv_request_id,
+                    pv_reply_id,
+                    pv_emoji
+                ]
+            )?;
+            debug!("Added new MeshPacket from {}", packet.from);
+            Ok(true)
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/gui.rs b/src/gui.rs
index a19f7d2c9ed730f38bae9e2d50b6e83ce348551b..38824fca44e2028ea274f534be0abce9b8eac3aa 100644
--- a/src/gui.rs
+++ b/src/gui.rs
@@ -1,7 +1,40 @@
+use std::str::FromStr;
+
+use chrono::DateTime;
 use egui_extras::{Column, TableBuilder};
+use meshtastic::protobufs::{config::device_config::Role, HardwareModel};
 use tracing::debug;
+use chrono_humanize::{self, HumanTime};
+use longitude::{Distance, DistanceUnit, Location};
+use meshtastic::protobufs::Position;
+
+use crate::{db::Database, CONFIG};
 
-use crate::db::Database;
+/// Calculates the distance betwen 2 coordinate points.
+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.
+    }
+}
 
 pub(crate) async fn run_gui() {
     debug!("Running in GUI Mode!");
@@ -14,16 +47,28 @@ pub(crate) async fn run_gui() {
             // Table Panel, showing nodes in a list.
             egui::CentralPanel::default().show(ctx, |ui| {
                 // Get nodes from DB
-                let nodes = Database::read_node_list().expect("Failed to read nodes");
+                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)
-                    .column(Column::auto().resizable(true))
-                    .column(Column::remainder())
-                    .column(Column::remainder())
-                    .column(Column::remainder())
-                    .column(Column::remainder())
-                    .column(Column::remainder())
-                    .column(Column::remainder())
-                    .header(20.0, |mut header| {
+                // 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");
                         });
@@ -33,6 +78,22 @@ pub(crate) async fn run_gui() {
                         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");
                         });
@@ -43,14 +104,27 @@ pub(crate) async fn run_gui() {
                             ui.heading("Role");
                         });
                         header.col(|ui| {
-                            ui.heading("Battery Level");
+                            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();
-                            body.row(10.0, |mut row| {
+                            // 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);
                                 });
@@ -60,18 +134,34 @@ pub(crate) async fn run_gui() {
                                 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| {
-                                    ui.label(user.hw_model.to_string());
+                                    // 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| {
-                                    ui.label(user.role.to_string());
+                                    // 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);
+                                });
                             });
                         }
                     });
diff --git a/src/packets.rs b/src/packets.rs
index a12c4b4662acf0916fea0edf8d36f593408bd412..d5fd3a0c1f3b51ed9530ea6b56a5e209b1bc18ed 100644
--- a/src/packets.rs
+++ b/src/packets.rs
@@ -2,7 +2,8 @@ use std::path::Path;
 
 use config::device_config;
 use from_radio::PayloadVariant;
-use meshtastic::protobufs::*;
+use log::info;
+use meshtastic::protobufs::{config::device_config::Role, *};
 use tracing::{debug, error};
 
 use crate::db::Database;
@@ -15,8 +16,8 @@ pub fn parse(message: &FromRadio) -> bool {
     match message.payload_variant {
         // A normal message
         Some(PayloadVariant::Packet(ref packet)) => {
-            debug!("New Message Packet!");
-            parse_packet(packet);
+            debug!("New MeshPacket!");
+            parse_meshpacket(packet);
         },
         // Information about the local node
         Some(PayloadVariant::MyInfo(ref info)) => {
@@ -59,14 +60,19 @@ pub fn parse(message: &FromRadio) -> bool {
     true
 }
 
-fn parse_packet(packet: &MeshPacket) {
+fn parse_meshpacket(packet: &MeshPacket) {
     // Handle packet data
-    error!("Unhandled MeshPacket Packet: {:#?}", &packet);
-    
+    debug!("Parsing MeshPacket Packet: {:?}", &packet);
+    let _ = Database::add_meshpacket(packet);
+
+    // I don't think we can, but imagine if meshtastic added the "last hop node"
+    // so we can figure out at least who im actively receiving from, and figure out their
+    // signal strength without brute forcing and sending/waiting for other packets.
+    // because I'd love to be able to update 0 hop nodes snr/rssi from here, but I have to let it g
 }
 
 fn parse_myinfo(packet: &MyNodeInfo) {
-    debug!("Parsing MyNodeInfo Packet: {:#?}", &packet);
+    debug!("Parsing MyNodeInfo Packet: {:?}", &packet);
     let _ = Database::update_mynodeinfo(packet);
 }
 
@@ -82,7 +88,7 @@ fn parse_nodeinfo(node_info: &NodeInfo) {
 
 fn parse_config(config: &Config) {
     // Handle Config data
-    debug!("Parsing Config Packet: {:#?}", &config);
+    debug!("Parsing Config Packet: {:?}", &config);
     
     // Match and destructure the payload_variant
     if let Some(payload_variant) = config.payload_variant.clone() {
@@ -113,7 +119,7 @@ fn parse_config(config: &Config) {
 }
 
 fn parse_module_config(module_config: &ModuleConfig) {
-    debug!("Parsing Module Packet: {:#?}", &module_config);
+    debug!("Parsing Module Packet: {:?}", &module_config);
     // Handle ModuleConfig data
     // Match and destructure the payload_variant
     if let Some(payload_variant) = module_config.payload_variant.clone() {
@@ -162,7 +168,7 @@ fn parse_module_config(module_config: &ModuleConfig) {
 }
 
 fn parse_channel_info(channel_info: &Channel) {
-    debug!("Parsing Channel Packet: {:#?}", &channel_info);
+    debug!("Parsing Channel Packet: {:?}", &channel_info);
     // Handle ChannelInfo data
     let _ = Database::update_channel(channel_info);
 }
diff --git a/yamm.db b/yamm.db
index ba450ab0817c9ed872df41b1eae021433714cba9..f77f5dcf23bda7fb3a2dd839ba13f9be42b95f22 100644
Binary files a/yamm.db and b/yamm.db differ