orin-status: report the CAN interface (up, bus state, frames/s); --can picks it
/ 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 (with SSID)/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, the CAN interface, 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
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
//! The CAN interface the recorder reads (the PEAK on the OBD port): up or down, frames
|
||||||
|
//! per second from the sysfs counter, and the controller's bus state from `ip`. Shown so
|
||||||
|
//! a down interface is seen before a capture, not discovered as refused marks.
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
|
pub struct Can {
|
||||||
|
iface: String,
|
||||||
|
prev: Option<(Instant, u64)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Controller state as `ip -j -d link show` reports it: ERROR-ACTIVE, ERROR-WARNING,
|
||||||
|
/// ERROR-PASSIVE, BUS-OFF or STOPPED.
|
||||||
|
fn bus_state(iface: &str) -> Option<String> {
|
||||||
|
let out = Command::new("ip").args(["-j", "-d", "link", "show", iface]).output().ok()?;
|
||||||
|
let v: Value = serde_json::from_slice(&out.stdout).ok()?;
|
||||||
|
Some(v[0]["linkinfo"]["info_data"]["state"].as_str()?.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Can {
|
||||||
|
pub fn new(iface: &str) -> Self {
|
||||||
|
Can { iface: iface.to_string(), prev: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(&mut self, out: &mut Vec<(&'static str, Value)>) {
|
||||||
|
let dir = Path::new("/sys/class/net").join(&self.iface);
|
||||||
|
if !dir.exists() {
|
||||||
|
self.prev = None;
|
||||||
|
out.push(("can_up", json!(false)));
|
||||||
|
out.push(("can_bus_state", json!("absent")));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// operstate is "up" for an interface that is up, "unknown" on some CAN drivers: use the flags.
|
||||||
|
let flags = fs::read_to_string(dir.join("flags")).ok().and_then(|s| u32::from_str_radix(s.trim().trim_start_matches("0x"), 16).ok());
|
||||||
|
out.push(("can_up", json!(flags.is_some_and(|f| f & 1 != 0))));
|
||||||
|
if let Some(state) = bus_state(&self.iface) {
|
||||||
|
out.push(("can_bus_state", json!(state)));
|
||||||
|
}
|
||||||
|
let rx: Option<u64> = fs::read_to_string(dir.join("statistics/rx_packets")).ok().and_then(|s| s.trim().parse().ok());
|
||||||
|
if let Some(rx) = rx {
|
||||||
|
let now = Instant::now();
|
||||||
|
if let Some((t, p)) = self.prev.replace((now, rx)) {
|
||||||
|
let dt = now.duration_since(t).as_secs_f64();
|
||||||
|
if dt > 0.0 && rx >= p {
|
||||||
|
out.push(("can_rx_fps", json!(((rx - p) as f64 / dt).round())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
//! orin-status — the Orin's own health on the bus, once a second.
|
//! orin-status — the Orin's own health on the bus, once a second.
|
||||||
//!
|
//!
|
||||||
//! Temperatures, CPU and GPU load, the shared memory pool, module power, the fan,
|
//! Temperatures, CPU and GPU load, the shared memory pool, module power, the fan,
|
||||||
//! network links (WiFi, LTE, the USB link to the tablet) and the UPS battery, each
|
//! network links (WiFi, LTE, the USB link to the tablet), the CAN interface and the UPS battery, each
|
||||||
//! as an envelope on `yukart/host/<signal>`, retained. Names and units come from
|
//! as an envelope on `yukart/host/<signal>`, retained. Names and units come from
|
||||||
//! contracts' `signals.json` (source `host`).
|
//! contracts' `signals.json` (source `host`).
|
||||||
//!
|
//!
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
//! it stale, which is the honest picture. The service's own state goes on
|
//! it stale, which is the honest picture. The service's own state goes on
|
||||||
//! `yukart/system/orin-status`, with a last will for when it vanishes.
|
//! `yukart/system/orin-status`, with a last will for when it vanishes.
|
||||||
|
|
||||||
|
mod can;
|
||||||
mod host;
|
mod host;
|
||||||
mod net;
|
mod net;
|
||||||
mod ups;
|
mod ups;
|
||||||
@@ -25,6 +26,7 @@ use serde_json::{json, Value};
|
|||||||
const USAGE: &str = "\
|
const USAGE: &str = "\
|
||||||
usage: orin-status [options]
|
usage: orin-status [options]
|
||||||
--broker HOST:PORT MQTT broker (127.0.0.1:1883)
|
--broker HOST:PORT MQTT broker (127.0.0.1:1883)
|
||||||
|
--can IFACE CAN interface to report (can_pcan)
|
||||||
--i2c DEV I²C bus of the UPS INA219 (/dev/i2c-7)
|
--i2c DEV I²C bus of the UPS INA219 (/dev/i2c-7)
|
||||||
--ups-addr ADDR INA219 address, hex (41)
|
--ups-addr ADDR INA219 address, hex (41)
|
||||||
--once print one reading as JSON and exit (no broker)";
|
--once print one reading as JSON and exit (no broker)";
|
||||||
@@ -57,6 +59,7 @@ fn units() -> std::collections::HashMap<String, String> {
|
|||||||
struct Sources {
|
struct Sources {
|
||||||
host: host::Host,
|
host: host::Host,
|
||||||
net: net::Net,
|
net: net::Net,
|
||||||
|
can: can::Can,
|
||||||
ups: ups::Ups,
|
ups: ups::Ups,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,6 +70,7 @@ impl Sources {
|
|||||||
let mut failed = Vec::new();
|
let mut failed = Vec::new();
|
||||||
self.host.read(&mut out);
|
self.host.read(&mut out);
|
||||||
self.net.read(&mut out);
|
self.net.read(&mut out);
|
||||||
|
self.can.read(&mut out);
|
||||||
if !self.ups.read(&mut out) {
|
if !self.ups.read(&mut out) {
|
||||||
failed.push("ups");
|
failed.push("ups");
|
||||||
}
|
}
|
||||||
@@ -77,6 +81,7 @@ impl Sources {
|
|||||||
fn main() -> ExitCode {
|
fn main() -> ExitCode {
|
||||||
let mut broker = "127.0.0.1:1883".to_string();
|
let mut broker = "127.0.0.1:1883".to_string();
|
||||||
let mut i2c = "/dev/i2c-7".to_string();
|
let mut i2c = "/dev/i2c-7".to_string();
|
||||||
|
let mut can_iface = "can_pcan".to_string();
|
||||||
let mut addr: u16 = 0x41;
|
let mut addr: u16 = 0x41;
|
||||||
let mut once = false;
|
let mut once = false;
|
||||||
let mut args = std::env::args().skip(1);
|
let mut args = std::env::args().skip(1);
|
||||||
@@ -85,6 +90,7 @@ fn main() -> ExitCode {
|
|||||||
"--once" => once = true,
|
"--once" => once = true,
|
||||||
"--broker" => broker = args.next().unwrap_or_default(),
|
"--broker" => broker = args.next().unwrap_or_default(),
|
||||||
"--i2c" => i2c = args.next().unwrap_or_default(),
|
"--i2c" => i2c = args.next().unwrap_or_default(),
|
||||||
|
"--can" => can_iface = args.next().unwrap_or_default(),
|
||||||
"--ups-addr" => match args.next().and_then(|v| u16::from_str_radix(v.trim_start_matches("0x"), 16).ok()) {
|
"--ups-addr" => match args.next().and_then(|v| u16::from_str_radix(v.trim_start_matches("0x"), 16).ok()) {
|
||||||
Some(v) => addr = v,
|
Some(v) => addr = v,
|
||||||
None => {
|
None => {
|
||||||
@@ -100,7 +106,7 @@ fn main() -> ExitCode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let units = units();
|
let units = units();
|
||||||
let mut src = Sources { host: host::Host::discover(), net: net::Net::start(), ups: ups::Ups::new(&i2c, addr) };
|
let mut src = Sources { host: host::Host::discover(), net: net::Net::start(), can: can::Can::new(&can_iface), ups: ups::Ups::new(&i2c, addr) };
|
||||||
|
|
||||||
if once {
|
if once {
|
||||||
src.read(); // rates and CPU load need two readings
|
src.read(); // rates and CPU load need two readings
|
||||||
|
|||||||
Reference in New Issue
Block a user