defmodule XMAVLink.SerialConnection do @moduledoc """ XMAVLink.Router delegate for Serial connections """ @smallest_mavlink_message 8 require Logger alias XMAVLink.ConnectionWorker alias XMAVLink.Frame alias Circuits.UART import XMAVLink.Frame, only: [binary_to_frame_and_tail: 1, validate_and_unpack: 3] defstruct port: nil, baud: nil, uart: nil, buffer: <<>>, worker: nil, signing: nil @type t :: %XMAVLink.SerialConnection{ port: binary, baud: non_neg_integer, uart: pid, buffer: binary, worker: pid | nil, signing: XMAVLink.Signing.t() | nil } 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, <<>>}) connection = struct(receiving_connection, buffer: rest) case validate_and_unpack(received_frame, dialect, receiving_connection.signing) do {:ok, valid_frame, signing} -> {:ok, port, struct(connection, signing: signing), valid_frame} {:unknown_message, signing} -> # We re-broadcast valid frames with unknown messages :ok = Logger.debug("rebroadcasting unknown message with id #{received_frame.message_id}") {:ok, port, struct(connection, signing: signing), struct(received_frame, target: :broadcast)} {:error, reason, signing} -> :ok = Logger.debug( "SerialConnection.handle_info: frame received failed: #{Atom.to_string(reason)}" ) {:error, reason, port, struct(connection, signing: signing)} end end end def open(["serial", port, baud], controlling_process) do if Map.has_key?(UART.enumerate(), port) do uart = :poolboy.checkout(XMAVLink.UARTPool) case UART.open(uart, port, speed: baud, active: true) do :ok -> :ok = Logger.info("Opened serial port #{port} at #{baud} baud") :ok = UART.controlling_process(uart, controlling_process) {:ok, port, struct( XMAVLink.SerialConnection, port: port, baud: baud, uart: uart, worker: controlling_process )} {:error, reason} -> :poolboy.checkin(XMAVLink.UARTPool, uart) {:error, {:open_failed, reason}} end else {:error, :not_attached} end end def close(%XMAVLink.SerialConnection{uart: uart}) do _ = UART.close(uart) :poolboy.checkin(XMAVLink.UARTPool, uart) end def forward( connection = %XMAVLink.SerialConnection{worker: worker}, frame ) when is_pid(worker) do ConnectionWorker.forward(worker, connection, frame) 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