Minimal, standalone demos of each modem communication primitive, built directly
on agentcall.modem.Eg25Modem. Each script does exactly one thing so you can learn
and verify a single building block in isolation — before wiring them into your own
app or debugging the full CallPilot service.
These are the atoms; the full application (app.py) is what you get when you compose
them (auto-answer + AI agent + audio bridge + web UI).
| Script | Capability | Needs a live call? |
|---|---|---|
at_console.py |
Send any raw AT command, print the reply (the lowest-level atom) | no |
probe_device.py |
Self-check: SIM, signal, network, voice channel, firmware | no |
dial_call.py |
Place an outbound call, wait for answer, hold, hang up | — |
answer_call.py |
Wait for RING, auto-answer (ATA), hold until the other side hangs up |
— |
send_sms.py |
Send one SMS (Chinese auto-encoded as UCS2) | no |
receive_sms.py |
Listen for and print incoming SMS (+CMTI → read → decode) |
no |
send_dtmf.py |
Send DTMF key tones for IVR navigation (AT+QVTS) |
yes |
- Install the package (editable), from the repo root:
python -m venv .venv .venv/bin/pip install -e ".[dev]" - Hardware: a Quectel EC20/EG25 modem with an active SIM (voice + SMS).
- Config: copy
.env.exampleto.env. The examples readMODEM_PORT/MODEM_BAUDfrom it (same source of truth as the app). - macOS only — the AT port is not a native device; start the USB→PTY bridge
first, in a separate terminal, and leave it running:
Then set
.venv/bin/python scripts/ec20_usb_pty.py --map 2:/tmp/ec20-at
MODEM_PORT=/tmp/ec20-atin.env(this is the macOS default). - Do not run these while the main app owns the serial port. Stop the service
first (e.g.
launchctl stop com.agentcall.app, or just don't runapp.py) — two processes cannot share the AT port. The USB bridge can stay running.
From the repo root:
.venv/bin/python examples/modem/probe_device.py
.venv/bin/python examples/modem/at_console.py "AT+CSQ"
.venv/bin/python examples/modem/dial_call.py 10000 20
.venv/bin/python examples/modem/send_sms.py 10086 CXLLanswer_call.py and receive_sms.py run until you press Ctrl-C.
dial_call.pyandsend_sms.pyperform real telephony and may incur carrier charges. Only dial/text numbers you are authorized to contact (your own phone, a carrier service hotline such as10000/10086, etc.).send_dtmf.pyonly does something useful during an active call — pair it withdial_call.py.
scripts/eg25_probe.py— broader device probe (scans all serial ports).scripts/uac_check.py— verify the modem's UAC sound card works both directions.scripts/ec20_usb_pty.py— the macOS USB→PTY bridge these examples depend on.