Rust daemon for monitoring Waveshare UPS HAT (E) on Linux (Raspberry Pi) and triggering safe shutdown logic on power loss or low battery voltage.
- Native Rust implementation (no C++/CMake)
- I2C communication with UPS HAT (default address
0x2D/ decimal45) - Power source transition detection (
mains<->battery) - Shutdown on prolonged power loss (configurable delay)
- Shutdown on repeated low-cell-voltage condition (configurable threshold/count)
DRY_RUNmode: full monitoring and alarms without host shutdown- Syslog/journald integration
- systemd service unit
- Makefile targets for build/check/format and symlink-based installation
Monitoring loop:
- Polling interval is derived from
PUBLISH_RATE_HZ - Loop timing is stabilized to reduce drift over long uptime
- Thermal data is cached and refreshed periodically to reduce sysfs I/O
Shutdown triggers:
power_loss_timeout: mains power is lost and not restored beforeSHUTDOWN_DELAY_SEClow_voltage_threshold_reached: low-voltage condition repeatedLOW_VOLTAGE_THRESHOLD_COUNTtimes
Shutdown sequence when DRY_RUN=false:
- Send UPS shutdown command (
register 0x01 <- 0x55) - Execute
SHUTDOWN_COMMAND(defaultsystemctl poweroff)
When DRY_RUN=true, shutdown intent is logged but real UPS/host shutdown actions are suppressed.
The daemon logs structured operational events into journald/syslog.
Typical event types:
monitor_start/monitor_stopstate_changemains_heartbeat(on mains when SOC or charge state changes)power_lost/power_restoredlow_voltage_detected/low_voltage_countdown/low_voltage_recoveredbattery_heartbeatshutdown_intent/shutdown_suppressedups_read_failed/ups_read_recovered
Charge code decoding (register 0x02, bits 2..0, per Waveshare):
0: Standby1: Trickle Charge2: Constant Current Charge3: Constant Voltage Charge4: Charging Pending5: Full State6: Charge Timeout
View logs:
sudo journalctl -u ups-hat-controller.service -fcargo build --releaseBinary:
./target/release/ups_hat_controllerPriority:
- Config file (
/etc/ups-hat-controller/ups-hat-controller.conf) - Environment variables (
EnvironmentFilefor systemd) - Built-in defaults
CLI:
ups_hat_controller --config /path/to/config.conf
ups_hat_controller --helpSupported config keys:
I2C_BUS(default:1)I2C_ADDR(default:45)PUBLISH_RATE_HZ(default:1.0)SHUTDOWN_DELAY_SEC(default:60)SHUTDOWN_COMMAND(default:systemctl poweroff)LOW_VOLTAGE_THRESHOLD(default:3150mV)LOW_VOLTAGE_THRESHOLD_COUNT(default:30)ENABLE_SYSLOG(default:true)DRY_RUN(default:false)
Config files in repo:
- Main config template:
config/ups-hat-controller.conf - Optional env overrides template:
config/ups-hat-controller.env
make build->cargo buildmake release->cargo build --releasemake check->cargo checkmake fmt->cargo fmtmake install-links-> create/update symlinks in system pathsmake uninstall-links-> remove created symlinksmake status-> show current symlink state
make install-linksCreates/updates:
/etc/ups-hat-controller/ups-hat-controller.env->config/ups-hat-controller.env/usr/local/bin/ups_hat_controller->target/release/ups_hat_controller/etc/systemd/system/ups-hat-controller.service->systemd/ups-hat-controller.service
Then enable service:
sudo systemctl enable --now ups-hat-controller.serviceCheck status:
make status
sudo systemctl status ups-hat-controller.serviceRemove symlinks:
make uninstall-linksThe service needs access to /dev/i2c-*.
- Run as root, or
- Run as a user in
i2cgroup with corresponding unit hardening/device rules
MIT, see LICENSE.