Skip to content

Adding Modem Support

Dennis Braun edited this page Mar 10, 2026 · 11 revisions

Adding Modem Support

DOCSight supports multiple cable modems through a class-based driver architecture. Here's how to add support for yours.

Supported Modems

Modem Driver Key Auth Method
Arris CM3500B cm3500 Form POST (IP-based session, HTTPS required)
Netgear CM3000 cm3000 Direct status page, with Login.htm form fallback on newer firmware
AVM FRITZ!Box (Cable) fritzbox SID-based session
Technicolor TC4400 tc4400 HTTP Basic Auth
Unitymedia Connect Box (CH7465) ch7465 Session-based
Vodafone Ultra Hub 7 ultrahub7 AES-CCM + PBKDF2
Vodafone Station (CGA/TG) vodafone_station Double PBKDF2 / AES-CCM (auto-detected)
Arris SURFboard (S33/S34/SB8200) surfboard HNAP1 HMAC-SHA256 (HTTPS required)
Arris Touchstone CM8200A cm8200 Base64 query string (IP-based session, HTTPS required)
Sagemcom F@st 3896 sagemcom SHA-512 session (XMO JSON-RPC)
Generic Router (No DOCSIS) generic None (no credentials needed)

Architecture (v2)

DOCSight v2 uses a class-based driver model:

  1. ModemDriver (app/drivers/base.py) -- Abstract base class defining the interface
  2. Driver implementations (app/drivers/*.py) -- One file per modem, inherits from ModemDriver
  3. Driver registry (app/drivers/registry.py) -- DriverRegistry class that maps driver keys to classes (built-in + module-contributed)
  4. Analyzer (app/analyzer.py) -- Transforms raw driver output into standardized format

The analyzer, storage, web UI, MQTT, and all other components work with the standardized output and don't need to know which modem produced the data.

Creating a New Driver

1. Create the Driver Class

Create app/drivers/your_modem.py:

"""Your Modem driver for DOCSight."""

import logging
import requests
from .base import ModemDriver

log = logging.getLogger("docsis.driver.your_modem")


class YourModemDriver(ModemDriver):
    """Driver for Your Modem DOCSIS 3.1 cable modem."""

    def __init__(self, url: str, user: str, password: str):
        super().__init__(url, user, password)
        self._session = requests.Session()

    def login(self) -> None:
        """Authenticate with the modem. Called before each poll cycle."""
        # Implement your auth flow here
        # Raise RuntimeError on failure
        pass

    def get_docsis_data(self) -> dict:
        """Retrieve DOCSIS channel data."""
        # Must return the format described below
        pass

    def get_device_info(self) -> dict:
        """Retrieve device model and firmware info."""
        return {
            "manufacturer": "Your Manufacturer",
            "model": "Your Model",
            "sw_version": "",
        }

    def get_connection_info(self) -> dict:
        """Retrieve internet connection info (speeds, type).

        Return empty dict if not available (standalone modems).
        """
        return {}

2. Return Format for get_docsis_data()

Your driver must return a dict with channel data. There are two supported formats:

Format A: Pre-split by DOCSIS version (preferred for mixed-mode modems)

Use this when your modem has both SC-QAM (DOCSIS 3.0) and OFDM/OFDMA (DOCSIS 3.1) channels. This ensures correct SNR interpretation (MSE for 3.0, MER for 3.1) and proper upstream power thresholds.

{
    "channelDs": {
        "docsis30": [
            # SC-QAM downstream channels
            {"channelID": "1", "type": "qam_256", "frequency": "602 MHz",
             "powerLevel": 3.2, "mse": -37.5, "mer": 37.5,
             "latency": 0, "corrError": 150, "nonCorrError": 5},
        ],
        "docsis31": [
            # OFDM downstream channels
            {"channelID": "33", "type": "ofdm", "frequency": "763 MHz",
             "powerLevel": 5.1, "mse": None, "mer": 39.0,
             "latency": 0, "corrError": 0, "nonCorrError": 0},
        ],
    },
    "channelUs": {
        "docsis30": [
            # SC-QAM upstream channels
            {"channelID": "1", "type": "qam_64", "frequency": "37 MHz",
             "powerLevel": 42.0, "multiplex": ""},
        ],
        "docsis31": [
            # OFDMA upstream channels
            {"channelID": "5", "type": "ofdma", "frequency": "54 MHz",
             "powerLevel": 38.0, "multiplex": ""},
        ],
    },
}

Format B: Flat lists with single DOCSIS version

Use this only when all channels are the same DOCSIS version (e.g., pure DOCSIS 3.0 modem). The analyzer will place all channels into a single bucket.

{
    "docsis": "3.0",  # or "3.1"
    "downstream": [
        {"channelID": "1", "type": "qam_256", "frequency": "602 MHz",
         "powerLevel": 3.2, "mse": -37.5, "mer": 37.5,
         "latency": 0, "corrError": 150, "nonCorrError": 5},
    ],
    "upstream": [
        {"channelID": "1", "type": "qam_64", "frequency": "37 MHz",
         "powerLevel": 42.0, "multiplex": ""},
    ],
}

Key conventions:

  • Frequency as string with MHz unit (e.g. "602 MHz")
  • Power as float in dBmV
  • SC-QAM downstream: set both mse (negative) and mer (positive)
  • OFDM downstream: set mse to None, only mer
  • Modulation normalized: qam_256, qam_64, ofdm, ofdma, atdma
  • For mixed-mode modems, always use Format A to avoid incorrect DOCSIS version classification

3. Register the Driver

Built-in drivers: Add your driver to the _BUILTINS dict in app/drivers/registry.py:

_BUILTINS = {
    # ... existing drivers ...
    "your_modem": "app.drivers.your_modem.YourModemDriver",
}

Community drivers: You don't need to edit any DOCSight source files. Instead, package your driver as a module with "contributes": {"driver": "driver.py:YourModemDriver"} in manifest.json. See Driver Modules for details.

4. Add UI Defaults (optional)

If your modem has a known default IP or username, add defaults to toggleUsernameField() in both app/templates/setup.html and app/templates/settings.html:

} else if(modemType === 'your_modem') {
    if(!urlField.value || urlField.value === 'http://192.168.178.1') {
        urlField.value = 'http://YOUR.DEFAULT.IP';
    }
    if(!usernameField.value) {
        usernameField.value = 'admin';
    }
    usernameField.placeholder = 'admin';
}

5. Add Dependencies

If your driver needs additional Python packages (e.g. beautifulsoup4 for HTML parsing), add them to requirements.txt.

6. Verify

# Import check
python -c "from app.drivers.your_modem import YourModemDriver"

# Docker build
docker build -t docsight:test .

Tips

  • Timeouts: Some modems are slow (TC4400 takes ~20s). Set appropriate timeouts (default in other drivers: 10s).
  • Session management: Invalidate session state on errors so the next poll cycle triggers a fresh login.
  • Only locked channels: Skip channels that aren't in "Locked" state.
  • Frequency units: Modems report frequencies in Hz, kHz, or MHz. Normalize to MHz in your driver.
  • Error handling: Raise RuntimeError with descriptive messages. The polling loop catches these and retries on the next cycle.

Reference Implementations

Driver Complexity Good example for
app/drivers/fritzbox.py Simple Wrapping an existing module
app/drivers/cm3500.py Medium HTML scraping, HTTPS enforcement, mixed DOCSIS 3.0/3.1
app/drivers/tc4400.py Medium HTML scraping, Basic Auth
app/drivers/ch7465.py Medium Session-based auth
app/drivers/ultrahub7.py Complex Encrypted auth, JSON API
app/drivers/surfboard.py Medium HNAP JSON API, HMAC-SHA256 auth, HTTPS enforcement
app/drivers/vodafone_station.py Complex Multi-variant auto-detection

Contributing a Driver via Module

Since v2026-03-03, you can contribute modem support as a community module without modifying DOCSight core. This is the recommended approach for third-party drivers.

  1. Create a Python file with your ModemDriver subclass (same interface as above)
  2. Create a manifest.json with "type": "driver" and "contributes": {"driver": "yourfile.py:YourClass"}
  3. Test locally by mounting the module directory (see Installing Community Modules)
  4. Share via the Community Module Registry

Module drivers take priority over built-in drivers with the same key. For security, driver modules cannot also contribute collectors or publishers.

See Driver Modules for full documentation.

Wanted Drivers

Modem Notes
Sagemcom F@st series Common ISP router in several EU countries
SNMP generic driver For modems exposing DOCSIS MIBs via SNMP

Want to work on one of these? Open an issue to discuss the approach first!

Clone this wiki locally