Run Atlas directly over a serial radio shipped

Documents the atlasd 3.0.0 release lineView as Markdown

A pair of SiK-family radios (RFD900-class) with Atlas on both ends needs no adapter, no pppd and no IP plan: one [[link]] type = "serial" on each node puts Atlas frames straight onto the port, paces them on the radio's own buffer and feeds its signal figures into the link score. This guide takes a radio pair that runs through pppd today to a native serial link, adds a third node on a shared channel, and shows how to go back. The mechanism is described in Native serial link.

What you need: both nodes on a build with the serial link, the radios on USB serial adapters, a terminal program for the radios' AT commands, and the radio's port free of any other process. Change both ends of a radio pair in the same maintenance window: a native link talks only to another native link.

Prepare the radios

Atlas does not configure the radio. Set each radio once with its AT commands (enter command mode with +++, list parameters with ATI5, write with AT&W, reboot with ATZ):

ParameterSet toWhy
SERIAL_SPEED (ATS1)the link's baud, e.g. 57 for 57 600Both sides of the serial line must agree; a mismatch reads as noise.
AIR_SPEED (ATS2)the same on both radios, e.g. 64Copy it into the link's air_rate, which sets the capacity and the pacer's ceiling.
MAVLINK (ATS6)1MAVLink framing makes the radio answer each heartbeat with a RADIO_STATUS frame: RSSI, noise and its transmit-buffer fill.
RTSCTS1 only if the cable carries RTS and CTSOptional. With it, a full radio stops the line instead of dropping bytes; pacing works either way.

For several nodes on one channel, load the radios' multipoint firmware, give each radio its own node id and set every radio's destination to broadcast (NODEDESTINATION 65535), so every radio hears every other.

Take the radio off pppd

On both nodes, stop and disable the SiK adapter that owns the radio and the pppd that attaches to its pseudo-terminal, so neither starts again at boot:

sudo systemctl disable --now <the adapter's unit> <the pppd unit for this radio>

Remove the old link that bound to the PPP interface (the [[link]] with bind_device = "ppp0" or similar) and any endpoint_per_link entry that names it. If something still holds the port when the daemon starts, the serial link's open fails and retries; once the link holds the port, it holds it exclusively, so a late starter gets EBUSY rather than a share of the byte stream.

Configure the link

Use the radio's /dev/serial/by-id/ path: it names the same radio after every replug. Point to point, on each node:

[[link]]
name = "rfd"
type = "serial"
device = "/dev/serial/by-id/usb-FTDI_FT231X_USB_UART_D30A1B2C-if00-port0"
baud = 57600              # = SERIAL_SPEED on the radio
radio = "rfd900"          # rfd900 | sik write heartbeats; generic does not
air_rate = "64 kbps"      # = AIR_SPEED on the radio
flow_control = "none"     # "rtscts" if radio and cable carry it
probe_interval_ms = 1000  # radios are slow and half duplex
peers = ["10.0.100.2"]    # the node at the other end

[[peer]]
public_key = "…"
allowed_ips = ["10.0.100.2/32"]
# no endpoint: a serial link needs none

With no capacity, the link's capacity is the lower of the line rate (baud × 0.8) and air_rate — 46 kbit/s for this example, where the 57 600-baud line is narrower than the air. At 115 200 baud the 64 kbit/s air sets it. Set the link's probe_interval_ms to 500–1000: a round trip on a half-duplex radio includes waiting for the other side's transmit window. Leave unusable_rtt_ms at its default.

Validate before restarting. atlasd config check runs the daemon's own load and refuses, by name, a serial key on a non-serial link, an unsupported baud, a reserved or duplicate address, peer_addresses without an own address, and two serial links on one device:

$ atlasd config check -c /etc/atlas/config.toml
  ✓ /etc/atlas/config.toml is loadable

Restart the daemon on both nodes. The log names the port as it opens:

serial link rfd: /dev/serial/by-id/usb-FTDI_FT231X_USB_UART_D30A1B2C-if00-port0 open at 57600 baud (none, radio rfd900)

A third node on the same channel

With the radios on multipoint firmware and broadcasting, give each node an address on the channel and tell it where the others sit. Node 1:

[[link]]
name = "rfd"
type = "serial"
device = "/dev/serial/by-id/usb-FTDI_FT231X_USB_UART_D30A1B2C-if00-port0"
baud = 57600
radio = "rfd900"
air_rate = "64 kbps"
probe_interval_ms = 1000
address = 1
peer_addresses = { "10.0.100.2" = 2, "10.0.100.3" = 3 }

Nodes 2 and 3 take address = 2 and 3 with their own maps. Addresses run 1–254 and must differ on one channel; peers is for point-to-point links and is refused here. A node missing from peer_addresses is learned from its first authenticated frame, so a map is needed only on the side that speaks first. Every addressed frame carries three more bytes.

Check it

$ curl -s localhost:9800/api/stats | jq '.links[] | select(.name == "rfd")
    | {alive, radio_rssi_dbm, radio_telemetry_source, queue_delay_ms,
       serial: .serial | {open, frames_rx, crc_errors, framing_errors,
                          radio_status_frames, radio_buffer_free_pct, air_rate_kbps}}'
What to look forWhereHealthy
The device is heldserial.open; the link card's serial 57600 chiptrue; the chip is not amber
The link is upalivetrue after the first probe round trip
Frames arrive cleanframes_rx against crc_errors and framing_errorsframes_rx climbing, errors a small share
The radio reportsradio_status_frames, radio_status_age_secs, radio_rssi_dbmReports climbing, age under a second, radio_telemetry_source is atlasd-serial
Pacing holds the radio's bufferradio_buffer_free_pct, radio_overflow_bytes_estFree space well above zero under load, overflow flat
The queue below is shortqueue_delay_ms, egress_queue.serial_bytesTens of milliseconds with voice in use
Recovery workspull and replug the radio's USB cable; atlasd journal -c linklink.serial_reopened, reopens +1, traffic back within a fraction of a second
Multipointnot_for_usClimbing on every node: frames for the others are seen and dropped

Voice and telemetry on the radio

Voice, Control and Position ride the high band and go to the radio first; video and bulk wait in Atlas's queue while the radio's buffer is full. Fifty 20 ms voice frames a second cost about 43 kbit/s on the wire natively, so a call fits a 64 kbit/s radio with room for telemetry, and on a 57 600-baud line leaves very little else. Carrying the previous packet for loss recovery raises the call to about 69 kbit/s, more than 64 kbit/s of air holds: on such a radio, turn redundancy off for the class or use 40 ms frames (about 28 kbit/s). Keep Video and Bulk off a serial radio that should carry only Control, Voice and Position; the transport classes page shows how.

Going back to pppd

Remove or disable the serial [[link]] on both nodes (enabled = false keeps it in the file), restore the PPP link, start the adapter and pppd again, and restart the daemon. Nothing on the radio needs to change: the adapter reads the same MAVLink framing.

Boundaries

  • Both ends run Atlas. Radios that must also carry non-Atlas IP traffic, or devices that offer only PPP or IP, stay on the adapter and pppd.
  • On a shared channel, link-state floods and gossip go out as one unicast copy per peer, and frames from two senders that interleave at a receiver lose the split frame (counted in framing_errors).
  • A radio with MAVLink framing off, or radio = "generic", reports nothing: the link still paces on the serial line and drains its model of the radio at the declared air_rate, and the link score has no RSSI.
  • radio_buffer_bytes (default 2048) is the size the pacer assumes for the radio's buffer; set it to your radio's figure if it differs.

When something does not look like the table above, see the serial entries in troubleshooting.

Esc
↑ ↓ navigateEnter — openEsc — close