- Python 93.7%
- Shell 6.3%
lactd ships After=multi-user.target, and this unit is WantedBy=multi-user.target, so systemd found an ordering cycle at boot and deleted our start job. The daemon already tolerates lactd being absent (stats/profile calls retry every 2 s). |
||
|---|---|---|
| .gitignore | ||
| astral-connector.service | ||
| astral-connector.user.service | ||
| astral_connector.py | ||
| config.example.toml | ||
| install-system.sh | ||
| LICENSE | ||
| README.md | ||
| test_astral_connector.py | ||
astral-connector
A per-pin 12V-2x6 (12VHPWR) watchdog for ASUS ROG Astral RTX 50 cards on Linux. It reads the card's own per-pin current sensor (the data GPU Tweak III shows as Power Detector+), keeps a history, and escalates when a pin is overloaded: desktop and phone alert, then a throttled LACT profile, then a clean poweroff.
One Python file, standard library only. It runs as a system service from boot, so the card is protected before anyone logs in, with desktop alerts delivered into whichever graphical session is active. No kernel module.
Written with an AI assistant (Claude) and tested on one card, a ROG Astral RTX 5090 OC (
1043:89E3), NVIDIA 615.78.08, kernel 7.2, LACT 0.10. Read the code before trusting it with your hardware. It never writes to the sensor chip.
What it reads
The Astral carries an ITE IT8915FN at 0x2b on the GPU's first NVIDIA i2c adapter.
| Register | Content |
|---|---|
0x80..0x97 |
6 x (u16 BE mV, u16 BE mA), reverse pin order: the first block is pin 6 |
0x98 |
an independent total-current channel, ~0.507 x the pin sum |
0xa0 |
a 12 V channel of ~1 A, probably the PCIe slot |
0x60 |
8 board temperatures, 0.1 °C (not per-pin) |
0xc0 |
3 time-in-state counters (s), a lifetime odometer |
Pin order is confirmed against HWiNFO by 12vhpwr-guard and matches LibreHardwareMonitor. Several Linux tools label the pins backwards.
The chip smooths its current readings: after a step change in load they take 5 to 8 s to settle, while board power reacts within half a second. Hold times below account for that.
Board power, temperatures (including every VRAM chip) and the active profile come from the LACT daemon socket.
Rules
Each condition must hold continuously for its hold time. A high pin reading only counts if board power can account for it, so one bad frame cannot trigger anything. The tiers follow what the established tools do: 12vhpwr-guard (9.5 A / 15 s, 13 A / 3 s, 16 A at once), ASUS GPU Tweak III (warns at 9.2 A, shuts down above 12.5 A), MSI GPU Safeguard+ (cuts the power limit first). Connectors melt from heat built up over minutes, so short excursions are logged, not acted on.
| Rule | Warning | Critical (throttle) | Emergency (poweroff) |
|---|---|---|---|
| per-pin current | 8.8 A for 5 s | 9.5 A for 15 s, or 12.5 A for 3 s | 16 A for 1 s |
| pin voltage (>= 10 A flowing) | < 11.6 V for 5 s | < 11.2 V for 5 s | |
| open pin (>= 10 A flowing) | < 0.3 A for 3 s | ||
| min/max imbalance (>= 20 A) | < 0.60 for 30 s | ||
| drift from your baseline (>= 15 A) | > 3 pp for 60 s | ||
| sensor silent | 15 s | ||
| chip power vs board power | > 20 % off for 60 s | ||
NVIDIA driver error (NVRM: Xid in the kernel log) |
each one |
Imbalance alone only warns: every connector is somewhat uneven, and the danger is amps on one pin, which the current rules cover. All thresholds are in the config.
Escalation
- Warning: desktop notification, optional ntfy push, a 2-minute flight-recorder CSV.
- Critical: switch LACT to the safe profile and latch there. A profile change by anything else is
reverted while latched. Only
astral-connector resetclears it: the fault is physical. - Still critical 20 s later (10 s for the 12.5 A tier): a 30 s poweroff countdown (5 s for 12.5 A),
astral-connector cancelstops it. This also runs if LACT could not switch profile. - Emergency: power off at once (
systemctl poweroff --check-inhibitors=no).
The RTX 5090's minimum power limit is 400 W, so the safe profile caps the core clock instead. On the test card, 1500 MHz cut a 333 W load to 141 W within half a second, and the busiest pin from 5.5 A to 2.4 A.
What gets logged
| Where | |
|---|---|
| every reading at 1 Hz, 14 days | /var/lib/astral-connector/history.db (report draws it) |
| every event (warnings, throttle, countdown, poweroff, reset, Xid, crash restarts) | same database, astral-connector events |
| the 2 minutes before any escalation and before a poweroff | /var/lib/astral-connector/events/*.csv, fsynced |
| why the PC was powered off | last-action.json; reported by notification and ntfy on the next boot |
| a previous boot that ended without a clean shutdown (freeze, power loss) | an unclean_boot event |
Install
git clone https://git.motaphe.dev/Motaphe/astral-connector.git && cd astral-connector
python3 astral_connector.py setup-profile # creates the Pin-Safe LACT profile
sudo ./install-system.sh
astral-connector selftest
The installer copies the script to /usr/local/lib/astral-connector, links /usr/local/bin/astral-connector,
puts the config in /etc/astral-connector/ (ntfy credentials in a root-only ntfy.env), migrates any
user-mode state, enables astral-connector.service, verifies fresh readings and rolls back if they do not
appear. Re-run it to update. Members of wheel can run reset, cancel and baseline without root.
Needs: an Astral card, the NVIDIA driver (it exposes the i2c adapters), i2c-dev loaded, LACT running.
astral-connector.user.service runs it as a user service instead, for testing: it protects only while you
are logged in, and logind may refuse its poweroff without an active session.
Once the card has run under load for a while, record your connector's normal per-pin shares so drift
can be detected: astral-connector baseline.
Commands
| Command | |
|---|---|
run [--dry-run] |
the daemon |
tui |
live terminal view |
status [--json] |
one-shot; exit code 0/1/2 for OK/warning/critical |
events [--days N] |
event log and the latest flight records |
report [--hours N] |
HTML charts of the history |
baseline [--minutes N | --shares a,b,c,d,e,f] |
record the per-pin fingerprint |
reset / cancel |
clear the latch / stop a poweroff countdown |
setup-profile [--clock MHz] / selftest / test-notify |
setup checks |
The live state is written to /run/astral-connector/state.json every half second for bars and widgets
(card name, pins, peak today, temperatures, fan, last event, latch and countdown).
Limits
- It measures current, not temperature. A green reading is not a statement about connector heat.
- The chip smooths its readings over several seconds, so a sub-second spike is invisible to it. That is also why it is not used for instant trips.
- A daemon that has died protects nothing. systemd restarts it without limit and its watchdog catches a hang; a crash restart raises a notification.
Credits
Register map and pin order: astral-hwmon, LibreHardwareMonitor #2168, 12vhpwr-guard, sus. The tiered response follows 12vhpwr-guard.