Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Hayes modem emulation

Copperline can put an emulated Hayes-compatible AT command modem on the Amiga’s serial port. The modem carries serial traffic over TCP, so terminal programs (such as Term, NComm, or JR-Comm) and BBS software can dial Internet telnet BBSes or accept incoming connections.

The command interpreter supports the standard Hayes AT commands (ATD, ATA, ATH, ATO, ATE, ATQ, ATV, ATI, ATZ, AT&F, AT&C, AT&D, AT&W, S-registers, and the +++ escape) and the AT* extension set of the WiModem232.

Configuration

In the launcher:

  1. Navigate to I/O Ports -> Serial Port.

  2. Set Device / Mode to Modem.

  3. Optionally set a Listen address (for incoming calls) and the Telnet NVT translation toggle.

In copperline.toml:

[serial]
mode = "modem"
# listen = "0.0.0.0:2323"   # Bind port for incoming calls (optional)
# telnet = true             # Enable Telnet NVT translation at power-on

From the command line:

copperline --model A1200 --serial modem KICK31.ROM

Dialing outbound connections

In your Amiga terminal software, use standard AT dialing commands:

[serial.phonebook]
"5551234" = "bbs.example.com:23"
"5555678" = "bbs2.example.com"

Result codes returned by the modem:

Dialing out resolves the host name and connects on the emulation thread (with a five-second connect timeout), so emulation pauses briefly while the connection is made.

Handling incoming calls

To allow inbound connections, specify a listening address with listen = "0.0.0.0:2323" (or via the launcher). Without one, or a port stored with AT&W, the modem only dials out.

Telnet NVT translation (AT*T1 / AT*T0)

By default the modem passes the raw 8-bit byte stream. For telnet servers that expect RFC 854 option negotiation:

WiModem232 command extensions

CommandDescription
AT*B<baud>Set the rate reported in CONNECT responses (a standard rate from 75 to 230400); the serial line itself always runs at the guest’s rate
AT*T0 / AT*T1Disable / enable telnet NVT translation
AT*L<port>Move the inbound listener to a new port on the same interface (127.0.0.1 unless listen named another)
AT*P<port>Set the default port for dials that name no port
AT*NList wireless networks (one simulated network)
AT*NS<n>,<pass>Join a network (accepted and ignored)
AT*REBOOTReset the modem, like ATZ

AT&W saves the current settings, including an AT*L port, and ATZ reloads them; AT&F restores the factory defaults. The stored profile is modem.profile in the NVRAM folder ([paths] nvram). Keys set explicitly in [serial] take precedence over the stored profile at power-on, and --factory neither reads nor writes it.

S-registers

RegisterFunctionDefault
S0Rings before auto-answer (0 = disabled)0
S1Ring counter0
S2Escape character code (+)43
S3Command-line carriage return character13
S4Linefeed character10
S9Delay after CONNECT before remote data reaches the guest, in tenths of a second of host time (WiModem compatibility)0
S12Escape sequence guard time in 1/50th seconds50 (1.0s)

Other S-registers read as 0 and ignore writes.

RS-232 control lines

Scripted session replay

For deterministic automated testing, CI, or demo playback, a scripted session file can stand in for the network, so a headless run that dials out is reproducible byte for byte:

[serial]
mode = "modem"
session = "session.txt"

--serial-session FILE does the same from the command line (and implies --serial modem). A scripted session only dials out, so it cannot be combined with listen. Rewinding or loading an earlier save state restarts the script from the top.

Directives in a session file, one per line (blank lines and lines starting with # are ignored):

# Session script example
accept
delay 0.5
send \r\nWelcome to the BBS\r\n
expect BYE\r
send \r\nGoodbye\r\n
close
DirectiveDescription
acceptThe next ATD succeeds (CONNECT)
refuse [busy|unreachable]The next ATD fails with BUSY or, by default, NO CARRIER
delay SECSHold the next action back for this many emulated seconds
send TEXTSend text to the guest (\r, \n, \t, and \\ escapes)
expect TEXTWait for the guest to send this text; a mismatch drops the call
closeThe far end hangs up

Each accept or refuse covers one call. A dial with none queued gets NO CARRIER, and a script that runs out mid-call leaves the line up with nothing more arriving.