Skip to main content

Unofficial Python client for Arris cable modem status via HNAP

Project description

arris-modem-status 🚀

Quality Check codecov PyPI version Python versions License: MIT Buy Me A Coffee

I got tired of logging into my Arris cable modem's clunky web interface just to check signal levels. So, with the help of AI (Claude), I reverse-engineered the modem's API and built this Python library!

What's This Thing Do? 🤔

It grabs ALL the juicy details from your Arris S34 (and likely S33/SB8200) cable modem:

  • 📊 Signal levels, SNR, error counts
  • 🌊 Downstream/upstream channel info
  • 🔧 Model name, firmware version, hardware version
  • ⏰ System uptime (e.g., "27 day(s) 10h:12m:37s")
  • 🔒 Boot status, security status, connectivity state
  • ⚡ And it's FAST (< 2 seconds in serial mode)

Quick Start 🏃‍♂️

# Install the latest version (v1.0.2)
pip install arris-modem-status

# Check your modem (serial mode by default for reliability)
arris-modem-status --password YOUR_PASSWORD

# Get JSON for your monitoring setup
arris-modem-status --password YOUR_PASSWORD --quiet | jq

# Use parallel mode if your modem supports it (30% faster but may fail)
arris-modem-status --password YOUR_PASSWORD --parallel

Python Usage 🐍

from arris_modem_status import ArrisModemStatusClient

# Serial mode by default (recommended)
with ArrisModemStatusClient(password="YOUR_PASSWORD") as client:
    status = client.get_status()

    print(f"Modem: {status['model_name']}")
    print(f"Firmware: {status['firmware_version']}")
    print(f"Uptime: {status['system_uptime']}")

    # Check signal levels
    for channel in status['downstream_channels']:
        print(f"Channel {channel.channel_id}: {channel.power} / SNR: {channel.snr}")

# Use concurrent mode if your modem handles it well
with ArrisModemStatusClient(password="YOUR_PASSWORD", concurrent=True) as client:
    status = client.get_status()  # ~30% faster but may get HTTP 403 errors

Serial vs Parallel Mode ⚠️

DEFAULT: Serial mode - Requests are made one at a time. Slower but much more reliable.

Many Arris modems have buggy HNAP implementations that return HTTP 403 errors when handling concurrent requests. This causes inconsistent data like:

  • Sometimes getting model name, sometimes not
  • Missing internet status randomly
  • Partial channel information

If you want to try parallel mode for speed (at your own risk):

arris-modem-status --password YOUR_PASSWORD --parallel

Complete Data Retrieved 📦

The library retrieves ALL available modem information, but the output format differs depending on how you use it:

Command Line Interface Output

When using the CLI, you get both human-readable summaries (to stderr) and structured JSON (to stdout):

Human-readable summary (stderr):

============================================================
ARRIS MODEM STATUS SUMMARY
============================================================
Model: S34
Hardware Version: 1.0
Firmware: AT01.01.010.042324_S3.04.735
Uptime: 27 day(s) 10h:12m:37s
Uptime (days): 27.4
Connection Status:
  Internet: Connected
  Network Access: Allowed
  Boot Status: OK
  Security: Enabled (BPI+)
Downstream Status:
  Frequency: 549000000 Hz
  Comment: Locked
System Information:
  MAC Address: 01:23:45:67:89:AB
  Serial Number: 000000000000000
  Current Time: 07/30/2025 23:31:23
  Current Time (ISO): 2025-07-30T23:31:23
Channel Summary:
  Downstream Channels: 32
  Upstream Channels: 8
  Channel Data Available: true
  Sample Channel: ID 1, 549000000 Hz, 0.6 dBmV, SNR 39.0 dB, Errors: 15/0
============================================================

JSON output (stdout) with CLI metadata:

{
  "model_name": "S34",
  "hardware_version": "1.0",
  "firmware_version": "AT01.01.010.042324_S3.04.735",
  "system_uptime": "31 day(s) 03h:42m:48s",
  "system_uptime-seconds": 2691768.0,
  "current_system_time": "08/03/2025 17:02:43",
  "current_system_time-ISO8601": "2025-08-03T17:02:43",
  "mac_address": "01:23:45:67:89:AB",
  "serial_number": "000000000000000",
  "internet_status": "Connected",
  "network_access": "Allowed",
  "boot_status": "OK",
  "boot_comment": "Operational",
  "configuration_file_status": "OK",
  "security_status": "Enabled",
  "security_comment": "BPI+",
  "downstream_frequency": "549000000 Hz",
  "downstream_comment": "Locked",
  "downstream_channels": [
    {
      "channel_id": "1",
      "frequency": "549000000 Hz",
      "power": "0.6 dBmV",
      "snr": "39.0 dB",
      "modulation": "256QAM",
      "lock_status": "Locked",
      "corrected_errors": "15",
      "uncorrected_errors": "0",
      "channel_type": "downstream"
    }
  ],
  "upstream_channels": [
    {
      "channel_id": "1",
      "frequency": "30600000 Hz",
      "power": "46.5 dBmV",
      "snr": "N/A",
      "modulation": "SC-QAM",
      "lock_status": "Locked",
      "channel_type": "upstream"
    }
  ],
  "query_timestamp": "2025-08-03T15:30:45",
  "query_host": "192.168.100.1",
  "client_version": "1.0.0",
  "elapsed_time": 1.85,
  "configuration": {
    "max_workers": 2,
    "max_retries": 3,
    "timeout": [5, 15],
    "concurrent_mode": false,
    "http_compatibility": true,
    "quick_check_performed": false
  }
}

Python Library Output

When using the Python library directly, you get a cleaner dictionary focused on modem data:

from arris_modem_status import ArrisModemStatusClient

with ArrisModemStatusClient(password="your_password") as client:
    status = client.get_status()
    print(status)

Returns:

{
  'model_name': 'S34',
  'hardware_version': '1.0',
  'firmware_version': 'AT01.01.010.042324_S3.04.735',
  'system_uptime': '31 day(s) 03h:42m:48s',
  'system_uptime-datetime': datetime.timedelta(days=31, seconds=13368), # Python datetime.timedelta object
  'system_uptime-seconds': 2691768.0,  # Automatically parsed
  'current_system_time': '08/03/2025 17:02:43',
  'current_system_time-ISO8601': '2025-08-03T17:02:43',  # Auto-formatted
  'current_system_time-datetime': datetime.datetime(2025, 8, 3, 17, 2, 43),  # Python datetime.datetime object
  'mac_address': '01:23:45:67:89:AB',
  'serial_number': '000000000000000',
  'internet_status': 'Connected',
  'network_access': 'Allowed',
  'boot_status': 'OK',
  'boot_comment': 'Operational',
  'connectivity_status': 'OK',
  'connectivity_comment': 'Operational',
  'configuration_file_status': 'OK',
  'security_status': 'Enabled',
  'security_comment': 'BPI+',
  'downstream_frequency': '549000000 Hz',
  'downstream_comment': 'Locked',
  'downstream_channels': [
    ChannelInfo(
      channel_id='1',
      frequency='549000000 Hz',
      power='0.6 dBmV',
      snr='39.0 dB',
      modulation='256QAM',
      lock_status='Locked',
      corrected_errors='15',
      uncorrected_errors='0',
      channel_type='downstream'
    )  # ... more channels
  ],
  'upstream_channels': [
    ChannelInfo(
      channel_id='1',
      frequency='30600000 Hz',
      power='46.5 dBmV',
      snr='N/A',
      modulation='SC-QAM',
      lock_status='Locked',
      channel_type='upstream'
    )  # ... more channels
  ],
  'channel_data_available': True,
  '_request_mode': 'serial',  # Internal metadata
  '_performance': {
    'total_time': 1.85,
    'requests_successful': 4,
    'requests_total': 4,
    'mode': 'serial'
  }
}

Key Differences

Feature CLI Output Python Library
Human Summary ✅ Printed to stderr ❌ Not included
CLI Metadata ✅ Query info, host, version ❌ Not included
Channel Objects ❌ Serialized to dicts ✅ Rich ChannelInfo objects
Time Parsing ✅ Enhanced fields ✅ Enhanced fields
Performance Data ✅ Configuration details ✅ Basic timing info
Monitoring Ready ✅ JSON with metadata ✅ Python objects

Both formats include automatically parsed time fields (like system_uptime-seconds) and enhanced data, but the CLI adds operational metadata while the Python library provides rich objects for programmatic use.

The Cool Technical Bits 🤓

I spent way too much time figuring out:

  • 🔐 The HNAP authentication (challenge-response with HMAC-SHA256)
  • 🏎️ Why concurrent requests fail (modem firmware bugs causing HTTP 403)
  • 🛡️ HTTP compatibility quirks (urllib3 is... picky)
  • 📦 Complete HNAP request mapping (including the missing GetCustomerStatusSoftware!)
  • 🐛 Why data was inconsistent (partial request failures in concurrent mode)

Monitoring Integration 📈

Perfect for Grafana, Prometheus, or any monitoring stack:

# Quick Prometheus exporter example
from prometheus_client import Gauge
downstream_power = Gauge('arris_downstream_power_dbmv', 'Power', ['channel'])

# Update metrics
for ch in status['downstream_channels']:
    downstream_power.labels(channel=ch.channel_id).set(float(ch.power.split()[0]))

Disclaimer

This is an unofficial library not affiliated with ARRIS® or CommScope, Inc. ARRIS® is a registered trademark of CommScope, Inc.

This is a personal project provided as-is under the MIT license.

Is my modem supported? ☎️

I tested this on an Arris S34 (running the "AT01.01.010.042324_S3.04.735" firmware on Comcast in the USA), so chances are good that it will work on its older sibbling the S33. I hope to validate the SB8200 in the near future, but otherwise if it's an Arris modem it may work, or not 🤷🏻. I'm open to helping to triage files, but the procedure will be a little loosey goosey until I do one or two.

Found a Bug? Want a Feature? 🐛

Open an issue! PRs welcome! The codebase is pretty clean thanks to the AI helping me follow best practices.

The Story 📖

I started this because I'm obsessive about my internet connection quality (aren't we all?). After discovering the modem had an API, I went down a rabbit hole of reverse engineering with Claude as my copilot.

Fun discoveries:

  • The modem returns the same data in multiple HNAP responses (redundancy FTW)
  • Many modems can't handle concurrent requests (firmware bugs)
  • The missing firmware version was in GetCustomerStatusSoftware all along
  • Serial mode is more reliable than parallel for most modems

Requirements 📋

License 📄

MIT - Use it, modify it, monitoring-ify it!


Built with 🧠 + 🤖 by Charles Marshall | GitHub

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

arris_modem_status-1.0.2.tar.gz (158.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

arris_modem_status-1.0.2-py3-none-any.whl (131.2 kB view details)

Uploaded Python 3

File details

Details for the file arris_modem_status-1.0.2.tar.gz.

File metadata

  • Download URL: arris_modem_status-1.0.2.tar.gz
  • Upload date:
  • Size: 158.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.13

File hashes

Hashes for arris_modem_status-1.0.2.tar.gz
Algorithm Hash digest
SHA256 703ade407bde4361b74c331a25ef56f6ef7b0becb9e2cdfc4d56809e3dd50292
MD5 2b40aa43a91dc902bde3940a7a1ffaa0
BLAKE2b-256 504d871f9a3e6dcbcb9a97fd6fd1cd7d285ecfbfbdea548fc4a4890ac280f4f0

See more details on using hashes here.

File details

Details for the file arris_modem_status-1.0.2-py3-none-any.whl.

File metadata

File hashes

Hashes for arris_modem_status-1.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 c8d12261c6bf33e8aa068b0bb6f5d98eb0052a842dfe5c2c3e54bc4670939b85
MD5 80eaee828ea789ee80d52c9b29d8c5ae
BLAKE2b-256 6cd2b46f0eb813bc5b402f8d8b9cc81ff1fa790a420bf9e21a9ac15381ca8388

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page