Lobes

Reference for OctoMY™ Lobes — pluggable controllers that translate high-level commands into actuator control.

Heads up — type names and placement

Lobe types are described by FixoLobeType records with two notable fields: outputPorts (a QVector<FixoPort> declaring named, semantically-tagged outputs) and placement (one of board-only, native-only, both). The placement field is what decides whether the lobe runs in the AVR firmware ELF, in the node-side .so, or either. Stanza templates use the output-port semantic tags to auto-wire to actuators via Jaccard similarity. The ground-truth references are Fixo Data Model and Stanza Templates; the conceptual model below applies with these extra knobs.

Did You Know?

Lobes are named after brain regions because they encapsulate decision-making that can run either on the OctoMY™ Agent or directly on the controller hardware. When running on-board the Arduino, lobes provide lower latency, reduced power draw, and resilience — your robot can keep walking even if it briefly loses connection to the Agent. The placement field on FixoLobeType is how each lobe declares which environment it can run in.


What is a Lobe?

Lobes are named after the distinct regions in our brain dedicated to specific tasks. In OctoMY™, Lobes represent real-time processing units that take input from sensors and user controls and generate output to actuators.

A Lobe encapsulates decision-making into a unit that can run in different environments:

Since the lobe API unification, both environments run the same C++ template from src/libs/libfixo/fixo/lobes/ — the host-side .so and the AVR firmware are compiled from one header, and the parity harness (test/testLobeParity/) asserts byte-identical actuator output between them.

Benefits of on-board processing

When Lobes run on the controller hardware:

Since Lobes are well-defined, they can be tested in simulation environments before deployment to real hardware.


Example: car steering lobe

Basic version

A simple car steering lobe (steering_throttle):

In this basic example, the Lobe simply passes throttle input to the motor and steering angle to the steering servo. The key is that it sets up expectations - this is made to control any car-like robot, allowing a fancy UI with steering wheel and foot pedal to be created.

Advanced version with sensors

Adding IMU and GPS enables advanced processing:

Now the Lobe can perform:

This takes a basic toy to a full race-car with just firmware changes.


Lobe overview

Lobes provide abstraction between movement intent and actuator control:

Lobe Architecture


Built-in lobe types

These are the shipping lobe templates. The lobeType string is what a preset stanza names; the header is the single source compiled into both targets.

lobeType Header Inputs Outputs
identity IdentityLobe.hpp one per actuator passthrough to each actuator
trim TrimLobe.hpp one per actuator + trim pot sensor passthrough with wire-space trim offset on one axis
idle IdleLobe.hpp none none (explicit no-op for each channel's idle stanza)
steering_throttle SteeringThrottleLobe.hpp forward, yaw throttle servo, steering servo
differential_drive DifferentialDriveLobe.hpp forward, yaw, parking_brake, max_step_rate two accumulating stepper step targets
legged_gait LeggedGaitLobe.hpp phase_offset, velocity, turn, body_pitch, body_roll, step_height 3 x legCount servo joints (coxa/femur/tibia per leg), trot pattern
legged_gait_2d LeggedGaitLobeIntent2D.hpp forward, yaw same as legged_gait (intent-face wrapper)

Configuration constants (e.g. legCount, maxStepRate) live on the generated Config; the Phase-18 parameter-binding machinery decides per target whether each is compile-time baked or runtime-adjustable.


The template interface

A lobe is a header-only struct with a nested Instance<Config> template — no QObject, no virtuals, no allocation:

struct MyLobe {
	static constexpr LobeId id = LobeId::IDENTITY;

	template<typename Config>
	struct Instance {
		using Inputs  = typename Config::LobeInputs;
		using Outputs = typename Config::LobeOutputs;

		void init();
		void update(const Inputs &in, Outputs &out, uint16_t deltaMs);

		// Phase-7 transition hooks (usually forwarded to
		// DefaultLobeTransition<Outputs>):
		void readCurrentState(const Outputs &current);
		void interpolate(const Outputs &target, double t, Outputs &out) const;
	};
};

Key facts:

Adding a lobe type

Three steps (see the Custom Lobes tutorial for the full walk-through):

  1. Write the header in src/libs/libfixo/fixo/lobes/.
  2. Register it: one row in kUnifiedLobeSpecs (fixo/controller/FirmwareGenerator.cpp) and a FixoLobeType record in the registry. An unregistered lobe type in a preset is a loud build failure — there is no silent fallback.
  3. Add golden parity vectors to test/testLobeParity/.

Using lobes

Lobes are selected per stanza in a preset — the stanza names the lobeType, maps its outputs onto actuators, and declares the logical inputs the remote drives. Switching stanzas at runtime switches lobes, optionally blending outputs over a transition duration. See Stanza Templates for the preset side and the Fixo Data Model for the full record shapes.


Troubleshooting

Issue Cause Solution
Build fails with "unknown lobe type" Preset names a type missing from kUnifiedLobeSpecs Register the type or fix the preset
Robot goes wrong direction Motor wiring Set invert flags
Robot curves when going straight Motor speed difference Calibrate motors
Walking robot falls Wrong gait timing Tune step timing / step_height
Host and board disagree A lobe bypassing the semantic accessors or using double/libm math Fix the lobe; testLobeParity pins the contract