the oneshot+timer produced 8367 journal lines a day of systemd start/stop noise. now Type=notify with an internal ticker, logging transitions only. hardware sits behind sensor.Sensor; internal/monitor is pure, so the shutdown state machine is tested without a pi. four guards, each tested, because a false poweroff of a box with no physical access is worse than a missed one: - a failed read resets the arming window rather than pausing it - voltage must actually fall across the window, else it is a stuck sensor. this is the only defence against the ac line reading 0 while mains is connected, so it is load-bearing - a settle period stops a restart loop acting on early readings - flapping ac cannot accumulate verified on hardware 2026-08-09: after poweroff on battery the x1208 starts the pi again when mains returns, with no button press. that was the risk that could have made this feature unsafe. a failed tick rewrites the previous sample with sensor_healthy 0 and the failure counter, so last_update still freezes for the staleness alert while the failure stays visible. module path moved to git.mial.net. debian units and the compose overlay deleted; deployment lives in the infra repo.
27 lines
845 B
Go
27 lines
845 B
Go
// Package sensor defines the hardware seam. Everything above this interface is
|
|
// pure and testable on any machine; everything below it touches the Pi.
|
|
package sensor
|
|
|
|
import "time"
|
|
|
|
// Reading is one complete sample. A Reading only exists if every channel was
|
|
// read successfully — there is no partial Reading, because a partial sample
|
|
// cannot be reasoned about downstream.
|
|
type Reading struct {
|
|
ACPresent bool
|
|
Volts float64
|
|
SOC float64
|
|
At time.Time
|
|
}
|
|
|
|
// Sensor reads the UPS. Read returns an error if any channel fails; callers
|
|
// must treat the Reading as invalid in that case.
|
|
//
|
|
// Implementations may hold OS handles open across calls. A Read that returns
|
|
// an error must leave the Sensor usable: the next Read reacquires whatever it
|
|
// needs.
|
|
type Sensor interface {
|
|
Read() (Reading, error)
|
|
Close() error
|
|
}
|