# `XMAVLink.Router`
[🔗](https://github.com/fancydrones/xmavlink/blob/main/lib/mavlink/router.ex#L1)

Connect to serial, udp and tcp ports and listen for, validate and
forward MAVLink messages towards their destinations on other connections
and/or Elixir processes subscribing to messages.

The rules for MAVLink packet forwarding are described here:

  https://mavlink.io/en/guide/routing.html

and here:

  http://ardupilot.org/dev/docs/mavlink-routing-in-ardupilot.html

# `connection_key`

```elixir
@type connection_key() ::
  :local
  | binary()
  | port()
  | {port(), XMAVLink.Types.net_address(), XMAVLink.Types.net_port()}
```

# `delivery`

```elixir
@type delivery() :: %{
  source_connection: connection_key(),
  recipients: [connection_key()],
  remote_recipients: [connection_key()],
  target: XMAVLink.Dialect.target() | nil,
  message_id: XMAVLink.Types.message_id(),
  unreachable?: boolean()
}
```

Delivery metadata returned by `send_message/1..3`.

# `mavlink_address`

```elixir
@type mavlink_address() :: XMAVLink.Types.mavlink_address()
```

Represents the state of the XMAVLink.Router. Initial values should be set in config.exs.

## Fields
- dialect:            The XMAVLink dialect module generated by mix mavlink from a MAVLink definition file
- connection_strings: Configuration strings describing the network and serial connections MAVLink messages
                      are to be received and sent over. Allowed formats are:

                      ```udpin:<local ip>:<local port>
                      udpout:<remote ip>:<remote port>
                      tcpout:<remote ip>:<remote port>
                      serial:<device>:<baud rate>```

                      Note there is no tcpin connection - tcp is rarely used for MAVLink, the exception
                      being SITL testing which requires a tcpout connection.

- connections:        A map containing internal connection state for configured
                      transports and the local process connection. This field is
                      owned by the router and is not a supported integration API.
- routes:             A map from a {system id, component id} tuple to the connection key a message from that
                      system/component was last received on. Used to forward messages to that system/component.
- system_time_boot_ms:
                      A map from a {system id, component id} tuple to the last SYSTEM_TIME.time_boot_ms seen
                      from that source. Used to clear stale learned routes when a remote system reboots.

# `mavlink_connection`

```elixir
@type mavlink_connection() :: XMAVLink.Types.connection()
```

# `router_name`

```elixir
@type router_name() :: atom() | {:global, term()} | {:via, module(), term()}
```

# `router_ref`

```elixir
@type router_ref() :: GenServer.server()
```

# `subscribe_query`

```elixir
@type subscribe_query() :: [
  {:message, XMAVLink.Message.t() | :unknown}
  | {subscribe_query_id_key(), 0..255}
  | {:as_frame, boolean()}
]
```

# `subscribe_query_id_key`

```elixir
@type subscribe_query_id_key() ::
  :source_system | :source_component | :target_system | :target_component
```

# `t`

```elixir
@type t() :: %XMAVLink.Router{
  connection_retry_ms: non_neg_integer(),
  connection_strings: [String.t()],
  connection_supervisor: pid() | nil,
  connection_worker_monitors: %{required(reference()) =&gt; pid()},
  connection_workers: %{required(connection_key()) =&gt; pid()},
  connections: %{required(connection_key()) =&gt; mavlink_connection()},
  dialect: module() | nil,
  name: router_name() | nil,
  remote_forwarding: boolean(),
  routes: %{required(mavlink_address()) =&gt; connection_key()},
  signing: XMAVLink.Signing.t() | nil,
  subscription_cache: router_name() | nil,
  system_time_boot_ms: %{required(mavlink_address()) =&gt; non_neg_integer()}
}
```

# `pack_and_send`

```elixir
@spec pack_and_send(XMAVLink.Message.t()) :: :ok | {:error, :protocol_undefined}
```

Send a MAVLink message to one or more recipients using available
connections. For now if destination is unreachable it will log
a warning

## Parameters

- router: Optional router name or pid. Defaults to `XMAVLink.Router`.
- message: A MAVLink message structure from the installed dialect
- version: Force sending using a specific MAVLink protocol (default 2)
- opts: Optional send settings:
  - `:source_system` and `:source_component` override the router's
    configured local identity for this message. Provide both or neither.

## Example

```
  XMAVLink.Router.pack_and_send(
    %Common.RcChannelsOverride{
      target_system: 1,
      target_component: 1,
      chan1_raw: 1500,
      chan2_raw: 1500,
      chan3_raw: 1500,
      chan4_raw: 1500,
      chan5_raw: 1500,
      chan6_raw: 1500,
      chan7_raw: 1500,
      chan8_raw: 1500,
      chan9_raw: 0,
      chan10_raw: 0,
      chan11_raw: 0,
      chan12_raw: 0,
      chan13_raw: 0,
      chan14_raw: 0,
      chan15_raw: 0,
      chan16_raw: 0,
      chan17_raw: 0,
      chan18_raw: 0
    }
  )
```

# `pack_and_send`

```elixir
@spec pack_and_send(router_ref(), XMAVLink.Message.t()) ::
  :ok | {:error, :protocol_undefined}
@spec pack_and_send(XMAVLink.Message.t(), XMAVLink.Types.version() | keyword()) ::
  :ok | {:error, :protocol_undefined}
```

# `pack_and_send`

```elixir
@spec pack_and_send(
  router_ref(),
  XMAVLink.Message.t(),
  XMAVLink.Types.version() | keyword()
) ::
  :ok | {:error, :protocol_undefined}
@spec pack_and_send(XMAVLink.Message.t(), XMAVLink.Types.version(), keyword()) ::
  :ok | {:error, :protocol_undefined}
```

# `pack_and_send`

```elixir
@spec pack_and_send(
  router_ref(),
  XMAVLink.Message.t(),
  XMAVLink.Types.version(),
  keyword()
) ::
  :ok | {:error, :protocol_undefined}
```

# `send_message`

```elixir
@spec send_message(XMAVLink.Message.t()) :: {:ok, delivery()} | {:error, term()}
```

Synchronously pack, route, and send a MAVLink message.

This is the structured alternative to `pack_and_send/2..4`. Pass `:version`
in `opts` to select MAVLink 1 or 2, and pass `:source_system` plus
`:source_component` to override the router's local identity for this message.

The success reply includes the selected recipient connection keys. Connection
keys are internal runtime identifiers, but they are useful for observability
and tests.

# `send_message`

```elixir
@spec send_message(
  XMAVLink.Message.t(),
  keyword()
) :: {:ok, delivery()} | {:error, term()}
@spec send_message(router_ref(), XMAVLink.Message.t()) ::
  {:ok, delivery()} | {:error, term()}
```

# `send_message`

```elixir
@spec send_message(router_ref(), XMAVLink.Message.t(), keyword()) ::
  {:ok, delivery()} | {:error, term()}
```

# `start_link`

```elixir
@spec start_link(
  %{
    :system =&gt; 1..255,
    :component =&gt; 1..255,
    :dialect =&gt; module(),
    optional(:name) =&gt; router_name() | nil,
    optional(:connection_strings) =&gt; [String.t()],
    optional(:connections) =&gt; [String.t()],
    optional(:connection_retry_ms) =&gt; non_neg_integer(),
    optional(:remote_forwarding) =&gt; boolean(),
    optional(:signing) =&gt; keyword() | nil
  },
  [{atom(), any()}]
) :: {:ok, pid()}
```

Start the MAVLink Router service. You should not call this directly, use the MAVLink application
to start this under a supervision tree with the services it expects to use.

## Parameters
- dialect:            Name of the module generated by mix mavlink task to represent the enumerations
                      and messages of a particular MAVLink dialect
- system:             The System id of this system  1..255, typically low for vehicles and high for
                      ground stations
- component:          The component id of this system 1..255, typically 1 for the autopilot
- connection_strings: A list of strings in the following formats:

                      udpin:<local ip>:<local port>
                      udpout:<remote ip>:<remote port>
                      tcpout:<remote ip>:<remote port>
                      serial:<device>:<baud rate>
- connection_retry_ms:
                      Retry delay for configured connection workers after
                      open failures or TCP/serial disconnects. Defaults to
                      1000 ms.
- remote_forwarding:
                      Whether frames received from remote links are forwarded
                      to other remote links. Defaults to true. Set false for
                      endpoint/GCS use cases that should receive remote
                      traffic locally without bridging it between links.

- opts:               Standard GenServer options. Pass `:name` to register
                      a non-default router, or `name: nil` in the args map to
                      start an unregistered router addressed by pid.

# `subscribe`

```elixir
@spec subscribe() :: :ok | {:error, :invalid_message}
```

Subscribes the calling process to MAVLink messages from the installed dialect matching the query.

## Parameters

- router: Optional router name or pid. Defaults to `XMAVLink.Router`.
- query:  Keyword list of zero or more of the following query keywords:

  message:          message_module | :unknown (use latter with as_frame)
  source_system:    integer 0..255
  source_component: integer 0..255
  target_system:    integer 0..255
  target_component: integer 0..255
  as_frame:         true|false (default false, shows entire message frame with sender/target details)

## Example

```
  XMAVLink.Router.subscribe message: XMAVLink.Message.Heartbeat, source_system: 1
```

# `subscribe`

```elixir
@spec subscribe(router_ref() | subscribe_query()) :: :ok | {:error, :invalid_message}
```

# `subscribe`

```elixir
@spec subscribe(router_ref(), subscribe_query()) :: :ok | {:error, :invalid_message}
```

# `unsubscribe`

```elixir
@spec unsubscribe() :: :ok
```

Un-subscribes calling process from all existing subscriptions

## Example

```
  XMAVLink.Router.unsubscribe
```

# `unsubscribe`

```elixir
@spec unsubscribe(router_ref()) :: :ok
```

---

*Consult [api-reference.md](api-reference.md) for complete listing*
