Skip to content

Add callstack doctor for safe modem bring-up diagnostics #57

Description

@Justinabox

Motivation

Callstack is moving toward an unattended Raspberry Pi PBX/SMS gateway, but hardware bring-up is still too spooky: users must already know the correct AT/audio ports before callstack status can work. The repo already has conservative pure discovery/profile dataclasses and issue #11 tracks safe modem discovery; this issue carves out a one-PR DX slice: a non-mutating callstack doctor command that produces a redacted modem bring-up report.

User journey

A user plugs in a SIMCOM/Quectel/Huawei/Sierra-class USB modem and runs:

callstack doctor
callstack doctor --json
callstack doctor --ports /dev/ttyUSB0,/dev/ttyUSB1,/dev/ttyUSB2,/dev/ttyUSB3

They get a concise report showing which port looks like the AT command port, whether an audio port candidate was found or remains unknown, a conservative capability summary, and actionable troubleshooting notes. The command must be safe to run on a production SIM: no SMS, no USSD, no calls, no SIM unlock, no storage mutation, and no secret identifiers printed.

API / UX sketch

Human output should be readable and PII-safe, for example:

Callstack doctor
AT port: /dev/ttyUSB2 (confidence: identity-response)
Audio port: unknown
Manufacturer: SIMCOM INCORPORATED
Model: SIMCOM_SIM7600E-H
Capabilities: sms_text_mode=supported, sms_pdu_mode=supported, ussd=supported, voice_calls=supported, pcm_audio=unknown
Notes:
- SIMCom-like identity matched; PCM audio remains unknown until explicit model evidence or a manual probe confirms it.
- No SMS, USSD, call, SIM unlock, or storage commands were sent.

JSON output should serialize the existing ModemDiscoveryReport shape without raw IMEI/IMSI/ICCID/SIM number values.

Technical approach

  • Add a small injectable probe layer, likely callstack/hardware/probe.py, that accepts candidate port names plus an async transport/opener abstraction so tests can use fake ports.
  • Probe only non-mutating identity commands such as AT, ATI, AT+GMI, AT+GMM, and AT+GMR with short timeouts.
  • Reuse ModemIdentity, ModemCapabilities, ModemDiscoveryReport, classify_capabilities(), and profile_notes() from callstack.hardware.*.
  • Add a doctor subcommand in callstack/cli.py next to status and send.
  • Keep current explicit ModemConfig(at_port=..., audio_port=...) behavior unchanged; doctor is diagnostic, not automatic connection magic.
  • Treat full auto-selection/auto-connect as a follow-up to Add safe modem discovery reports and capability profiles #11; this issue only reports safe findings.

Affected modules

  • callstack/cli.py
  • callstack/hardware/discovery.py
  • callstack/hardware/profiles.py
  • New: callstack/hardware/probe.py or similarly scoped helper
  • Tests: tests/test_cli.py, tests/test_hardware_profiles.py, new tests/test_hardware_probe.py
  • Docs: README troubleshooting / CLI section

Hardware / modem caveats

  • Some modems expose multiple serial ports and only one responds reliably to AT identity commands.
  • Audio-port roles are vendor/model-specific and should remain unknown unless there is deterministic profile evidence; do not infer PCM support merely from an extra serial device.
  • Do not query or print sensitive identifiers. If a future probe uses an identifier command for confidence, store only a boolean such as imei_present=True, never the value.
  • No command in this slice may send SMS, USSD, place/answer calls, unlock SIMs, alter CNMI/CMGF/CSMP settings, or delete/read message storage.

Acceptance criteria

  • callstack doctor --json returns a machine-readable redacted discovery report using fake probe data in tests.
  • callstack doctor human output includes AT-port confidence, conservative capabilities, and notes without phone numbers, SMS bodies, SIM identifiers, API keys, or raw IMEI/IMSI/ICCID values.
  • Probe logic is injectable and fully covered by fake transports; tests do not require real serial hardware.
  • Probe command allowlist contains only safe, non-mutating identity/attention commands.
  • Timeouts and failed ports produce actionable notes instead of tracebacks.
  • Existing callstack status and callstack send behavior is unchanged.
  • README documents doctor as the first troubleshooting step before status.

Exact gates

git diff --check
PYTHONPATH=. uv run --no-project --with pytest --with pytest-asyncio --with pytest-aiohttp --with pyserial-asyncio --with aiosqlite pytest tests/test_cli.py tests/test_hardware_profiles.py tests/test_hardware_probe.py -q
PYTHONPATH=. uv run --no-project --with pytest --with pytest-asyncio --with pytest-aiohttp --with pyserial-asyncio --with aiosqlite pytest tests/ -q

Non-goals

  • No automatic modem connection changes in ModemConfig.
  • No active voice/audio loopback tests.
  • No delivery-report, SMS, USSD, SIM PIN/PUK, or call commands.
  • No dashboard or WebSocket surface.

Related work

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions