Module serial_dist

Distribution over serial (UART) connections.

Description

This module implements the Erlang distribution protocol over UART, enabling distributed Erlang between devices connected via serial lines. This is particularly useful for microcontrollers that lack WiFi/TCP (e.g. STM32) but have UART available.

Multi-port support

Many MCUs have multiple UARTs (e.g. UART0 for console, UART1 and UART2 for peer connections). This module supports opening several serial ports, each connecting to a different peer node. Pass a list of UART configurations via the uart_ports option:

  {ok, _} = net_kernel:start('mynode@serial.local', #{
      name_domain => longnames,
      proto_dist => serial_dist,
      avm_dist_opts => #{
          uart_ports => [
              [{peripheral, "UART1"}, {speed, 115200}, {tx, 17}, {rx, 16}],
              [{peripheral, "UART2"}, {speed, 115200}, {tx, 4}, {rx, 5}]
          ],
          uart_module => uart
      }
  }).

For a single port, the uart_opts key can also be used:

  avm_dist_opts => #{
      uart_opts => [{peripheral, "UART1"}, {speed, 115200}]
  }

Peer-to-peer model

Each UART handles exactly one connection. A coordinator process manages one link manager per UART port. Each link manager owns all reads from its UART and arbitrates between incoming connections (accept) and outgoing connections (setup).

The link managers periodically send sync markers (<<16#AA, 16#55>>) on their UART so the peer can detect we are alive. When a peer wants to connect, it sends a preamble of repeated sync markers followed by a framed handshake packet. Each frame on the wire uses the format:

  <<16#AA, 16#55, Length, Payload, CRC32:32>>

The CRC32 covers the Length and Payload bytes. False sync matches (where <<16#AA, 16#55>> appears in payload data) are rejected by maximum frame size checks and CRC verification. On CRC failure during an active connection, the connection is torn down. During pre-connection scanning, the buffer is discarded and retried.

BEAM compatibility

This module also works on BEAM (standard Erlang/OTP). On BEAM, start the VM with -proto_dist serial and configure UART options via application environment before calling net_kernel:start/1:

  application:set_env(serial_dist, dist_opts, #{
      uart_opts => [{peripheral, "/dev/ttyUSB0"}, {speed, 115200}],
      uart_module => my_uart_module
  }).
  net_kernel:start(['mynode@serial.local', longnames]).

Data Types

listen_handle()


listen_handle() = [{uart_handle(), module()}, ...]

uart_handle()


uart_handle() = pid() | port()

Function Index

accept/1Called by net_kernel after listen/1 succeeds.
accept_connection/5
address/0
close/1
listen/1
listen/2
select/1
setup/5Called by net_kernel to initiate an outgoing connection.

Function Details

accept/1


accept(Ports::listen_handle() | {uart_handle(), module()}) -> pid()

Called by net_kernel after listen/1 succeeds. Spawns a coordinator process and one link manager per UART port.

accept_connection/5

accept_connection(AcceptPid, DistCtrl, MyNode, Allowed, SetupTime) -> any()

address/0


address() -> #net_address{}

close/1

close(Ports) -> any()

listen/1


listen(Name::string()) -> {ok, {listen_handle(), #net_address{}, pos_integer()}} | {error, any()}

listen/2


listen(Name::string(), Opts::map() | string()) -> {ok, {listen_handle(), #net_address{}, pos_integer()}} | {error, any()}

select/1

select(Node) -> any()

setup/5

setup(Node, Type, MyNode, LongOrShortNames, SetupTime) -> any()

Called by net_kernel to initiate an outgoing connection. Forwards the request to the coordinator which broadcasts to idle link managers.