eww better battery
This commit is contained in:
@@ -1,17 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
# Battery state from sysfs; refreshes on power_supply udev events and every
|
||||
# 30 s. Emits a `state` (charging / discharging / low / full / idle) that the
|
||||
# CSS maps to a colour, instead of hard-coding one here.
|
||||
# 30 s. Emits a `state` (charging / discharging / low / critical / full /
|
||||
# idle) that the CSS maps to a colour, instead of hard-coding one here.
|
||||
#
|
||||
# Every refresh also appends a sample to $XDG_STATE_HOME/eww/battery.log,
|
||||
# which feeds the drain rate (%/h) and the history plot.
|
||||
|
||||
bat=""
|
||||
for d in /sys/class/power_supply/*; do
|
||||
[[ -r $d/type && $(<"$d/type") == Battery ]] && { bat=$d; break; }
|
||||
done
|
||||
if [[ -z $bat ]]; then
|
||||
echo '{"visible":false,"percentage":0,"wattage":"","status":"","state":"none"}'
|
||||
echo '{"visible":false,"percentage":0,"wattage":"","status":"","time":"","state":"none","rate":"","history":[]}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log=${XDG_STATE_HOME:-$HOME/.local/state}/eww/battery.log
|
||||
hist_hours=6 buckets=24
|
||||
mkdir -p "${log%/*}" && touch "$log"
|
||||
|
||||
attr() { # first readable, non-empty attribute of the list, else 0
|
||||
local a v
|
||||
for a in "$@"; do
|
||||
@@ -22,39 +29,83 @@ attr() { # first readable, non-empty attribute of the list, else 0
|
||||
|
||||
hours_to_clock() { awk -v h="$1" 'BEGIN { printf "%d:%02d", int(h), int((h - int(h)) * 60 + 0.5) }'; }
|
||||
|
||||
# Append "epoch pct state" and, once the head is an hour past the plot window,
|
||||
# drop everything older than the window.
|
||||
log_sample() {
|
||||
local now=$1 first
|
||||
printf '%s %s %s\n' "$now" "$2" "$3" >> "$log"
|
||||
read -r first _ < "$log"
|
||||
((first < now - (hist_hours + 1) * 3600)) || return 0
|
||||
awk -v min=$((now - hist_hours * 3600)) '$1 >= min' "$log" > "$log.tmp" && mv "$log.tmp" "$log"
|
||||
}
|
||||
|
||||
# %/h over the last 30 min of samples in the current state, signed; empty
|
||||
# until there are 5 min of them.
|
||||
drain_rate() {
|
||||
awk -v now="$1" -v st="$2" '
|
||||
$1 >= now - 1800 && $3 == st { if (!n++) { t0 = $1; p0 = $2 } t1 = $1; p1 = $2 }
|
||||
END { if (n > 1 && t1 - t0 >= 300) printf "%.1f", (p1 - p0) * 3600 / (t1 - t0) }' "$log"
|
||||
}
|
||||
|
||||
# The plot window split into buckets: mean % of the bucket's samples (-1 when
|
||||
# it has none), the state of its last sample, and its start time as label.
|
||||
plot() {
|
||||
awk -v now="$1" -v hours="$hist_hours" -v n="$buckets" '
|
||||
BEGIN { w = hours * 3600 / n; t0 = now - hours * 3600 }
|
||||
$1 >= t0 { b = int(($1 - t0) / w); if (b >= n) b = n - 1; sum[b] += $2; cnt[b]++; st[b] = $3 }
|
||||
END {
|
||||
printf "["
|
||||
for (b = 0; b < n; b++) {
|
||||
p = -1; s = "none"
|
||||
if (cnt[b]) {
|
||||
p = sprintf("%.1f", sum[b] / cnt[b])
|
||||
s = st[b] == "c" ? "charging" : st[b] == "d" ? "discharging" : "idle"
|
||||
if (st[b] == "d" && p < 25) s = "low"
|
||||
if (st[b] == "d" && p <= 15) s = "critical"
|
||||
}
|
||||
printf "%s{\"t\":\"%s\",\"p\":%s,\"s\":\"%s\"}", (b ? "," : ""), strftime("%H:%M", t0 + b * w), p, s
|
||||
}
|
||||
printf "]"
|
||||
}' "$log"
|
||||
}
|
||||
|
||||
state() {
|
||||
local cap status rate now full power time="" wattage state
|
||||
local now cap status rate now_e full power pct st time="" wattage state
|
||||
now=$(printf '%(%s)T' -1)
|
||||
cap=$(attr capacity)
|
||||
status=$(attr status)
|
||||
rate=$(attr power_now current_now) # µW or µA
|
||||
now=$(attr energy_now charge_now) # µWh or µAh — same unit as rate, so now/rate = hours
|
||||
now_e=$(attr energy_now charge_now) # µWh or µAh — same unit as rate, so now_e/rate = hours
|
||||
full=$(attr energy_full charge_full)
|
||||
if [[ -r $bat/power_now ]]; then power=$rate; else power=$((rate * $(attr voltage_now) / 1000000)); fi
|
||||
wattage=$(awk -v p="$power" 'BEGIN { printf "%.1f W", p / 1000000 }')
|
||||
pct=$(awk -v n="$now_e" -v f="$full" -v c="$cap" 'BEGIN { printf "%.1f", (f > 0 ? 100 * n / f : c) }')
|
||||
if ((rate > 0)); then
|
||||
case $status in
|
||||
Discharging) time=$(hours_to_clock "$(awk -v n="$now" -v r="$rate" 'BEGIN { print n / r }')") ;;
|
||||
Charging) time=$(hours_to_clock "$(awk -v n="$now" -v f="$full" -v r="$rate" 'BEGIN { print (f - n) / r }')") ;;
|
||||
Discharging) time=$(hours_to_clock "$(awk -v n="$now_e" -v r="$rate" 'BEGIN { print n / r }')") ;;
|
||||
Charging) time=$(hours_to_clock "$(awk -v n="$now_e" -v f="$full" -v r="$rate" 'BEGIN { print (f - n) / r }')") ;;
|
||||
esac
|
||||
fi
|
||||
case $status in
|
||||
Charging)
|
||||
state=charging
|
||||
state=charging st=c
|
||||
status="Charging${time:+, $time left}"
|
||||
;;
|
||||
Discharging)
|
||||
state=discharging
|
||||
((cap <= 20)) && state=low
|
||||
state=discharging st=d
|
||||
((cap < 25)) && state=low
|
||||
((cap <= 15)) && state=critical
|
||||
status=${time:+$time left}
|
||||
;;
|
||||
Full)
|
||||
state=full
|
||||
state=full st=i
|
||||
status="Fully charged"
|
||||
;;
|
||||
*) state=idle ;;
|
||||
*) state=idle st=i ;;
|
||||
esac
|
||||
printf '{"visible":true,"percentage":%s,"wattage":"%s","status":"%s","state":"%s"}' \
|
||||
"$cap" "$wattage" "$(jstr "$status")" "$state"
|
||||
log_sample "$now" "$pct" "$st"
|
||||
printf '{"visible":true,"percentage":%s,"wattage":"%s","status":"%s","time":"%s","state":"%s","rate":"%s","history":%s}' \
|
||||
"$cap" "$wattage" "$(jstr "$status")" "$time" "$state" "$(drain_rate "$now" "$st")" "$(plot "$now")"
|
||||
}
|
||||
|
||||
refresh() { emit "$(state)"; }
|
||||
|
||||
Reference in New Issue
Block a user