-
Notifications
You must be signed in to change notification settings - Fork 0
Extending OttoFlow
- Introduction
- Anatomy of a part
- Step 1: the module
- Step 2: the facade
- Step 3: configuration
- Step 4: wire it into start()
- Step 5: an example
- Checklist
Adding a new capability -- a sensor, an actuator, an add-on board -- follows the same shape every built-in part follows. Do it right and your part feels native: same naming, same guard behaviour, same zero-config defaults. This is also the best way to contribute to OttoFlow.
Read Architecture first; this page assumes the three-layer model and the naming rules.
A part is up to four small pieces:
- A module in
src/modules/-- the hardware-truth logic. Required. - A facade in
src/facades/-- friendly sugar. Optional, add it when a nicer vocabulary helps. -
Configuration in
core/OttoConfig.h-- pins and an enable flag, with kit-accurate defaults. - Initialisation wired into
OttoFlow::start().
We will add a fictional RGB LED part as the running example.
Create the header and source under src/modules/. Names state meaning and unit; reach the shared driver and config through core/Internal.h if you need them.
src/modules/RgbLed.h
//================================================================
// OttoFlow - modules/RgbLed.h
// A common-cathode RGB LED on three PWM pins.
//================================================================
#pragma once
#include <Arduino.h>
namespace RgbLed {
void begin(); // called by start() when enabled
void setColor(uint8_t red, uint8_t green, uint8_t blue);
void off();
}src/modules/RgbLed.cpp
#include "RgbLed.h"
#include "../core/Internal.h"
using ottoflow_internal::config;
namespace RgbLed {
static bool s_ready = false;
void begin() {
if (!config().rgbLed.enabled) return;
pinMode(config().rgbLed.redPin, OUTPUT);
pinMode(config().rgbLed.greenPin, OUTPUT);
pinMode(config().rgbLed.bluePin, OUTPUT);
s_ready = true;
}
void setColor(uint8_t red, uint8_t green, uint8_t blue) {
if (!s_ready) return; // guard: silent no-op when off
analogWrite(config().rgbLed.redPin, red);
analogWrite(config().rgbLed.greenPin, green);
analogWrite(config().rgbLed.bluePin, blue);
}
void off() { setColor(0, 0, 0); }
} // namespace RgbLedThe guard pattern is the important part: when the part is disabled or not yet initialised, its calls do nothing rather than crash. Every built-in module behaves this way -- it is what makes disabling and optional hardware painless. See Architecture.
If a friendlier vocabulary helps, add an inline facade in src/facades/. It contains no logic -- only forwarding -- so it stays in lockstep with the module and costs nothing.
src/facades/Mood.h
//================================================================
// OttoFlow - facades/Mood.h
// Express a mood as a colour on the RGB LED.
//================================================================
#pragma once
#include "../modules/RgbLed.h"
namespace Mood {
inline void happy() { RgbLed::setColor(0, 255, 0); }
inline void angry() { RgbLed::setColor(255, 0, 0); }
inline void calm() { RgbLed::setColor(0, 0, 255); }
inline void off() { RgbLed::off(); }
}Not every module needs a facade. LightSensor and Bluetooth ship without one because their module names are already plain.
Add a config struct with kit-accurate defaults and an enabled flag, then include it in OttoConfig.
In core/OttoConfig.h:
struct RgbLedConfig {
bool enabled = false; // off by default
uint8_t redPin = A0;
uint8_t greenPin = A1;
uint8_t bluePin = A2;
};
struct OttoConfig {
// ... existing members ...
RgbLedConfig rgbLed;
};Optional hardware is enabled = false by default; a part every kit has can default to on.
In core/OttoCore.cpp, include the header and initialise the part where the other optional modules are brought up:
#include "../modules/RgbLed.h"
// inside start(const OttoConfig& config), after s_config = config;
if (s_config.rgbLed.enabled) RgbLed::begin();Your part now comes up with the rest of the robot whenever it is enabled.
Ship a short sketch so people can see it work, and add it to the single top-level include if it is a core part. The Examples page is where sketches live today.
#include <OttoFlow.h>
void setup() {
OttoConfig cfg;
cfg.rgbLed.enabled = true;
OttoFlow::start(cfg);
Mood::happy();
}
void loop() {
Mood::angry(); delay(500);
Mood::calm(); delay(500);
}Finally, add the includes to src/OttoFlow.h alongside the other modules and facades so a sketch gets your part from the one include.
A part that does all of this will feel like it was always there:
- Module in
src/modules/, unit-explicit names, guard pattern for the disabled state. - Optional facade in
src/facades/, inline forwarding only, no logic. - Config struct in
OttoConfigwith kit-accurate defaults and anenabledflag. - Initialisation wired into
OttoFlow::start(). - Headers added to
src/OttoFlow.h. - An example sketch.
-
.cppfiles include<Otto.h>only if they truly need the driver -- public headers never do.
Parts that follow this pattern are welcome as contributions. Several are already sketched out on the Roadmap.
OttoFlow is licensed under GPL-3.0 and wraps OttoDIYLib. Buy the kits at ottodiy.com.
Prologue
Getting Started
The Basics
Digging Deeper
- Bench Testing
- Serial Console
- Calibration
- Bluetooth and the Otto App
- Modules
- The Driver
- Extending OttoFlow
Reference