Ever been in the middle of an important task only to hear that dreaded “battery low” warning pop up at the worst possible moment? While most operating systems provide a basic notification, power users often crave a more customizable, persistent, and cross‑platform solution. In this guide we’ll walk through creating a **Python battery low alert desktop notifier** that works on Windows, macOS, and Linux. You’ll learn how to detect battery status with psutil, send native notifications using libraries like plyer and win10toast, and even add a system‑tray icon for continuous monitoring. By the end, you’ll have a lightweight script you can run in the background, ensuring you never lose unsaved work again.
Why Build Your Own Battery Notifier?
Out‑of‑the‑box battery alerts are often generic and disappear quickly. A custom notifier gives you:
- Full control over thresholds – set warnings at 30%, 20%, or any level you prefer.
- Persistent desktop alerts – keep the warning visible until you acknowledge it.
- Cross‑platform consistency – the same script works on Windows, macOS, and Linux.
- Additional actions – log events, play a sound, or trigger a shutdown script.
Key Python Packages You’ll Need
psutil – Battery Information
The psutil library provides a simple way to query the system’s battery status across all major operating systems.
pip install psutil
Notification Libraries
Depending on your platform, choose the appropriate notifier:
- Windows:
win10toastorwinrtfor modern toast notifications. - macOS:
pync(wrapsterminal-notifier) orosascriptviasubprocess. - Linux:
notify2orplyerfor DBus notifications.
pip install win10toast pync notify2 plyer
Optional: pystray – System Tray Icon
Adding a tray icon lets you pause, resume, or quit the notifier without closing the terminal.
pip install pystray pillow
Step‑by‑Step Implementation
1. Detecting Battery Level with psutil
The psutil.sensors_battery() function returns a namedtuple containing the current charge, power plug status, and estimated time left.
import psutil
def get_battery_status():
battery = psutil.sensors_battery()
if battery is None:
return None # Desktop PC without a battery
return {
"percent": battery.percent,
"plugged": battery.power_plugged,
"secsleft": battery.secsleft
}
2. Sending Native Desktop Notifications
Below is a helper that abstracts the platform‑specific notification calls. It chooses the right library at runtime, keeping the main logic clean.
import platform
from plyer import notification as plyer_notify
def send_notification(title, message):
system = platform.system()
if system == "Windows":
try:
from win10toast import ToastNotifier
toaster = ToastNotifier()
toaster.show_toast(title, message, duration=10, threaded=True)
except Exception:
# Fallback to plyer
plyer_notify.notify(title=title, message=message, timeout=10)
elif system == "Darwin": # macOS
try:
from pync import Notifier
Notifier.notify(message, title=title, sound='default')
except Exception:
plyer_notify.notify(title=title, message=message, timeout=10)
else: # Linux and others
try:
import notify2
notify2.init("Battery Notifier")
n = notify2.Notification(title, message)
n.set_urgency(notify2.URGENCY_CRITICAL)
n.show()
except Exception:
plyer_notify.notify(title=title, message=message, timeout=10)
3. Adding a System Tray Icon (Optional)
The tray icon offers a quick way to stop the script or change the low‑battery threshold without editing the code.
import threading
import pystray
from PIL import Image, ImageDraw
class TrayIcon:
def __init__(self, stop_callback):
self.stop_callback = stop_callback
self.icon = pystray.Icon("BatteryNotifier")
self.icon.icon = self._create_image()
self.icon.title = "Battery Low Notifier"
self.icon.menu = pystray.Menu(
pystray.MenuItem('Quit', self.quit)
)
def _create_image(self):
# Simple red battery icon
img = Image.new('RGB', (64, 64), "white")
d = ImageDraw.Draw(img)
d.rectangle([10, 20, 54, 44], outline="black", fill="red")
d.rectangle([54, 28, 58, 36], outline="black", fill="red")
return img
def run(self):
threading.Thread(target=self.icon.run).start()
def quit(self, icon, item):
self.stop_callback()
self.icon.stop()
4. Main Monitoring Loop
The core loop checks the battery every few seconds, compares it against a configurable threshold, and fires a notification only once per low‑battery event.
import time
LOW_BATTERY_THRESHOLD = 20 # percent
CHECK_INTERVAL = 60 # seconds
def monitor_battery(stop_event):
notified = False
while not stop_event.is_set():
status = get_battery_status()
if status is None:
print("No battery detected. Exiting.")
break
percent = status["percent"]
plugged = status["plugged"]
if not plugged and percent <= LOW_BATTERY_THRESHOLD:
if not notified:
title = "Battery Low"
message = f"Your battery is at {percent}% – plug in your charger!"
send_notification(title, message)
notified = True
else:
# Reset notification flag when battery is charged or plugged in
notified = False
time.sleep(CHECK_INTERVAL)
5. Putting It All Together
Finally, combine the monitoring loop with the optional tray icon. Use a threading.Event to allow graceful shutdown from the tray menu.
import threading
def main():
stop_event = threading.Event()
# Start battery monitor in a separate thread
monitor_thread = threading.Thread(target=monitor_battery, args=(stop_event,))
monitor_thread.start()
# Optional: start system tray icon
tray = TrayIcon(stop_event.set)
tray.run()
# Keep the main thread alive until monitor exits
try:
while monitor_thread.is_alive():
monitor_thread.join(timeout=1)
except KeyboardInterrupt:
stop_event.set()
monitor_thread.join()
if __name__ == "__main__":
main()
Customizing the Notifier for Your Workflow
Once the basic script is running, you can tailor it to match your daily routine. Below are common enhancements and how to implement them.
Persisting Settings with a Config File
- Create a
config.jsonthat storesthreshold,interval, andsoundpreferences. - Load the JSON at startup and fall back to defaults if the file is missing.
import json
import os
CONFIG_PATH = os.path.expanduser("~/.battery_notifier_config.json")
def load_config():
defaults = {"threshold": 20, "interval": 60, "sound": true}
if not os.path.isfile(CONFIG_PATH):
return defaults
with open(CONFIG_PATH, "r") as f:
data = json.load(f)
return {**defaults, **data}
Playing a Custom Alert Sound
Use the built‑in winsound module on Windows or playsound (cross‑platform) to play an audio cue.
from playsound import playsound
def alert_with_sound():
send_notification("Battery Low", "Plug in your charger!")
playsound("/path/to/alert.wav")
Automatic System Actions
If you prefer the notebook to hibernate or shut down when the battery reaches a critical level, add a call to the OS shutdown command.
import subprocess
def critical_shutdown():
system = platform.system()
if system == "Windows":
subprocess.run(["shutdown", "/s", "/t", "60"])
elif system == "Darwin":
subprocess.run(["osascript", "-e", 'tell app "System Events" to shut down'])
else: # Linux
subprocess.run(["shutdown", "-h", "+1"])
Testing and Debugging Tips
- Simulate low battery: Manually set
LOW_BATTERY_THRESHOLDto a higher value (e.g.,
Leave a Reply