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/1 | Called by net_kernel after listen/1 succeeds. |
| accept_connection/5 | |
| address/0 | |
| close/1 | |
| listen/1 | |
| listen/2 | |
| select/1 | |
| setup/5 | Called 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.