Behaviour for parameter bridge GenServers in the BB framework.
Bridges provide bidirectional parameter access between BB and remote systems (GCS, web UIs, flight controllers).
Bridges do NOT implement safety callbacks - they handle data transport, not physical hardware control.
Two Directions
Outbound (local → remote): Expose BB's parameters to remote clients
- Subscribe to
[:param]viaBB.PubSubin GenServerinit/1 - Implement
handle_change/3to push local changes to remote clients - Remote clients query local params via bridge (calls
BB.Parameter.list/get/set)
Inbound (remote → local): Access remote system's parameters from BB
- Implement
list_remote/1to enumerate remote parameters - Implement
get_remote/2to read remote values - Implement
set_remote/3to write remote values - Implement
subscribe_remote/2to subscribe to remote changes - Publish remote changes via PubSub (path structure up to bridge)
Usage
The use BB.Bridge macro:
- Adds
use GenServer(you must implement GenServer callbacks) - Adds
@behaviour BB.Bridge - Optionally defines
options_schema/0if you pass the:options_schemaoption
Options Schema
If your bridge accepts configuration options, pass them via :options_schema:
defmodule MyMavlinkBridge do
use BB.Bridge,
options_schema: [
port: [type: :string, required: true, doc: "Serial port path"],
baud_rate: [type: :pos_integer, default: 57600, doc: "Baud rate"]
]
@impl BB.Bridge
def handle_change(_robot, changed, state) do
send_to_gcs(state.conn, changed)
{:ok, state}
end
# ... other BB.Bridge callbacks
endFor bridges that don't need configuration, omit :options_schema:
defmodule SimpleBridge do
use BB.Bridge
# Must be used as bare module in DSL: bridge :simple, SimpleBridge
endDSL Usage
parameters do
bridge :mavlink, {MyMavlinkBridge, port: "/dev/ttyACM0", baud_rate: 115200}
bridge :phoenix, {PhoenixBridge, url: "ws://gcs.local/socket"}
endAuto-injected Options
The :bb option is automatically provided by the supervisor and should
NOT be included in your options_schema. It contains %{robot: module, path: [atom]}.
IEx Usage
# List remote parameters (e.g., ArduPilot's params)
{:ok, params} = BB.Parameter.list_remote(MyRobot, :mavlink)
# => [%{id: "PITCH_RATE_P", value: 0.1, path: [:mavlink, :pitch, :rate, :p], ...}, ...]
# Get a remote parameter
{:ok, value} = BB.Parameter.get_remote(MyRobot, :mavlink, "PITCH_RATE_P")
# => 0.1
# Set a remote parameter
:ok = BB.Parameter.set_remote(MyRobot, :mavlink, "PITCH_RATE_P", 0.15)
# Subscribe to remote parameter changes (tells bridge to track this param)
:ok = BB.Parameter.subscribe_remote(MyRobot, :mavlink, "PITCH_RATE_P")
# Then subscribe to PubSub using the path from list_remote
BB.PubSub.subscribe(MyRobot, [:mavlink, :pitch, :rate, :p])Example Implementation
defmodule MyMavlinkBridge do
use BB.Bridge
# Define a payload type for remote param change messages
defmodule ParamValue do
defstruct [:value]
use BB.Message,
schema: [value: [type: :any, required: true]]
end
# GenServer init - extract robot from :bb metadata, subscribe to param changes
@impl GenServer
def init(opts) do
%{robot: robot} = Keyword.fetch!(opts, :bb)
BB.PubSub.subscribe(robot, [:param])
conn = connect_to_mavlink(opts[:conn])
{:ok, %{robot: robot, conn: conn, subscriptions: MapSet.new()}}
end
# Outbound: local param changed, notify remote
@impl BB.Bridge
def handle_change(_robot, changed, state) do
send_param_to_gcs(state.conn, changed)
{:ok, state}
end
# Inbound: list remote params
@impl BB.Bridge
def list_remote(state) do
# Return params with path for PubSub subscriptions
params = Enum.map(fetch_all_params_from_fc(state.conn), fn {id, value} ->
%{id: id, value: value, type: nil, doc: nil, path: param_id_to_path(id)}
end)
{:ok, params, state}
end
# Inbound: get remote param
@impl BB.Bridge
def get_remote(param_id, state) do
value = fetch_param_from_fc(state.conn, param_id)
{:ok, value, state}
end
# Inbound: set remote param
@impl BB.Bridge
def set_remote(param_id, value, state) do
:ok = send_param_set_to_fc(state.conn, param_id, value)
{:ok, state}
end
# Inbound: subscribe to remote param changes
@impl BB.Bridge
def subscribe_remote(param_id, state) do
{:ok, %{state | subscriptions: MapSet.put(state.subscriptions, param_id)}}
end
# When FC sends param update, publish via PubSub
@impl GenServer
def handle_info({:mavlink_param_value, param_id, value}, state) do
if MapSet.member?(state.subscriptions, param_id) do
path = param_id_to_path(param_id)
message = BB.Message.new!(ParamValue, :remote, value: value)
BB.PubSub.publish(state.robot, path, message)
end
{:noreply, state}
end
# Convert "PITCH_RATE_P" to [:mavlink, :pitch, :rate, :p]
defp param_id_to_path(param_id) do
atoms = param_id |> String.downcase() |> String.split("_") |> Enum.map(&String.to_atom/1)
[:mavlink | atoms]
end
end
Summary
Callbacks
Get a parameter value from the remote system.
Handle a local parameter change.
List parameters available on the remote system.
Returns the options schema for this bridge.
Set a parameter value on the remote system.
Subscribe to changes for a remote parameter.
Types
Callbacks
Get a parameter value from the remote system.
@callback handle_change(robot(), changed :: BB.Parameter.Changed.t(), state()) :: {:ok, state()}
Handle a local parameter change.
Called when a BB parameter changes locally. The bridge should notify any subscribed remote clients.
@callback list_remote(state()) :: {:ok, [remote_param()], state()} | {:error, term(), state()}
List parameters available on the remote system.
Returns a list of parameter info from the remote (e.g., flight controller).
@callback options_schema() :: Spark.Options.t()
Returns the options schema for this bridge.
The schema should NOT include the :bb option - it is auto-injected.
If this callback is not implemented, the module cannot accept options
in the DSL (must be used as a bare module).
@callback set_remote(param_id(), value :: term(), state()) :: {:ok, state()} | {:error, term(), state()}
Set a parameter value on the remote system.
Subscribe to changes for a remote parameter.
When the remote parameter changes, the bridge should publish via
BB.PubSub. The path structure is up to the bridge implementation.