arris-modem-status 🚀
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 📋
- Python 3.9+
- An Arris S34 (and likely S33/SB8200) cable modem
- The admin password by default the last 8 digits of your modem's serial number
- Patience if your modem hates concurrent requests
License 📄
MIT - Use it, modify it, monitoring-ify it!
Built with 🧠 + 🤖 by Charles Marshall | GitHub
Metadata
Release files for arris-modem-status 1.0.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| arris_modem_status-1.0.2.tar.gz | 158.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| arris_modem_status-1.0.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 289.8 kB
Release files / arris_modem_status-1.0.2.tar.gz
| Download URL | arris_modem_status-1.0.2.tar.gz |
|---|---|
| Size | 158.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
703ade407bde4361b74c331a25ef56f6ef7b0becb9e2cdfc4d56809e3dd50292
|
|
BLAKE2b-256 checksum How to use checksums |
504d871f9a3e6dcbcb9a97fd6fd1cd7d285ecfbfbdea548fc4a4890ac280f4f0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.11.13
|
Release files / arris_modem_status-1.0.2-py3-none-any.whl
| Download URL | arris_modem_status-1.0.2-py3-none-any.whl |
|---|---|
| Size | 131.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c8d12261c6bf33e8aa068b0bb6f5d98eb0052a842dfe5c2c3e54bc4670939b85
|
|
BLAKE2b-256 checksum How to use checksums |
6cd2b46f0eb813bc5b402f8d8b9cc81ff1fa790a420bf9e21a9ac15381ca8388
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.1.0 CPython/3.11.13
|