Reactive Controllers

View Source

Overview

Reactive controllers monitor PubSub messages and trigger actions when conditions are met. They provide a declarative way to implement common reactive patterns like threshold monitoring and event-driven responses without writing custom controller code.

Controller Types

BB provides two reactive controller types:

ControllerPurpose
BB.Controller.PatternMatchTriggers when a message matches a predicate function
BB.Controller.ThresholdTriggers when a numeric field exceeds min/max bounds

Threshold is a convenience wrapper around PatternMatch - internally it generates a match function from the field and bounds configuration.

Actions

When a condition is met, the controller executes an action. Two action types are available:

Command Action

Invokes a robot command:

action: command(:disarm)
action: command(:move_to, target: :home)

Callback Action

Calls an arbitrary function with the triggering message and context:

action: handle_event(fn msg, ctx ->
  Logger.warning("Threshold exceeded: #{inspect(msg.payload)}")
  # ctx contains: robot_module, robot, robot_state, controller_name
  :ok
end)

The callback receives:

Configuration

PatternMatch Options

OptionTypeRequiredDescription
:topic[atom]YesPubSub topic path to subscribe to
:matchfn msg -> booleanYesPredicate that returns true when action should trigger
:actionactionYesAction to execute (see Actions above)
:cooldown_msintegerNoMinimum ms between triggers (default: 1000)

Threshold Options

OptionTypeRequiredDescription
:topic[atom]YesPubSub topic path to subscribe to
:fieldatom or [atom]YesField path to extract from message payload
:minfloatOne requiredMinimum acceptable value
:maxfloatOne requiredMaximum acceptable value
:actionactionYesAction to execute when threshold exceeded
:cooldown_msintegerNoMinimum ms between triggers (default: 1000)

At least one of :min or :max must be provided for Threshold.

Examples

Current Limiting

Disarm the robot if servo current exceeds safe limits:

defmodule MyRobot do
  use BB

  controllers do
    controller :over_current, {BB.Controller.Threshold,
      topic: [:sensor, :servo_status],
      field: :current,
      max: 1.21,
      action: command(:disarm)
    }
  end
end

Collision Detection

React to proximity sensor readings:

controllers do
  controller :collision, {BB.Controller.PatternMatch,
    topic: [:sensor, :proximity],
    match: fn msg -> msg.payload.distance < 0.05 end,
    action: command(:disarm)
  }
end

Temperature Monitoring with Callback

Log warnings when temperature is outside safe range:

controllers do
  controller :temp_monitor, {BB.Controller.Threshold,
    topic: [:sensor, :temperature],
    field: :value,
    min: 10.0,
    max: 45.0,
    cooldown_ms: 5000,
    action: handle_event(fn msg, ctx ->
      Logger.warning("[#{ctx.controller_name}] Temperature out of range: #{msg.payload.value}°C")
      :ok
    end)
  }
end

Nested Field Access

Access nested fields in message payloads:

controllers do
  controller :voltage_monitor, {BB.Controller.Threshold,
    topic: [:sensor, :power],
    field: [:battery, :voltage],  # Accesses msg.payload.battery.voltage
    min: 11.0,
    action: command(:disarm)
  }
end

Cooldown Behaviour

The :cooldown_ms option prevents rapid repeated triggering. After an action executes, the controller ignores matching messages until the cooldown period elapses. This is useful for:

  • Preventing command spam from noisy sensors
  • Allowing time for the triggered action to take effect
  • Reducing log noise from callback actions

The first matching message always triggers immediately (no initial delay).

Integration with Commands

Reactive controllers work alongside the command system. When a controller triggers command(:disarm), it's equivalent to calling MyRobot.disarm([]) - the command goes through the normal command execution flow with state machine validation.

This means:

  • Commands are logged via telemetry
  • State machine rules apply (can't disarm if already disarmed)
  • Command results are returned (but typically ignored by the controller)

When to Use Reactive Controllers

Good use cases:

  • Safety limits (current, temperature, force thresholds)
  • Event-driven responses (collision detection, limit switches)
  • Monitoring and alerting (logging unusual conditions)

Consider alternatives when:

  • You need complex logic spanning multiple messages (use a custom controller)
  • You need to modify robot state directly (use a custom controller with handle_info)
  • You need request/response patterns (use commands instead)