thermal: watch NVMe/RP1 temps, fan stall, and undervoltage on the Pi 5

van-thermal grows sensor kinds beyond plain temperatures: "fan" alerts when
pwmfan is commanded on (pwm > 0) but reads 0 RPM (warn on first sample, crit
if it persists — 0 RPM with pwm 0 is just the firmware idling a cool SoC),
and "undervolt" goes crit on the live rpi_volt alarm plus a sticky warn off
the firmware's latched since-boot bit, so dips shorter than the 10s sample
interval still surface once. All kinds share the existing hysteresis /
journal / Pushover machinery (fan 🌀, undervolt ).

Config adds nvme Composite (65/70), rp1_adc (80/85 — the RP1 die drives all
USB/eth I/O), fan, and undervolt to the existing cpu sensor. The CSV logger
now derives per-kind columns and self-rotates when the header changes; the
Cockpit Temps card renders RPM + pwm duty and undervoltage state alongside
the temperatures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Andreas Wrede
2026-07-12 06:41:17 -04:00
co-authored by Claude Fable 5
parent 6fe683000d
commit 2da97d26f4
4 changed files with 194 additions and 36 deletions
+16 -4
View File
@@ -143,14 +143,26 @@ function renderThermal(th) {
return;
}
const pillClass = { ok: "ok", warn: "warn", crit: "bad" };
let html = `<table><thead><tr><th>Sensor</th><th>Temp</th><th>Status</th><th>Warn / Crit</th></tr></thead><tbody>`;
let html = `<table><thead><tr><th>Sensor</th><th>Value</th><th>Status</th><th>Limits</th></tr></thead><tbody>`;
Object.keys(th.sensors).forEach(name => {
const s = th.sensors[name];
const temp = s.temp == null ? "—" : `${esc(s.temp)} °C`;
let value, limits;
if (s.kind === "fan") {
value = s.rpm == null ? "—" : `${esc(s.rpm)} RPM`;
limits = s.pwm == null ? "—" : `pwm ${esc(s.pwm)}/255`;
} else if (s.kind === "undervolt") {
value = s.now == null ? "—"
: s.now ? "undervoltage"
: s.since_boot ? "dip since boot" : "ok";
limits = "—";
} else {
value = s.temp == null ? "—" : `${esc(s.temp)} °C`;
limits = `${esc(s.warn)} / ${esc(s.crit)} °C`;
}
const lvl = s.level || "ok";
html += `<tr><td>${esc(name.toUpperCase())}</td><td>${temp}</td>` +
html += `<tr><td>${esc(name.toUpperCase())}</td><td>${value}</td>` +
`<td><span class="pill ${pillClass[lvl] || ""}">${esc(lvl)}</span></td>` +
`<td class="muted">${esc(s.warn)} / ${esc(s.crit)} °C</td></tr>`;
`<td class="muted">${limits}</td></tr>`;
});
html += `</tbody></table>`;
html += `<p class="muted">History: <code>/var/log/van-thermal.csv</code> · alerts: <code>journalctl -u van-thermal</code></p>`;