Skip to content

Bluetooth and the Otto App

Ahmed Dabak edited this page Jul 25, 2026 · 2 revisions

Bluetooth and the Otto App

Introduction

A serial Bluetooth module -- an HC-05, HC-06, or a BLE-serial board -- gives Otto a wireless link. OttoFlow uses it three ways:

  • AppLink -- speak the official Otto DIY app's protocol, so the phone app drives your robot.
  • Console over Bluetooth -- run the Serial Console untethered.
  • Your own stream -- read and write raw bytes for a protocol of your own.

Wiring the module

Module pin Nano pin Why
TXD 11 The board receives on pin 11
RXD 12 The board sends on pin 12
VCC 5V
GND GND

The crossover is the usual source of trouble: the module's transmit goes to the board's receive. The defaults are rxPin = 11 and txPin = 12; change them in the configuration if you wired it differently.

Warning Many HC-05/06 modules expect 3.3 V on their RX pin. Check your board -- some tolerate 5 V, others need a divider on the line from Nano pin 12. The TXD side is safe to read at 5 V.

Enabling Bluetooth

OttoConfig cfg;
cfg.bluetooth.enabled = true;
cfg.bluetooth.baud    = 9600;   // standard for HC-05/06 and BLE serial
OttoFlow::start(cfg);

start() brings the module up. After that, Bluetooth::isReady() reports its state and Bluetooth::stream() hands you the serial stream to attach a console or the app link to.

Controlling Otto from the phone app

Install the official Otto DIY app, then hand incoming commands to AppLink:

#include <OttoFlow.h>

void setup() {
  OttoConfig cfg;
  cfg.bluetooth.enabled = true;
  OttoFlow::start(cfg);
  AppLink::begin();          // listen on the Bluetooth stream
}

void loop() {
  AppLink::poll();           // handle commands, keep the current move running
}

AppLink::poll() must run every iteration. It does two jobs: it processes incoming commands, and it keeps the currently selected movement repeating until the app sends stop -- which is how the app's "walk forward" button makes Otto walk continuously rather than take one step.

Pair the module (the HC-05/06 PIN is usually 1234 or 0000), connect in the app, and the movement, gesture, sound, and matrix buttons all drive your robot.

Note AppLink movements respect Legs::disable(). A disabled robot still answers the app -- it acknowledges every command -- but it does not move. Handy for demonstrating the link on a desk.

The console over Bluetooth

The test console runs over Bluetooth just as happily as over USB -- point it at the stream instead of a baud rate:

OttoConfig cfg;
cfg.bluetooth.enabled = true;
OttoFlow::start(cfg);
Console::begin(Bluetooth::stream());

Now you can bench-test a robot that is on the floor and moving. Use any serial Bluetooth terminal app to type the commands from the Serial Console reference.

Choose one consumer of the stream: either AppLink or Console, not both at once, since they would fight over the same incoming bytes.

Your own protocol

For a custom remote control or telemetry link, take the stream directly:

Stream& bt = Bluetooth::stream();

void loop() {
  if (bt.available()) {
    char c = bt.read();
    if (c == 'f') Legs::walkForward(1);
    if (c == 's') Legs::center();
  }
  bt.println(Eyes::distanceCm());
}

It is an ordinary Arduino Stream, so anything you know from Serial applies.

The app protocol, in detail

AppLink implements the same serial command set as the official Otto_APP firmware, so it is compatible with the stock phone app. Each command is a letter followed by space-separated arguments; Otto replies with &&A%% to acknowledge receipt and &&F%% when the action finishes.

Command Meaning
S Stop, return to home
M <id> [period] [size] Start movement id, repeating until S
H <id> Play gesture id (1..13)
K <id> Play sound id (1..19)
L <pattern> Show a matrix pattern (binary)
T <hz> <ms> Play a tone
C <ll> <lr> <fl> <fr> Set and save leg trims
G <ll> <lr> <fl> <fr> Move the four leg servos directly

The movement ids (M) run 0 to 20 and cover home, walking, turning, and every dance move; gesture and sound ids follow the same order as the reference catalogs. You will not normally type these -- the app sends them -- but they are documented so you can build a compatible controller of your own.

Clone this wiki locally