lib/mavlink/serial_connection.ex

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.warn "Could not open serial port #{port}. Retrying in 1 second"
          :timer.sleep(1000)
          connect(["serial", port, baud, uart], controlling_process)
      end
    else
      :ok = Logger.warn "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