Skip to content
chrisdeweesePublic

About

Know when only one AirPod is charging with this 112-line macOS launch agent.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

9 Commits

Folders and files

Repository files navigation

Oddpod

One charged AirPod is zero AirPods.

A bud doesn't seat, only one charges, and you find out in the car. Oddpod notices in two minutes and tells you.

CI License: MIT Platform: macOS Python 3

macOS notification reading "Chris's AirPods Pro — Right AirPod is NOT charging (67%) — left went 83%→91%"

Quick start

git clone https://github.com/chrisdeweese/oddpod
cd oddpod
./scripts/install.sh

That's it — no dependencies, no build. Needs /usr/bin/python3, which arrives with the Command Line Tools (xcode-select --install); the installer checks and stops if it's missing rather than failing silently later.

One thing to check: notifications are delivered by Script Editor, so it must be enabled under System Settings → Notifications. Otherwise Oddpod fires silently forever.

Uninstall with ./scripts/install.sh --uninstall.

What it does

Every two minutes, a launch agent reads the left and right AirPod battery percentages — the same ones your battery widget shows — and keeps twenty minutes of history. When one bud climbs at least 3% while the other hasn't moved at all, that's not a slow charge: that's a bud that isn't seated. You get one native notification, at most once an hour, re-armed when the pair comes back in sync.

Why not just look at the widget

The failure is silent, and the moment you'd catch it is the moment you stopped paying attention. The case clicks the same either way. Nothing distinguishes "both charging" from "one charging" unless you go looking — and you only look after it's cost you.

Tuning

Constants at the top of oddpod.py. Edit and re-run ./scripts/install.sh.

Constant Default Meaning
WINDOW 20 min How much history is kept
MIN_SPAN 10 min History required before it will judge
RISE 3% Climb that counts as charging
FULL 95% A bud this high is done, not stuck
COOLDOWN 60 min Minimum gap between repeat alerts

Poll interval lives in scripts/com.oddpod.watch.plist (StartInterval, seconds).

Raise RISE or MIN_SPAN if you get false alarms; lower them if it's too slow to notice. AirPods report in coarse steps, so very short windows are unreliable.

Security model

Deliberately boring, because it runs forever:

  • No network access. It never opens a socket.
  • No telemetry, no analytics, no account.
  • No admin privileges. No helper tool, no privilege escalation, no kernel extension. It runs as you, in your login session.
  • Reads one read-only command: system_profiler SPBluetoothDataType -json.
  • Writes one file: ~/.oddpod.json, holding twenty minutes of percentages.
  • No menu bar icon, no window, no dock item.

The whole thing is under 100 lines in a single file. Reading it takes less time than reading this README.

Known limits

macOS exposes no charging flag for AirPods, so Oddpod infers charging from rising percentages. It needs about ten minutes of history before it will call a bud stuck, and it errs toward silence: a genuinely stuck bud that reads identically to a full one is invisible to it.

Requires a device that macOS reports separate left and right batteries for — AirPods and AirPods Pro. AirPods Max report a single battery and are skipped.

Troubleshooting

launchctl list | grep oddpod      # should print: -  0  com.oddpod.watch
cat /tmp/oddpod.err               # stderr from the agent
python3 oddpod.py                 # run a poll by hand
python3 oddpod.py --selftest      # exercise the detection logic
cat ~/.oddpod.json                # what it has observed so far

No notification arriving? Check Script Editor under System Settings → Notifications first — that's the usual cause. Delete ~/.oddpod.json to reset history.

Contributing

Issues and pull requests welcome. python3 oddpod.py --selftest must pass; add an assertion to selftest() for any change to the detection logic. Keep it one file with no dependencies.

No warranty

Oddpod is provided as is, with no warranty of any kind, and its authors are not liable for any damages arising from its use — see LICENSE for the binding text.

In plain terms: it is a best-effort convenience, not a guarantee. Don't make it the only thing standing between you and a dead AirPod.

License

MIT — see LICENSE. Design principles behind the tool and site are in DESIGN.md.

About

Know when only one AirPod is charging with this 112-line macOS launch agent.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages