Beam Bots Logo

Beam Bots PID Controller

CI License: Apache 2.0 Hex version badge REUSE status

A general-purpose PID controller for the Beam Bots robotics framework.

This library provides a BB.Controller implementation that subscribes to configurable topics for setpoint and measurement values, runs a periodic PID loop, and publishes output to a configurable topic. One controller instance = one PID loop.

Features

  • Configurable subscriptions - subscribe to any topic/message combination for setpoint and measurement
  • Flexible path extraction - extract values from nested message fields or list indices
  • Configurable output - publish to any topic with any numeric message field
  • Runtime parameter updates - PID gains can be changed at runtime via BB parameters
  • Validation - validates configuration at init time with clear error messages

Installation

Add bb_pid_controller to your list of dependencies in mix.exs:

def deps do
  [
    {:bb_pid_controller, "~> 0.2.1"}
  ]
end

Requirements

  • BB framework (~> 0.13)

Usage

Define a PID controller in your robot DSL:

defmodule MyRobot do
  use BB

  controller :shoulder_pid, {BB.PID.Controller,
    kp: 2.0, ki: 0.1, kd: 0.05,
    output_min: -1.0, output_max: 1.0,

    # Subscribe to position commands
    setpoint_topic: [:actuator, :base_link, :shoulder, :pid],
    setpoint_message: BB.Message.Actuator.Command.Position,
    setpoint_path: [:position],

    # Subscribe to encoder feedback
    measurement_topic: [:sensor, :base_link, :shoulder, :encoder],
    measurement_message: BB.Message.Sensor.JointState,
    measurement_path: [:positions, 0],

    # Publish velocity commands to servo
    output_topic: [:actuator, :base_link, :shoulder, :servo],
    output_message: BB.Message.Actuator.Command.Velocity,
    output_field: :velocity,
    output_frame_id: :shoulder,

    rate: 100
  }

  topology do
    link :base_link do
      joint :shoulder, type: :revolute do
        link :upper_arm do
        end
      end
    end
  end
end

Sending Setpoints

Send position setpoints to the PID controller via pubsub:

{:ok, msg} = BB.Message.new(
  BB.Message.Actuator.Command.Position,
  :shoulder,
  position: 1.57
)

BB.PubSub.publish(MyRobot, [:actuator, :base_link, :shoulder, :pid], msg)

The controller will compute the PID output and publish velocity commands to the servo.

Configuration Options

PID Gains

OptionTypeDefaultDescription
kpfloatrequiredProportional gain
kifloat0.0Integral gain
kdfloat0.0Derivative gain
taufloat1.0Derivative low-pass filter (0-1, 1=no filter)
output_minfloat-1.0Output clamp minimum
output_maxfloat1.0Output clamp maximum

Setpoint Subscription

OptionTypeDescription
setpoint_topic[atom]Topic path to subscribe to
setpoint_messagemoduleMessage module to filter for
setpoint_path[atom | integer]Path to value in payload

Measurement Subscription

OptionTypeDescription
measurement_topic[atom]Topic path to subscribe to
measurement_messagemoduleMessage module to filter for
measurement_path[atom | integer]Path to value in payload

Output Publication

OptionTypeDescription
output_topic[atom]Topic path to publish to
output_messagemoduleMessage module to construct
output_fieldatomField name for output value
output_frame_idatomframe_id for constructed messages

Control Loop

OptionTypeDefaultDescription
ratepos_integer100Control loop frequency (Hz)

Path Extraction

The *_path options support atoms (field names) and integers (list indices):

# Simple field access
setpoint_path: [:position]           # payload.position

# List index access
measurement_path: [:positions, 0]    # payload.positions |> Enum.at(0)

# Nested access
path: [:data, :readings, 0, :value]  # payload.data.readings[0].value

How It Works

Architecture

Setpoint Topic 
(configurable message/field)            
                                        
                               
Measurement Topic  BB.PID.Controller
(configurable message/field)                     
                                  PIDControl     
                                  .step()        
                               
                                        
                                        
                               Output Topic
                               (configurable message/field)

Message Flow

  1. Controller subscribes to setpoint_topic and measurement_topic at init
  2. When a message arrives on setpoint_topic with matching setpoint_message type, the setpoint value is extracted and stored
  3. When a message arrives on measurement_topic with matching measurement_message type, the measurement value is extracted and stored
  4. Every 1000/rate ms, if both setpoint and measurement exist:
    • PID step is computed: output = Kp*error + Ki*integral + Kd*derivative
    • Output is clamped to [output_min, output_max]
    • Output message is constructed and published to output_topic

Validation

The controller validates configuration at init time:

  1. Unique sources - {setpoint_topic, setpoint_message} must differ from {measurement_topic, measurement_message}
  2. Non-empty paths - setpoint_path and measurement_path cannot be empty
  3. Valid output field - output_field must exist in output_message schema and be numeric

Common Use Cases

Position Control with Velocity Output

Use encoder feedback to control position, outputting velocity commands:

controller :joint_pid, {BB.PID.Controller,
  kp: 5.0, ki: 0.5, kd: 0.1,
  setpoint_topic: [:actuator, :joint, :pid],
  setpoint_message: BB.Message.Actuator.Command.Position,
  setpoint_path: [:position],
  measurement_topic: [:sensor, :joint, :encoder],
  measurement_message: BB.Message.Sensor.JointState,
  measurement_path: [:positions, 0],
  output_topic: [:actuator, :joint, :motor],
  output_message: BB.Message.Actuator.Command.Velocity,
  output_field: :velocity,
  output_frame_id: :joint
}

Velocity Control with Effort Output

Use velocity feedback to control velocity, outputting effort commands:

controller :velocity_pid, {BB.PID.Controller,
  kp: 1.0, ki: 0.1,
  setpoint_topic: [:actuator, :wheel, :velocity_cmd],
  setpoint_message: BB.Message.Actuator.Command.Velocity,
  setpoint_path: [:velocity],
  measurement_topic: [:sensor, :wheel, :encoder],
  measurement_message: BB.Message.Sensor.JointState,
  measurement_path: [:velocities, 0],
  output_topic: [:actuator, :wheel, :motor],
  output_message: BB.Message.Actuator.Command.Effort,
  output_field: :effort,
  output_frame_id: :wheel
}

Documentation

Full documentation is available at HexDocs.