ChannelWatch: Solving the "Notification Fatigue" Problem in Home Media

How a stateful Python middleware transforms volatile DVR event streams into high-signal alerts for the modern home lab.

• View on GitHub • More from CoderLuii

A vintage telegraph operator drowning in identical paper slips, illustrating the chaotic nature of raw, stateless event notifications.
Stateless webhooks often result in notification storms when network conditions fluctuate.

I was a Channels DVR user who wanted real-time monitoring and alerts. Nothing existed. So I built it. Late nights, learning Python and Docker by doing, failing and iterating until it worked. That project taught me more than any course — and watching others use it was the best feeling I'd had in tech.

CoderLuii, Project Creator · CoderLuii

Key Takeaways

The Curse of the Chatty Stream

Home automation often falls into a predictable trap: you set up a webhook to alert you when an event occurs, and within a week, you mute the channel. The problem is volume. A single user watching a movie on a Channels DVR server can trigger dozens of "Stream Started" and "Stream Stopped" events simply by pausing, buffering, or experiencing a minor network hiccup.

Most monitoring scripts are stateless. They see an event, format a string, and fire a POST request. ChannelWatch takes a different approach by introducing a stateful middleware layer that understands the context of a viewing session.

Hedcut portrait of CoderLuii, creator of ChannelWatch.

Architecture of a "Memory"

The core of ChannelWatch's anti-fatigue mechanism lives in core/alerts/common/session_manager.py. By implementing a thread-safe locking mechanism and Time-To-Live (TTL) tracking, the system maintains a "memory" of active streams.

When a new event arrives, the Session Manager checks if a session for that specific device and user already exists. If it does, and the event is just a minor state change, the notification is suppressed. A CleanupMixin works in the background to gracefully age out stale sessions, ensuring memory isn't leaked when a client disconnects unexpectedly.

The Session Manager intercepts raw events and uses a TTL-based locking mechanism to deduplicate notifications.

A close-up of a sophisticated mechanical sorting gate turning messy sparks into a polished diamond.
The Session Manager acts as a firewall of logic, filtering out noise.

From Raw Bytes to Rich Metadata

Raw DVR events are barebones. A payload might only indicate that a client is watching channel "6001". To make this useful, ChannelWatch employs a ChannelInfoProvider. This module intercepts the raw event, queries the DVR's API for the full channel lineup, caches the result using a thread-safe lock, and enriches the payload with program titles and high-resolution logos.

def format_message(self, data: dict, alert_type: str) -> str:
    # Ordered composition ensures consistent UI
    order = ['channel', 'program', 'device', 'ip']
    parts = []
    for key in order:
        if key in data and self.config.get(f'show_{key}'):
            parts.append(self._format_part(key, data[key]))
    return '\n'.join(parts)

The Modernization of the Home Lab

Early versions of ChannelWatch relied heavily on environment variables for configuration. As the project evolved to handle more complex routing rules, this became unmanageable. Version 0.6 introduced a significant architectural pivot: wrapping the Python engine in a Next.js and FastAPI dashboard, orchestrated via Supervisord within a single Docker container.

FeatureStateless WebhooksChannelWatch
Event HandlingFires on every triggerDeduplicates via Session Manager
MetadataRaw IDs (e.g., ch6001)Enriched (Titles, Logos, Resolution)
ConfigurationEnvironment Variables / JSONNext.js Web UI
Resource TrackingNoneDisk space and VOD progress monitoring

By bringing "Tautulli-level" maturity to the Channels DVR ecosystem, ChannelWatch demonstrates how thoughtful state management can transform a noisy data stream into a polished, high-signal observability tool.