M.2 UPS PMH Linux Installer V08 - matched to firmware V8B5 / 0x08B5
24 October 2026

PURPOSE AND CONTENTS
Automated clean installation, upgrade/adoption of recognized old/manual UPS
services, health checking and uninstall on Raspberry Pi 5 Model B with
Raspberry Pi OS / Debian 12 or newer and systemd.
The installer itself uses the Python standard library. It installs these OS
packages unless --no-apt is specified: python3-smbus, python3-serial,
i2c-tools, util-linux. Internet access is needed for apt downloads.

Payload:
- M2_PMH_i2c.py v1.6 -> /usr/local/sbin/M2_PMH_i2c.py
- M2_PMH_i2c.service -> /etc/systemd/system/M2_PMH_i2c.service
- M2_PMH_status_0B.py -> /opt/M2_UPS_PMH/M2_PMH_status.py
- M2_PMH_config_06.py -> /opt/M2_UPS_PMH/M2_PMH_config.py
- Existing M2_PM_UPS_BL01D.py -> /opt/M2_UPS_PMH/M2_PM_UPS_BL01D.py
- payload/SHA256.json -> verified before any installation mutations.

RUN / UPGRADE
Keep stable cable power connected. Extract this entire directory; do not
copy only the installer Python file without payload.

sudo python3 install_M2_UPS_PMH.py --upgrade

--upgrade works for fresh installs as well as upgrades. It authorizes adopting
recognized manual M2_PMH_i2c.service units, including the prior -u ExecStart,
and archiving/removing their service overrides. It also removes recognized
legacy pico_i2c.service / picoUPS.service units and their known installed
scripts. The new service uses the existing 250-ms heartbeat/shutdown protocol.
Managed older installer state (schema1, including V01-V06) is supported.
Without --upgrade, a known managed installation may be updated, but a custom
manual service/drop-in is refused with an instruction to use --upgrade.

There is no need for an uninstall gap before upgrading. Dependencies and boot
preparation finish first. Old service downtime is limited to the actual
replacement/restart. Existing battery chemistry, firmware, MCU Flash and
Raspberry Pi EEPROM are not changed. The service is enabled for future boots.

BOOT CONFIGURATION
Detects /boot/firmware/config.txt, otherwise /boot/config.txt.
Configures I2C userspace support and a DS1307-compatible RTC overlay.
Unless --no-uart is used, enables the Pi5 GPIO14/15 UART, removes only the
matching Linux console tokens and masks the matching serial gettys.
Adds dtparam=rtc_bbat_vchg=0 in a final unconditional block because the
M.2 RTC backup cable uses an external regulated supply. It does not write
RTC time itself or modify battery charge settings.
Existing conflicting RTC/UART overlays and ambiguous includes are refused.
A reboot is requested only when boot configuration requires it; never forced.
Preserve the same --no-uart choice for managed reinstallation.

BACKUPS, CONFLICTS AND ROLLBACK
Persistent record: /var/lib/M2_UPS_PMH/installer.json.
Originals and per-invocation snapshots: /var/backups/M2_UPS_PMH/<UTC stamp>/.
The first pre-install originals are retained across managed upgrades.
Manually updated managed application scripts can be archived/replaced using
--upgrade. Externally changed boot configuration is not silently overwritten.
Only recognized services in /etc/systemd/system and known system-installed
Python targets are adopted. Arbitrary scripts, vendor units, symlinked units,
unknown executable overrides and unrelated running UPS processes are refused
before stopping protection. Home-directory copies are preserved.
Handled errors/interruptions restore files and prior service state for that
invocation. apt-installed dependencies are not rolled back. Abrupt power loss
or SIGKILL cannot run rollback: an incomplete state is left for review and
manual recovery from backups; do not assume automatic repair in that case.

CHECK
sudo python3 install_M2_UPS_PMH.py --check

