Python Battery Low Alert Desktop Notifier

Written by

in

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: win10toast or winrt for modern toast notifications.
  • macOS: pync (wraps terminal-notifier) or osascript via subprocess.
  • Linux: notify2 or plyer for 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.json that stores threshold, interval, and sound preferences.
  • 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_THRESHOLD to a higher value (e.g.,

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *