defmodule XMAVLink.SerialConnection do
@moduledoc """
XMAVLink.Router delegate for Serial connections
"""
@smallest_mavlink_message 8
require Logger
alias XMAVLink.Frame
alias Circuits.UART
import XMAVLink.Frame, only: [binary_to_frame_and_tail: 1, validate_and_unpack: 2]
defstruct port: nil,
baud: nil,
uart: nil,
buffer: <<>>
@type t :: %XMAVLink.SerialConnection{
port: binary,
baud: non_neg_integer,
uart: pid,
buffer: binary
}
def handle_info(
{:circuits_uart, port, raw},
receiving_connection = %XMAVLink.SerialConnection{buffer: buffer},
dialect
) do
case binary_to_frame_and_tail(buffer <> raw) do
:not_a_frame ->
# Noise or malformed frame
if byte_size(buffer) + byte_size(raw) > 0 do
:ok =
Logger.debug("SerialConnection.handle_info: Not a frame: #{inspect(buffer <> raw)}")
end
{:error, :not_a_frame, port, struct(receiving_connection, buffer: <<>>)}
{nil, rest} ->
{:error, :incomplete_frame, port, struct(receiving_connection, buffer: rest)}
{received_frame, rest} ->
# Rest could include a complete message, return later to try emptying the buffer
if byte_size(rest) >= @smallest_mavlink_message,
do: send(self(), {:circuits_uart, port, <<>>})
case validate_and_unpack(received_frame, dialect) do
{:ok, valid_frame} ->
{:ok, port, struct(receiving_connection, buffer: rest), valid_frame}
:unknown_message ->
# We re-broadcast valid frames with unknown messages
:ok =
Logger.debug("rebroadcasting unknown message with id #{received_frame.message_id}}")
{:ok, port, struct(receiving_connection, buffer: rest),
struct(received_frame, target: :broadcast)}
reason ->
:ok =
Logger.debug(
"SerialConnection.handle_info: frame received failed: #{Atom.to_string(reason)}"
)
{:error, reason, port, struct(receiving_connection, buffer: rest)}
end
end
end
def connect(["serial", port, baud, uart], controlling_process) do
if Map.has_key?(UART.enumerate(), port) do
case UART.open(uart, port, speed: baud, active: true) do
:ok ->
:ok = Logger.info("Opened serial port #{port} at #{baud} baud")
send(
controlling_process,
{
:add_connection,
port,
struct(
XMAVLink.SerialConnection,
port: port,
baud: baud,
uart: uart
)
}
)
UART.controlling_process(uart, controlling_process)
{:error, _} ->
:ok = Logger.warning("Could not open serial port #{port}. Retrying in 1 second")
:timer.sleep(1000)
connect(["serial", port, baud, uart], controlling_process)
end
else
:ok = Logger.warning("Serial port #{port} not attached. Retrying in 1 second")
:timer.sleep(1000)
connect(["serial", port, baud, uart], controlling_process)
end
end
def forward(
%XMAVLink.SerialConnection{uart: uart},
%Frame{version: 1, mavlink_1_raw: packet}
) do
UART.write(uart, packet)
end
def forward(
%XMAVLink.SerialConnection{uart: uart},
%Frame{version: 2, mavlink_2_raw: packet}
) do
UART.write(uart, packet)
end
end