Battery notifications
When you’re working with an external monitor and the lid closed, it’s easy to miss that the power adapter disconnected — and in an hour the laptop will go to sleep. This can be solved with a single script.
Linux: systemd timer
Section titled “Linux: systemd timer”Script ~/bin/notifly-battery:
#!/usr/bin/env bashset -euset -a; source ~/.notifly.env; set +a
LEVEL=$(cat /sys/class/power_supply/BAT0/capacity)STATUS=$(cat /sys/class/power_supply/BAT0/status)STATE_FILE=/tmp/notifly-battery.statePREV=$(cat "$STATE_FILE" 2>/dev/null || echo "Full:100")
PREV_STATUS="${PREV%%:*}"PREV_LEVEL="${PREV##*:}"
send() { ~/bin/notifly-send "$1" "$2" "$3"}
# Low battery (only when discharging).if [[ "$STATUS" == "Discharging" ]]; then if (( LEVEL <= 5 )) && (( PREV_LEVEL > 5 )); then send "🪫 Батарея 5%" "Срочно подключайте зарядку!" 9; fi if (( LEVEL <= 15 )) && (( PREV_LEVEL > 15 )); then send "🔋 Батарея 15%" "Скоро понадобится зарядка." 6; fi if (( LEVEL <= 30 )) && (( PREV_LEVEL > 30 )); then send "🔋 Батарея 30%" "Заряд снижается." 3; fifi
# AC status change (adapter disconnected/connected).if [[ "$STATUS" != "$PREV_STATUS" ]]; then case "$STATUS" in Discharging) send "⚡ Зарядка отключена" "Текущий уровень: ${LEVEL}%" 5 ;; Charging) send "🔌 Зарядка подключена" "Текущий уровень: ${LEVEL}%" 2 ;; Full) send "✅ Батарея 100%" "Можно отключать." 3 ;; esacfi
echo "$STATUS:$LEVEL" > "$STATE_FILE"Make it executable and set up a systemd timer to run every 60 seconds:
[Unit]Description=Notifly battery check
[Service]Type=oneshotExecStart=%h/bin/notifly-battery[Unit]Description=Run notifly-battery every minute
[Timer]OnBootSec=2minOnUnitActiveSec=60s
[Install]WantedBy=timers.targetsystemctl --user daemon-reloadsystemctl --user enable --now notifly-battery.timermacOS: launchd + pmset
Section titled “macOS: launchd + pmset”#!/usr/bin/env bashset -eu; set -a; source ~/.notifly.env; set +aLEVEL=$(pmset -g batt | grep -Eo "[0-9]+%" | head -1 | tr -d '%')SRC=$(pmset -g batt | head -1 | grep -Eo "AC|Battery")STATE=/tmp/notifly-battery.statePREV=$(cat "$STATE" 2>/dev/null || echo "AC:100")# ... same logic as for Linuxecho "$SRC:$LEVEL" > "$STATE"~/Library/LaunchAgents/com.notifly.battery.plist:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict> <key>Label</key> <string>com.notifly.battery</string> <key>ProgramArguments</key> <array><string>/Users/you/bin/notifly-battery</string></array> <key>StartInterval</key> <integer>60</integer></dict></plist>launchctl load ~/Library/LaunchAgents/com.notifly.battery.plist.
Windows: PowerShell + Task Scheduler
Section titled “Windows: PowerShell + Task Scheduler”$ErrorActionPreference = "Stop"$cfg = Get-Content "$env:USERPROFILE\.notifly.json" | ConvertFrom-Json$bat = (Get-CimInstance Win32_Battery)$level = $bat.EstimatedChargeRemaining$onBattery = ($bat.BatteryStatus -eq 1)
$state = "$env:TEMP\notifly-bat.txt"$prev = if (Test-Path $state) { Get-Content $state } else { "AC:100" }$prevLevel = [int]($prev.Split(":")[1])
function Send($title, $msg, $pri) { Invoke-RestMethod -Method Post -Uri "$($cfg.url)/message?token=$($cfg.token)" ` -ContentType "application/json" ` -Body (@{ title = $title; message = $msg; priority = $pri } | ConvertTo-Json)}
if ($onBattery) { if ($level -le 5 -and $prevLevel -gt 5) { Send "🪫 Батарея 5%" "СРОЧНО!" 9 } if ($level -le 15 -and $prevLevel -gt 15) { Send "🔋 Батарея 15%" "Подключите зарядку." 6 }}"$(if($onBattery){'BAT'}else{'AC'}):$level" | Set-Content $stateIn Task Scheduler — run every minute as your user.
Benefits
Section titled “Benefits”- You won’t lose a work day due to an accidental discharge.
- You know when the power adapter disconnected — no need to move the laptop to check the indicator.
- At priority 9 the push arrives even in DND.
Further improvements
Section titled “Further improvements”- Attach an estimate of remaining time to the message (
pmset -g batt,upower -i). - Separate scripts for a laptop and for a computer with UPS.