orin-status: publish net_wifi_ssid from iw while WiFi is up, re-read every 10 s
/ crosscheck (push) Successful in 1s
/ crosscheck (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DDwfd6Ki8UfG4hK6W5YpJz
This commit is contained in:
@@ -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 |
|
| `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)) |
|
| `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/*`) |
|
| `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
|
```bash
|
||||||
scripts/deploy-recorder.sh # build, glibc check, install on the car Orin
|
scripts/deploy-recorder.sh # build, glibc check, install on the car Orin
|
||||||
|
|||||||
+25
-2
@@ -1,6 +1,7 @@
|
|||||||
//! Network links: WiFi, LTE and the USB link to the tablet. Up/down and rates
|
//! 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 sysfs counters; WiFi signal from /proc/net/wireless, its SSID from `iw`;
|
||||||
//! from ModemManager, polled on a thread because `mmcli` can take seconds.
|
//! the modem's own view from ModemManager, polled on a thread because `mmcli` can
|
||||||
|
//! take seconds.
|
||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@@ -52,6 +53,18 @@ fn wifi_dbm(iface: &str) -> Option<f64> {
|
|||||||
line.split_whitespace().nth(3)?.trim_end_matches('.').parse().ok()
|
line.split_whitespace().nth(3)?.trim_end_matches('.').parse().ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// SSID of the associated network, from `iw dev <iface> 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<String> {
|
||||||
|
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)]
|
#[derive(Default, Clone)]
|
||||||
struct Modem {
|
struct Modem {
|
||||||
state: String,
|
state: String,
|
||||||
@@ -80,6 +93,7 @@ fn poll_modem() -> Modem {
|
|||||||
pub struct Net {
|
pub struct Net {
|
||||||
wifi: Option<String>,
|
wifi: Option<String>,
|
||||||
wifi_rate: Rate,
|
wifi_rate: Rate,
|
||||||
|
ssid: Option<(Instant, Option<String>)>,
|
||||||
lte_rate: Rate,
|
lte_rate: Rate,
|
||||||
tablet_rate: Rate,
|
tablet_rate: Rate,
|
||||||
modem: Arc<Mutex<Option<Modem>>>,
|
modem: Arc<Mutex<Option<Modem>>>,
|
||||||
@@ -97,6 +111,7 @@ impl Net {
|
|||||||
Net {
|
Net {
|
||||||
wifi: wifi_iface(),
|
wifi: wifi_iface(),
|
||||||
wifi_rate: Rate { prev: None },
|
wifi_rate: Rate { prev: None },
|
||||||
|
ssid: None,
|
||||||
lte_rate: Rate { prev: None },
|
lte_rate: Rate { prev: None },
|
||||||
tablet_rate: Rate { prev: None },
|
tablet_rate: Rate { prev: None },
|
||||||
modem,
|
modem,
|
||||||
@@ -110,6 +125,14 @@ impl Net {
|
|||||||
if let Some(w) = self.wifi.clone() {
|
if let Some(w) = self.wifi.clone() {
|
||||||
let up = attr(&w, "operstate").as_deref() == Some("up");
|
let up = attr(&w, "operstate").as_deref() == Some("up");
|
||||||
out.push(("net_wifi_up", json!(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) {
|
if let Some(dbm) = wifi_dbm(&w).filter(|_| up) {
|
||||||
out.push(("net_wifi_signal_dbm", json!(dbm)));
|
out.push(("net_wifi_signal_dbm", json!(dbm)));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user