fix(power): Dell charge limit needs Custom type, not Adaptive
All checks were successful
Check / eval (push) Successful in 2m58s
All checks were successful
Check / eval (push) Successful in 2m58s
charge_control_end_threshold was written (80) but charge_types stayed [Adaptive], which ignores the cap while still reporting it — battery kept charging past 80% on Latitude. Oneshot now selects Custom when a limit is set, Adaptive/Standard when off; sets start hysteresis; doctor warns if type ≠ Custom while limited. V0: flake check --no-build green. V3: re-apply limit, confirm charge_types shows [Custom] and status stops Charging above cap.
This commit is contained in:
@@ -157,7 +157,7 @@ let
|
|||||||
" <span size='small' alpha='55%'>${prettyKeys (lib.head m)}</span>";
|
" <span size='small' alpha='55%'>${prettyKeys (lib.head m)}</span>";
|
||||||
menuHint = module: hintForAction "exec, nomarchy-menu ${module}";
|
menuHint = module: hintForAction "exec, nomarchy-menu ${module}";
|
||||||
# Apps opens rofi drun directly, not a nomarchy-menu module.
|
# Apps opens rofi drun directly, not a nomarchy-menu module.
|
||||||
appsHint = " <span size='small' alpha='55%'>SUPER + D</span>";
|
appsHint = " <span size='small' alpha='55%'>SUPER + Space</span>";
|
||||||
# A menu row that runs the same op as a global bind whose action isn't a
|
# A menu row that runs the same op as a global bind whose action isn't a
|
||||||
# `nomarchy-menu` module (the Capture rows ↔ the Print screenshot keys):
|
# `nomarchy-menu` module (the Capture rows ↔ the Print screenshot keys):
|
||||||
# match on mods+key so the hint still reads from keybinds.nix (single
|
# match on mods+key so the hint still reads from keybinds.nix (single
|
||||||
@@ -327,6 +327,7 @@ let
|
|||||||
set_limit() {
|
set_limit() {
|
||||||
nomarchy-theme-sync --quiet set settings.power.batteryChargeLimit "$1" --no-switch
|
nomarchy-theme-sync --quiet set settings.power.batteryChargeLimit "$1" --no-switch
|
||||||
n="$1"; [ "$n" = null ] && n=100
|
n="$1"; [ "$n" = null ] && n=100
|
||||||
|
# Root oneshot sets Custom charge type (Dell) + thresholds.
|
||||||
applied=
|
applied=
|
||||||
if systemctl restart nomarchy-battery-charge-limit.service 2>/dev/null; then
|
if systemctl restart nomarchy-battery-charge-limit.service 2>/dev/null; then
|
||||||
applied=1
|
applied=1
|
||||||
@@ -334,21 +335,34 @@ let
|
|||||||
for d in /sys/class/power_supply/*/; do
|
for d in /sys/class/power_supply/*/; do
|
||||||
[ "$(cat "$d/type" 2>/dev/null)" = Battery ] || continue
|
[ "$(cat "$d/type" 2>/dev/null)" = Battery ] || continue
|
||||||
[ "$(cat "$d/scope" 2>/dev/null || echo System)" = Device ] && continue
|
[ "$(cat "$d/scope" 2>/dev/null || echo System)" = Device ] && continue
|
||||||
|
if [ "$n" -lt 100 ] 2>/dev/null; then
|
||||||
|
for tnode in charge_types charge_type; do
|
||||||
|
[ -w "$d$tnode" ] || continue
|
||||||
|
listed=$(cat "$d$tnode" 2>/dev/null || true)
|
||||||
|
case " $listed " in *" Custom "*|*"Custom"*) echo Custom > "$d$tnode" 2>/dev/null || true ;; esac
|
||||||
|
done
|
||||||
|
fi
|
||||||
thresh="$d/charge_control_end_threshold"
|
thresh="$d/charge_control_end_threshold"
|
||||||
if [ -w "$thresh" ] && echo "$n" > "$thresh" 2>/dev/null; then
|
if [ -w "$thresh" ] && echo "$n" > "$thresh" 2>/dev/null; then
|
||||||
applied=1
|
applied=1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
actual=
|
actual=; ctype=
|
||||||
for d in /sys/class/power_supply/*/; do
|
for d in /sys/class/power_supply/*/; do
|
||||||
[ "$(cat "$d/type" 2>/dev/null)" = Battery ] || continue
|
[ "$(cat "$d/type" 2>/dev/null)" = Battery ] || continue
|
||||||
[ "$(cat "$d/scope" 2>/dev/null || echo System)" = Device ] && continue
|
[ "$(cat "$d/scope" 2>/dev/null || echo System)" = Device ] && continue
|
||||||
[ -r "$d/charge_control_end_threshold" ] \
|
[ -r "$d/charge_control_end_threshold" ] \
|
||||||
&& actual=$(cat "$d/charge_control_end_threshold" 2>/dev/null) && break
|
&& actual=$(cat "$d/charge_control_end_threshold" 2>/dev/null)
|
||||||
|
ctype=$(cat "$d/charge_types" 2>/dev/null || cat "$d/charge_type" 2>/dev/null || true)
|
||||||
|
[ -n "$actual" ] && break
|
||||||
done
|
done
|
||||||
|
# Highlight active type: "… [Custom] …"
|
||||||
|
active=$(printf '%s' "$ctype" | sed -n 's/.*\[\([^]]*\)\].*/\1/p')
|
||||||
|
extra=
|
||||||
|
[ -n "$active" ] && extra=" (type $active)"
|
||||||
if [ -n "$applied" ] && [ -n "$actual" ]; then
|
if [ -n "$applied" ] && [ -n "$actual" ]; then
|
||||||
notify-send "Battery limit" "$2 — sysfs now $actual%."
|
notify-send "Battery limit" "$2 — stop at $actual%$extra."
|
||||||
elif [ -n "$applied" ]; then
|
elif [ -n "$applied" ]; then
|
||||||
notify-send "Battery limit" "$2 — applied (could not re-read sysfs)."
|
notify-send "Battery limit" "$2 — applied (could not re-read sysfs)."
|
||||||
else
|
else
|
||||||
@@ -970,21 +984,32 @@ ${themeRows}
|
|||||||
for d in /sys/class/power_supply/*/; do
|
for d in /sys/class/power_supply/*/; do
|
||||||
[ "$(cat "$d/type" 2>/dev/null)" = Battery ] || continue
|
[ "$(cat "$d/type" 2>/dev/null)" = Battery ] || continue
|
||||||
[ "$(cat "$d/scope" 2>/dev/null || echo System)" = Device ] && continue
|
[ "$(cat "$d/scope" 2>/dev/null || echo System)" = Device ] && continue
|
||||||
|
if [ "$n" -lt 100 ] 2>/dev/null; then
|
||||||
|
for tnode in charge_types charge_type; do
|
||||||
|
[ -w "$d$tnode" ] || continue
|
||||||
|
listed=$(cat "$d$tnode" 2>/dev/null || true)
|
||||||
|
case " $listed " in *" Custom "*|*"Custom"*) echo Custom > "$d$tnode" 2>/dev/null || true ;; esac
|
||||||
|
done
|
||||||
|
fi
|
||||||
thresh="$d/charge_control_end_threshold"
|
thresh="$d/charge_control_end_threshold"
|
||||||
if [ -w "$thresh" ] && echo "$n" > "$thresh" 2>/dev/null; then
|
if [ -w "$thresh" ] && echo "$n" > "$thresh" 2>/dev/null; then
|
||||||
applied=1
|
applied=1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
actual=
|
actual=; ctype=
|
||||||
for d in /sys/class/power_supply/*/; do
|
for d in /sys/class/power_supply/*/; do
|
||||||
[ "$(cat "$d/type" 2>/dev/null)" = Battery ] || continue
|
[ "$(cat "$d/type" 2>/dev/null)" = Battery ] || continue
|
||||||
[ "$(cat "$d/scope" 2>/dev/null || echo System)" = Device ] && continue
|
[ "$(cat "$d/scope" 2>/dev/null || echo System)" = Device ] && continue
|
||||||
[ -r "$d/charge_control_end_threshold" ] \
|
[ -r "$d/charge_control_end_threshold" ] \
|
||||||
&& actual=$(cat "$d/charge_control_end_threshold" 2>/dev/null) && break
|
&& actual=$(cat "$d/charge_control_end_threshold" 2>/dev/null)
|
||||||
|
ctype=$(cat "$d/charge_types" 2>/dev/null || cat "$d/charge_type" 2>/dev/null || true)
|
||||||
|
[ -n "$actual" ] && break
|
||||||
done
|
done
|
||||||
|
active=$(printf '%s' "$ctype" | sed -n 's/.*\[\([^]]*\)\].*/\1/p')
|
||||||
|
extra=; [ -n "$active" ] && extra=" (type $active)"
|
||||||
if [ -n "$applied" ] && [ -n "$actual" ]; then
|
if [ -n "$applied" ] && [ -n "$actual" ]; then
|
||||||
notify-send "Battery limit" "$2 — sysfs now $actual%."
|
notify-send "Battery limit" "$2 — stop at $actual%$extra."
|
||||||
elif [ -n "$applied" ]; then
|
elif [ -n "$applied" ]; then
|
||||||
notify-send "Battery limit" "$2 — applied (could not re-read sysfs)."
|
notify-send "Battery limit" "$2 — applied (could not re-read sysfs)."
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -103,6 +103,8 @@
|
|||||||
batteryChargeLimit = lib.mkOption {
|
batteryChargeLimit = lib.mkOption {
|
||||||
type = lib.types.nullOr (lib.types.ints.between 50 100);
|
type = lib.types.nullOr (lib.types.ints.between 50 100);
|
||||||
default = config.nomarchy.settings.power.batteryChargeLimit or null;
|
default = config.nomarchy.settings.power.batteryChargeLimit or null;
|
||||||
|
# Dell Adaptive charge mode ignores the end threshold unless we
|
||||||
|
# also select Custom (power.nix oneshot); see Latitude 5310 QA.
|
||||||
example = 80;
|
example = 80;
|
||||||
description = ''
|
description = ''
|
||||||
Stop charging at this percentage to extend battery lifespan,
|
Stop charging at this percentage to extend battery lifespan,
|
||||||
@@ -112,8 +114,10 @@
|
|||||||
null leaves charging at the firmware default (menu writes 100).
|
null leaves charging at the firmware default (menu writes 100).
|
||||||
Backend-independent: the menu applies live via sysfs (udev
|
Backend-independent: the menu applies live via sysfs (udev
|
||||||
GROUP=users on the threshold node) and persists settings in
|
GROUP=users on the threshold node) and persists settings in
|
||||||
theme-state; a oneshot re-applies on boot and AC replug. Needs
|
theme-state; a oneshot re-applies on boot and AC replug. On
|
||||||
nomarchy.system.power.laptop.
|
Dell (and similar) the oneshot also selects charge type Custom
|
||||||
|
— Adaptive ignores the threshold while still reporting it.
|
||||||
|
Needs nomarchy.system.power.laptop.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -75,14 +75,62 @@ in
|
|||||||
[ "$(cat "$d/scope" 2>/dev/null || echo System)" = Device ] && continue
|
[ "$(cat "$d/scope" 2>/dev/null || echo System)" = Device ] && continue
|
||||||
thresh="$d/charge_control_end_threshold"
|
thresh="$d/charge_control_end_threshold"
|
||||||
[ -e "$thresh" ] || continue
|
[ -e "$thresh" ] || continue
|
||||||
# Keep the node group-writable for the menu's live path (udev
|
# Keep nodes group-writable for the menu's live path (udev
|
||||||
# sets this on add; re-assert after firmware recreates attrs).
|
# sets this on add; re-assert after firmware recreates attrs).
|
||||||
chgrp users "$thresh" 2>/dev/null || true
|
for node in charge_control_end_threshold charge_control_start_threshold charge_types charge_type; do
|
||||||
chmod 0664 "$thresh" 2>/dev/null || true
|
[ -e "$d$node" ] || continue
|
||||||
|
chgrp users "$d$node" 2>/dev/null || true
|
||||||
|
chmod 0664 "$d$node" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
# Dell (and some others): charge_control_* thresholds are only
|
||||||
|
# honoured in Custom mode. Adaptive/Standard ignore end_threshold
|
||||||
|
# while still reporting the written value — battery keeps charging
|
||||||
|
# past the cap (Latitude 5310: end=80, type=[Adaptive], capacity 96%+).
|
||||||
|
# Off (100): restore Adaptive if listed, else leave type alone.
|
||||||
|
ctypes="$d/charge_types"
|
||||||
|
ctype="$d/charge_type"
|
||||||
|
if [ -e "$ctypes" ] || [ -e "$ctype" ]; then
|
||||||
|
listed=$(cat "$ctypes" 2>/dev/null || cat "$ctype" 2>/dev/null || true)
|
||||||
|
write_type() {
|
||||||
|
local t="$1"
|
||||||
|
[ -n "$t" ] || return 0
|
||||||
|
if [ -w "$ctypes" ]; then echo "$t" > "$ctypes" 2>/dev/null || true
|
||||||
|
elif [ -w "$ctype" ]; then echo "$t" > "$ctype" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
if [ "$limit" -lt 100 ] 2>/dev/null; then
|
||||||
|
# listed looks like: "Trickle Fast Standard [Adaptive] Custom"
|
||||||
|
case "$listed" in *Custom*) write_type Custom ;; esac
|
||||||
|
else
|
||||||
|
case "$listed" in
|
||||||
|
*Adaptive*) write_type Adaptive ;;
|
||||||
|
*Standard*) write_type Standard ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# Start threshold: must be < end. Keep a ~10% hysteresis band when
|
||||||
|
# the node exists (firmware may reject start >= end).
|
||||||
|
start_node="$d/charge_control_start_threshold"
|
||||||
|
if [ -w "$start_node" ] && [ "$limit" -lt 100 ] 2>/dev/null; then
|
||||||
|
start=$(( limit > 15 ? limit - 10 : 0 ))
|
||||||
|
echo "$start" > "$start_node" 2>/dev/null || true
|
||||||
|
fi
|
||||||
# Some firmwares (Dell) reset the threshold on unplug *after*
|
# Some firmwares (Dell) reset the threshold on unplug *after*
|
||||||
# the udev event — write once, wait, write again.
|
# the udev event — write once, wait, write again (type first).
|
||||||
echo "$limit" > "$thresh" 2>/dev/null || true
|
echo "$limit" > "$thresh" 2>/dev/null || true
|
||||||
sleep 1
|
sleep 1
|
||||||
|
if [ -e "$ctypes" ] || [ -e "$ctype" ]; then
|
||||||
|
listed=$(cat "$ctypes" 2>/dev/null || cat "$ctype" 2>/dev/null || true)
|
||||||
|
if [ "$limit" -lt 100 ] 2>/dev/null; then
|
||||||
|
case "$listed" in
|
||||||
|
*Custom*)
|
||||||
|
if [ -w "$ctypes" ]; then echo Custom > "$ctypes" 2>/dev/null || true
|
||||||
|
elif [ -w "$ctype" ]; then echo Custom > "$ctype" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
fi
|
||||||
echo "$limit" > "$thresh" 2>/dev/null || true
|
echo "$limit" > "$thresh" 2>/dev/null || true
|
||||||
done
|
done
|
||||||
exit 0
|
exit 0
|
||||||
@@ -101,6 +149,9 @@ in
|
|||||||
systemdRun = "${config.systemd.package}/bin/systemd-run";
|
systemdRun = "${config.systemd.package}/bin/systemd-run";
|
||||||
in ''
|
in ''
|
||||||
SUBSYSTEM=="power_supply", ATTR{type}=="Battery", TEST=="charge_control_end_threshold", GROUP="users", MODE="0664"
|
SUBSYSTEM=="power_supply", ATTR{type}=="Battery", TEST=="charge_control_end_threshold", GROUP="users", MODE="0664"
|
||||||
|
SUBSYSTEM=="power_supply", ATTR{type}=="Battery", TEST=="charge_types", GROUP="users", MODE="0664"
|
||||||
|
SUBSYSTEM=="power_supply", ATTR{type}=="Battery", TEST=="charge_type", GROUP="users", MODE="0664"
|
||||||
|
SUBSYSTEM=="power_supply", ATTR{type}=="Battery", TEST=="charge_control_start_threshold", GROUP="users", MODE="0664"
|
||||||
SUBSYSTEM=="power_supply", ATTR{type}=="Mains", RUN+="${systemctl} --no-block restart nomarchy-battery-charge-limit.service"
|
SUBSYSTEM=="power_supply", ATTR{type}=="Mains", RUN+="${systemctl} --no-block restart nomarchy-battery-charge-limit.service"
|
||||||
SUBSYSTEM=="power_supply", ATTR{type}=="Mains", RUN+="${systemdRun} --no-block --collect --on-active=2s --timer-property=AccuracySec=200ms ${systemctl} --no-block restart nomarchy-battery-charge-limit.service"
|
SUBSYSTEM=="power_supply", ATTR{type}=="Mains", RUN+="${systemdRun} --no-block --collect --on-active=2s --timer-property=AccuracySec=200ms ${systemctl} --no-block restart nomarchy-battery-charge-limit.service"
|
||||||
''
|
''
|
||||||
|
|||||||
@@ -233,6 +233,8 @@ fi
|
|||||||
|
|
||||||
# ── laptop battery charge threshold (only when a limit is set) ───────
|
# ── laptop battery charge threshold (only when a limit is set) ───────
|
||||||
# Name-agnostic (BAT0, CMB0, …) — same type/scope filter as notify (#60).
|
# Name-agnostic (BAT0, CMB0, …) — same type/scope filter as notify (#60).
|
||||||
|
# Dell: Adaptive mode ignores end_threshold while still reporting the
|
||||||
|
# written value — warn if type is not Custom when a limit is active.
|
||||||
bat_seen=0; bat_limited=0
|
bat_seen=0; bat_limited=0
|
||||||
for bat in /sys/class/power_supply/*/; do
|
for bat in /sys/class/power_supply/*/; do
|
||||||
[ "$(cat "$bat/type" 2>/dev/null)" = Battery ] || continue
|
[ "$(cat "$bat/type" 2>/dev/null)" = Battery ] || continue
|
||||||
@@ -246,7 +248,15 @@ for bat in /sys/class/power_supply/*/; do
|
|||||||
esac
|
esac
|
||||||
if [ "$lim" -lt 100 ]; then
|
if [ "$lim" -lt 100 ]; then
|
||||||
bat_limited=1
|
bat_limited=1
|
||||||
ok "$(basename "$bat") charge limit active at ${lim}%"
|
ctype=$(cat "$bat/charge_types" 2>/dev/null || cat "$bat/charge_type" 2>/dev/null || true)
|
||||||
|
active=$(printf '%s' "$ctype" | sed -n 's/.*\[\([^]]*\)\].*/\1/p')
|
||||||
|
name=$(basename "$bat")
|
||||||
|
if [ -n "$active" ] && [ "$active" != Custom ]; then
|
||||||
|
warn "$name charge limit ${lim}% but type is $active (not Custom)" \
|
||||||
|
"thresholds only apply in Custom — run: systemctl restart nomarchy-battery-charge-limit"
|
||||||
|
else
|
||||||
|
ok "$name charge limit active at ${lim}%${active:+ (type $active)}"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
if [ "$bat_seen" -eq 1 ] && [ "$bat_limited" -eq 0 ]; then
|
if [ "$bat_seen" -eq 1 ] && [ "$bat_limited" -eq 0 ]; then
|
||||||
|
|||||||
Reference in New Issue
Block a user