diff --git a/README.md b/README.md index db35484..f7d63b5 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ needed. | `recorder` | `yukart-recorder.service` | Raw CAN + GNSS to disk, in sessions, under a storage bound; OBD-II values from the bus beside them | | `bus-bench` | not installed: a test tool | Raw CAN and decoded-signal load over MQTT, measured: loss, latency, broker CPU ([report](docs/impl/reports/BUS-BENCH.md)) | | `obd-poller` | `yukart-obd-poller.service` | OBD-II mode 01 polling, the single CAN writer: rpm, speed, throttle, load, MAP, MAF ~10 Hz; temperatures and fuel 1 Hz (`yukart/vehicle/*`) | -| `orin-status` | `yukart-orin-status.service` | The Orin's health on the bus, once a second: temperatures, CPU/GPU load, memory, power, fan, WiFi/LTE/tablet links, UPS battery (`yukart/host/*`) | +| `orin-status` | `yukart-orin-status.service` | The Orin's health on the bus, once a second: temperatures, CPU/GPU load, memory, power, fan, WiFi (with SSID)/LTE/tablet links, UPS battery (`yukart/host/*`) | ```bash scripts/deploy-recorder.sh # build, glibc check, install on the car Orin diff --git a/src/orin-status/net.rs b/src/orin-status/net.rs index bdde5e2..f598641 100644 --- a/src/orin-status/net.rs +++ b/src/orin-status/net.rs @@ -1,6 +1,7 @@ //! Network links: WiFi, LTE and the USB link to the tablet. Up/down and rates -//! from sysfs counters; WiFi signal from /proc/net/wireless; the modem's own view -//! from ModemManager, polled on a thread because `mmcli` can take seconds. +//! from sysfs counters; WiFi signal from /proc/net/wireless, its SSID from `iw`; +//! the modem's own view from ModemManager, polled on a thread because `mmcli` can +//! take seconds. use std::fs; use std::path::Path; @@ -52,6 +53,18 @@ fn wifi_dbm(iface: &str) -> Option { line.split_whitespace().nth(3)?.trim_end_matches('.').parse().ok() } +/// SSID of the associated network, from `iw dev link` ("\tSSID: name"); None when +/// not associated. iw prints non-printable bytes as \xNN escapes, which are kept as they are. +fn wifi_ssid(iface: &str) -> Option { + let out = Command::new("iw").args(["dev", iface, "link"]).output().ok()?; + let text = String::from_utf8_lossy(&out.stdout); + let ssid = text.lines().find_map(|l| l.trim_start().strip_prefix("SSID: "))?; + Some(ssid.to_string()) +} + +/// How often the SSID is re-read while WiFi stays up; a change of network without a drop is rare. +const SSID_EVERY: Duration = Duration::from_secs(10); + #[derive(Default, Clone)] struct Modem { state: String, @@ -80,6 +93,7 @@ fn poll_modem() -> Modem { pub struct Net { wifi: Option, wifi_rate: Rate, + ssid: Option<(Instant, Option)>, lte_rate: Rate, tablet_rate: Rate, modem: Arc>>, @@ -97,6 +111,7 @@ impl Net { Net { wifi: wifi_iface(), wifi_rate: Rate { prev: None }, + ssid: None, lte_rate: Rate { prev: None }, tablet_rate: Rate { prev: None }, modem, @@ -110,6 +125,14 @@ impl Net { if let Some(w) = self.wifi.clone() { let up = attr(&w, "operstate").as_deref() == Some("up"); out.push(("net_wifi_up", json!(up))); + if !up { + self.ssid = None; + } else if self.ssid.as_ref().is_none_or(|(at, _)| at.elapsed() >= SSID_EVERY) { + self.ssid = Some((Instant::now(), wifi_ssid(&w))); + } + if let Some((_, Some(ssid))) = &self.ssid { + out.push(("net_wifi_ssid", json!(ssid))); + } if let Some(dbm) = wifi_dbm(&w).filter(|_| up) { out.push(("net_wifi_signal_dbm", json!(dbm))); }