Skip to content

The Driver

Ahmed Dabak edited this page Jul 24, 2026 · 1 revision

The Driver

Introduction

Underneath every facade and every module sits the raw OttoDIYLib driver -- a C++ object of class Otto. OttoFlow does not wall it off. The driver is the official escape hatch: if OttoDIYLib can do something OttoFlow has not wrapped yet, you can still reach it, in a documented and supported way.

A framework that hides its lower layer loses its advanced users. OttoFlow's promise is no locked doors -- and this is the door.

Reaching the driver

The escape hatch lives in its own header, not in <OttoFlow.h>. Include both:

#include <OttoFlow.h>
#include <OttoFlowDriver.h>

void setup() {
  OttoFlow::start();

  Otto& raw = OttoFlow::driver();
  raw.oscillateServos(/* ... */);      // anything OttoDIYLib can do
}

OttoFlow::driver() returns a reference to the one driver instance the framework itself uses. It is valid after OttoFlow::start(). Changes you make through it are visible to the framework, and vice versa -- there is only ever one robot.

Why it is a separate include

<OttoFlowDriver.h> pulls in all of OttoDIYLib's headers, and those define lowercase macros for the mouth shapes: heart, smile, happyOpen, and so on. In a beginner's sketch those macros would collide with ordinary variable names and produce baffling errors.

Keeping the driver in an opt-in header means the plain <OttoFlow.h> include stays clean, and you only take on the macros when you deliberately ask for the driver. This is the same reasoning that gives the framework its OttoFlow:: namespace -- see Architecture.

What you can do with it

Everything OttoDIYLib exposes, including things OttoFlow deliberately leaves raw:

Otto& raw = OttoFlow::driver();

raw.putMouth(heart);                 // raw mouth macro
raw._moveServos(500, positions);     // low-level 4-servo keyframe
raw.getRestState();                  // driver's internal rest flag
raw.enableServoLimit(255);           // OttoDIYLib features with no wrapper yet

Consult the OttoDIYLib source and examples for the full surface. If you find yourself using a driver call often, that is a good sign it should become a module or facade -- see Extending OttoFlow, and consider contributing it back.

Playing nicely with the framework

The framework and the driver share one instance, so a few habits keep them in agreement:

  • Respect the guards yourself. Calls made straight on the driver bypass Legs::disable() and Voice::mute(). If you drive servos through raw, the robot moves even when the framework thinks movement is off. Check Legs::isEnabled() first if that matters.
  • Trims still apply to the driver's own movement calls (walk, turn, _moveServos), because trims live inside the driver. They do not apply to arm servos, which are separate.
  • Let start() do the setup. The driver has already been initialised with your configuration by the time driver() returns. Do not call init() again unless you mean to reconfigure from scratch.
  • Prefer the higher layer when one exists. The driver is for what is not wrapped. Mixing Mouth::show() and raw.putMouth() in the same sketch works, but the facade reads better and survives future refactors.

Clone this wiki locally