Checks firmware word at 0x69:0x30 (must be 0x08B5), 3 command-bank reads,
installed payload hashes, absence of legacy active/enabled services, active
new service and recent successful heartbeat from its current PID, the kernel
registered M.2 RTC, UART presence when requested, reboot status, RTC charging
policy/runtime indication where available, and NTP status.
It sends no serial data and does not set RTC time, firmware or MCU registers.
A firmware mismatch returns NOT READY even when Linux files installed
successfully. Daemon1.6 suppresses telemetry on a mismatched firmware map;
heartbeat/shutdown handling continues at the unchanged state register.
On clean systems I2C/RTC/UART may become available only after reboot.
NTP pending is reported; the daemon waits for confirmed NTP before RTC sync.

DETECT / PREVIEW
sudo python3 install_M2_UPS_PMH.py --detect
sudo python3 install_M2_UPS_PMH.py --upgrade --dry-run

These modes do not create installer state, backups or a lock file, install
packages, change services or write hardware settings. Detection reports the
recorded installer version and recognized script headers/service state.

UNINSTALL
sudo python3 install_M2_UPS_PMH.py --uninstall --dry-run
sudo python3 install_M2_UPS_PMH.py --uninstall

Removes current recognized UPS services and installed application files.
Restores recorded boot/cmdline/modules originals and serial-getty state.
Old UPS application/service copies stay archived: they are NOT restored or
restarted because their register map may be incompatible with V8B5.
Uninstallation also supports a recognized manual service without installer
state. In that case only known service/script files and the marked installer
boot block can be removed; unknown previous console/UART settings cannot be
reconstructed. Package dependencies and home-directory files are retained.
Modified managed files block uninstall before any service is stopped.
Uninstall disables Linux UPS shutdown protection. Keep cable power connected;
reboot to apply restored boot settings. A later --upgrade installs it again.

FIRMWARE UPDATING
The installer never loads an image. Protected MCU bootloader is unchanged.
The bundled existing updater01D must use the tested -i option with V8B5,
which skips its old application-version register reads; do not run its -s
legacy setup mode (use this installer instead). Example manual/recovery load:

sudo python3 /opt/M2_UPS_PMH/M2_PM_UPS_BL01D.py -v -i -j -k -p /dev/ttyAMA0 -b 38400 -f /path/to/M2_PMH.hex

This is a separate, explicitly invoked operation, after entering BL using the
usual tested procedure. Firmware images are not included in this installer.

EXTRA OPTIONS / RETURN CODES
--no-apt: skip apt; Python dependencies are still checked.
--no-uart: leave UART and console unchanged.
0: install/check/uninstall succeeded, or install finished and requested reboot.
1: error/refusal; see output and backup/state.
2: files installed or check ran, but hardware/runtime is NOT READY.
130: interrupted.

VALIDATION
41 host-simulated installer tests pass (see VALIDATION.txt). System commands
are mocked and filesystem paths redirected in tests; this does not replace a
first real install/check on a Raspberry Pi 5. No target installation was run
automatically while preparing this archive.

V8B5 ULED MAPPING
Configurator05 adds USR LED mapping at command register 0x0D:
0 Manual; 1 SYS; 2 BAT; 3 TMR; 4 FAN; 5 TMP. Selection is saved in MCU Flash on change and restored after reset.
Daemon1.6 accepts the unchanged V8B2/V8B3/V8B5 telemetry map during upgrading.
Config06/status0B and the full installer check require firmware 0x08B5.

V8B5 adds Advanced external ADC range selection at 0x6B:0x08 and voltage at
0x69:0x12. Connect only EXT0 (P2 pin11, 0..5.2 V) OR EXT1 (pin12, 0..32 V);
leave the other input disconnected. Configurator06 and status0B describe it.


This corrected V8B5 rebuild requires R75=1.00k; gains EXT0=2.4, EXT1=20.6.
D19 protects UART RX (P2 pin10), not the analogue input. Leave D19 unchanged.
