# UPS HAT (E) Support Tools

Linux telemetry, desktop monitoring, and guarded shutdown tools for the
[Waveshare UPS HAT (E)](https://www.waveshare.com/wiki/UPS_HAT_%28E%29) on a
Raspberry Pi 5.

This is an independent community project derived in part from Waveshare's
official Python demonstration. It is not an official Waveshare package.

## Status

The package has been exercised on a Raspberry Pi 5 with a UPS HAT (E), four
21700 cells, Raspberry Pi OS, and the HAT MCU at I2C address `0x2d`. The local
test suite currently contains 74 passing tests.

The read-only telemetry and tray-widget paths are mature enough for evaluation.
The automatic shutdown policy is intentionally guarded and must be validated in
dry-run mode on each system before it is armed. The optional HAT V2 watchdog
feed service is experimental and remains disabled by default.

## Safety

Li-ion battery systems and automatic shutdown controls can cause hardware
damage or data loss when configured incorrectly.

- Begin with read-only telemetry.
- Run the shutdown policy in dry-run mode before arming it.
- Calibrate thresholds against the cells, load, and UPS firmware in use.
- Do not enable the V2 watchdog feed service without a supervised recovery
  test.
- Keep a known-good boot device and current backups available.

The tray widget cannot directly power off Linux or write HAT registers. Those
operations belong to separate root-owned services.

## Features

- HAT-native telemetry from MCU address `0x2d`:
  - charge state and external-input indication;
  - VBUS voltage, current, and power;
  - pack voltage/current and reported state of charge;
  - individual cell voltages and imbalance;
  - time-to-empty and time-to-full values;
  - MCU communication health and software revision;
  - V2 auto-restart, I2C address, and watchdog readback.
- Raspberry Pi `EXT5V_V` and `get_throttled` telemetry.
- Desktop tray widget with a selectable, dynamically scaled status report.
- Bounded diagnostic and transition-history logs.
- Guarded low-cell and reserve-SOC shutdown policy with dry-run and armed
  systemd units.
- Fixed-command PolicyKit integration for selecting `Disabled`, `Dry run`, or
  `Armed` shutdown modes from the widget.
- Optional read-only INA219-style telemetry at address `0x40`.
- Explicitly guarded V2 watchdog tooling; register writes require command-line
  opt-in and the feed service is not enabled by installation.

## Transition Classification

The widget does not treat battery discharge current alone as proof of an input
outage.

- Valid VBUS plus negative battery current is retained as battery assistance or
  an unconfirmed transfer pulse.
- One coherent sample with low/absent measured VBUS, the MCU input-present flag
  clear, and healthy fuel-gauge and Type-C-controller communication confirms a
  sampled input interruption.
- A completed interruption with one loss sample is labeled brief; additional
  non-normal samples make it a sustained outage.
- Restoration requires two valid-input samples and is timestamped at the first
  valid sample.
- Sampling gaps, I2C errors, conflicting input evidence, and controller
  communication faults cannot independently create a confirmed outage.

The shutdown service has a separate, stricter multi-sample qualification path.
Widget history classification never lowers the shutdown safety threshold.

## Requirements

Verified Raspberry Pi OS packages:

```bash
sudo apt-get update
sudo apt-get install python3-pyqt5 python3-smbus i2c-tools raspi-utils-core \
  desktop-file-utils polkitd pkexec mate-polkit shellcheck
```

I2C bus 1 must be enabled. On Raspberry Pi OS:

```bash
sudo raspi-config
```

Enable I2C, reboot if requested, and verify that `/dev/i2c-1` exists.

## Read-Only Evaluation

Download and verify the current source release, then enter it:

```bash
curl -fLO https://wb6bbb.com/Uups-widgit/ups-hat-e-support-2026.09.19.1.tar.gz
curl -fLO https://wb6bbb.com/Uups-widgit/ups-hat-e-support-2026.09.19.1.tar.gz.sha256
sha256sum -c ups-hat-e-support-2026.09.19.1.tar.gz.sha256
tar -xzf ups-hat-e-support-2026.09.19.1.tar.gz
cd ups-hat-e-support-2026.09.19.1
```

Check the bus and read one HAT snapshot without installing services:

```bash
i2cdetect -y 1
python3 bin/ups_hat_e_probe.py --pretty
python3 bin/ups_hat_e_read.py --pretty
```

Expected HAT MCU address: `0x2d`. Some boards also expose supplemental
INA219-style telemetry at `0x40`.

## System Services

The root installer copies the command-line tools to
`/usr/local/lib/ups-hat-e-support`, renders portable systemd units, installs the
fixed service-control helper and PolicyKit action, and creates default
configuration files only when they do not already exist.

```bash
sudo bin/install-system.sh
```

The installer does **not** enable, disable, start, stop, or restart any service.

Start with telemetry and the non-destructive shutdown observer:

```bash
sudo systemctl enable --now ups-hat-e-monitor.service
sudo systemctl enable --now ups-hat-e-shutdown-dry-run.service
journalctl -fu ups-hat-e-shutdown-dry-run.service
```

Review `/etc/default/ups-hat-e` before testing on battery. Its default reserve
SOC mode is `observe`, so a reported low percentage is logged but does not cause
shutdown. Cell-voltage thresholds remain active only in the armed service.

After a controlled discharge has proven the readings, thresholds, recovery
behavior, and dry-run decisions, select the armed mode explicitly:

```bash
sudo /usr/local/libexec/ups-hat-e-service-control set-mode armed
```

Return to dry-run or disable both policy units with:

```bash
sudo /usr/local/libexec/ups-hat-e-service-control set-mode dry-run
sudo /usr/local/libexec/ups-hat-e-service-control set-mode disabled
```

The armed policy requires stable on-battery telemetry, observes a startup grace
period, and resets threshold timers after read or gating failures. When a
qualified shutdown threshold is reached, it requests the HAT power-cycle action
and then asks systemd to power off Linux.

The optional `ups-ina219-monitor.service` is supplemental; it does not replace
the HAT-native per-cell data. The `ups-hat-e-watchdog-feed.service` must remain
disabled until deliberately validated on the target system.

## Desktop Widget

Install the widget as the desktop user, not as root:

```bash
gui/install-user.sh
```

The installer uses standard XDG locations, creates an application-menu entry,
and enables desktop autostart. Use `--no-autostart` to suppress autostart.

On Raspberry Pi OS desktops where Qt does not register the tray icon through
the native Wayland backend, install with the XWayland backend:

```bash
gui/install-user.sh --qt-platform xcb
```

Start it from the application menu or run:

```bash
$HOME/.local/bin/ups-hat-e-tray-widget
```

Installed user data is stored below the normal XDG data, configuration, cache,
state, and runtime directories. The diagnostic log rotates at 5 MiB with two
backups. Transition history rotates at 1 MiB with one backup. Files are mode
`0600`, and private application directories are mode `0700`.

The root service installer is optional for telemetry display. It is required
for the widget's authenticated shutdown-policy mode selector.

See [gui/README.md](gui/README.md) for widget details and uninstallation.

## Watchdog Tooling

Read-only status is safe to inspect:

```bash
python3 bin/ups_hat_e_watchdog.py status --pretty
```

Every watchdog write path requires `--allow-watchdog-write`; enabling the
watchdog requires additional explicit risk confirmation. The bundled feed unit
is a candidate implementation based on rewriting the timeout register because
the available Waveshare sample does not document a dedicated feed operation.
Do not enable it as part of routine installation.

## Validation

Run the source validation suite:

```bash
python3 -m py_compile lib/ups_hat_e.py bin/*.py gui/ups_hat_e_tray_widget.py
python3 -m unittest discover -s tests -v
shellcheck bin/*.sh gui/*.sh

stage=$(mktemp -d)
bin/render-systemd-units.sh "$stage"
systemd-analyze verify "$stage"/*.service
```

The offscreen widget startup check should remain alive until `timeout` exits
with status `124`:

```bash
UPS_HAT_E_QT_PLATFORM=offscreen timeout 5 \
  python3 gui/ups_hat_e_tray_widget.py
```

## Project Layout

- `lib/ups_hat_e.py`: register parser and I2C helpers.
- `bin/ups_hat_e_read.py`: one-shot JSON reader.
- `bin/ups_hat_e_monitor.py`: continuous JSONL telemetry.
- `bin/ups_hat_e_probe.py`: read-only address and identity probe.
- `bin/ups_hat_e_shutdown.py`: guarded shutdown policy.
- `bin/ups_hat_e_watchdog.py`: guarded V2 watchdog tool.
- `bin/ext5v-tsv-report.py`: report one or more `ext5v-monitor.sh` TSV files.
- `bin/ext5v-tsv-maintain.sh`: list or safely compress older TSV files.
- `bin/install-system.sh`: portable root-side installer; changes no service
  state.
- `gui/`: tray widget, XDG installer, launcher, and icon assets.
- `systemd/*.service.in`: portable service templates.
- `config/`: service defaults, PolicyKit action, and optional journal limits.
- `tests/`: parser, shutdown, service-control, and widget tests.

## Attribution And Licensing

The tray widget began as a substantial extension of Waveshare's UPS HAT (E)
demo, and the battery images originated in that demo archive. See
[NOTICE.md](NOTICE.md) for provenance and licensing limitations.

No project-wide open-source license has been selected for this initial
publication. In the absence of a license, normal copyright restrictions apply.
