refactor(shoukei): cap battery charging from a udev rule

macsmc-battery's "add" uevent fires before the supply accepts writes
(power_supply_register bumps use_cnt after device_add); its deferred
register work then emits "change". Match both instead of polling for 30 s
from a oneshot.

Assisted-by: Claude Code (claude-opus-5-5)
This commit is contained in:
Ryan Yin authored and ryan4yin | 二花 committed 2026-11-01 11:42:57 -06:00
1 parent ecf242d6ab
commit 42341491f3
1 file changed
+5 -31
+5 -31
View File
@@ -1,5 +1,4 @@
{
mylib,
pkgs,
nixos-apple-silicon,
my-asahi-firmware,
@@ -66,36 +65,11 @@
HibernateDelaySec = "5min";
};
# Cap charging at 90%: the SMC's charge_control_end_threshold does not reliably survive a reboot,
# and a udev rule that writes it races the driver probe, so re-apply it from a boot-time service.
# TLP is not used for this: its macbook plugin still validates the 80-or-100 values of the older
# CHWA firmware and rejects anything else, while this machine's CHLS key takes any value. The
# driver ignores charge_control_start_threshold writes and derives it as end - 5.
systemd.services.apple-charge-limit = {
description = "Cap the battery charge at 90%";
wantedBy = [ "multi-user.target" ];
after = [ "systemd-modules-load.service" ];
path = [ pkgs.coreutils ];
serviceConfig = mylib.systemdHardening.privilegedOneshot // {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = pkgs.writeShellScript "apple-charge-limit" ''
limit=90 # percent
node=/sys/class/power_supply/macsmc-battery/charge_control_end_threshold
# macsmc probes asynchronously, so retry briefly instead of racing the driver.
for _ in $(seq 1 30); do
if [ -e "$node" ] && echo "$limit" > "$node" 2>/dev/null &&
[ "$(cat "$node")" = "$limit" ]; then
echo "battery charge capped at $limit%"
exit 0
fi
sleep 1
done
echo "battery charge cap: $(cat "$node" 2>/dev/null || echo "$node missing")" >&2
exit 1
'';
};
};
# Cap charging at 90%. The driver rejects writes at "add" (before registration finishes) and
# announces readiness with "change". TLP's macbook plugin only accepts 80/100 here.
services.udev.extraRules = ''
ACTION=="add|change", SUBSYSTEM=="power_supply", KERNEL=="macsmc-battery", ATTR{charge_control_end_threshold}!="90", ATTR{charge_control_end_threshold}="90"
'';
# After adding this snippet, you need to restart the system for the touchbar to work.
hardware.apple.touchBar = {