Skip to content

About

MILA (Miniature Integrated Logic Automaton): a small WiFi tank robot on an Arduino Uno + ESP8266. Web dashboard, live telemetry, obstacle avoidance and desktop controllers in C++, C#, F#, Python, Rust, Go and Julia, plus a simulator and a Fortran telemetry and dataset toolkit.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

MILA

MILA

Miniature Integrated Logic Automaton

A DREAM Robotics Agent

Twitter: @NorowaretaGemu License: MIT

C++ Arduino Git Shell

  • Robot Type: Tank
MILA avatar: a human representation of the robot

MILA

MILA Robot front view MILA Robot angled view MILA Robot side view MILA Robot top view

Software


MILA's Python controller, driving a pretend MILA

Windows controller: builds/MILA-Controller.exe shows MILA's own web page in a window, so it always matches the page. Source: scripts/web_controller.


Related Projects

ARIA
ARIA
KIDA-01
KIDA-01
KIDA-00
KIDA-00
WHIP
WHIP
NORA
NORA
NIKA
NIKA
IDA
IDA
ARM
ARM
DREAM
DREAM
RIFT
RIFT
RITA
RITA
NINA
NINA
TINA
TINA
AURA
AURA
FLORA
FLORA

Overview

Website: https://cursedprograms.github.io/MILA-Robot-v00/

MILA is a small tank-chassis robot you can drive over WiFi. She hosts her own web dashboard, streams live sensor data, and can steer itself around obstacles.

  • Drive modes: WASD (car-style), TANK (independent tracks) and OBSTACLE (autonomous).
  • Live telemetry: front, left and right distance, temperature, humidity and the last IR remote key.
  • WiFi dashboard: served by the ESP8266 on port 5010. She joins the fleet's "RIFT" network first, NORA's "NORA" network if there's no RIFT, and hosts her own "MILA" access point at 192.168.4.1 if neither is around - and keeps looking, so she moves to RIFT (or NORA) as soon as it appears.
  • Safety: a collision guard force-stops her in manual modes, and a watchdog stops her if the connection drops.
  • Speed: cycle 100 / 75 / 50 / 25 % from the IR remote, or set an exact value from the dashboard slider (see the pinout warning below).
  • MIND mode: the LLM on her PC controller drives her, one short move at a time: she looks (her distances, or a SCAN left / ahead / right), thinks out loud on screen, says what she's doing in her own voice (playful and quick), moves, and remembers. M on the Python controller, or MIND on her dashboard while it's connected. Only on an x86 PC with 8 GB+ RAM and a GTX 1050 or better.
  • Ticks near walls: driving forward, the buzzer ticks faster as a wall gets close, like a parking sensor (ticks, not a tone: the buzzer shares a timer with the IR receiver).
  • Lights in the dark: driving forward in the dark (the light sensor), her LED goes white instead of blue to light the way.
  • Android app (Android App/, package com.synthwomb): FIND gets her address from the fleet list, or type it in. Hold the stick to drive, let go to stop, and her modes, HELLO, DANCE, speed and the wheel lock are buttons. Build it with gradlew assembleDebug. Install: MILA-debug-2026-10-30.apk.
  • FOLLOW: she keeps your hand 30 cm from her sonar, in short pulses. A button on her dashboard, F on the Python controller, 4 on the remote (a guessed code).
  • HELLO and DANCE: on the spot, in WASD or TANK: her sonar head shakes and she beeps. Buttons on her dashboard, H and N on the Python controller, 5 and 6 on the remote (guessed codes). Anything from her ESP cuts them short.
  • Live view: her Python controller draws her from above, tracks turning, sonar reaching out.
  • Several devices: up to four can be unlocked at once, her page follows a mode changed elsewhere, and /status carries rev, her settings in one number.
  • Gamepad rumble: the pad buzzes harder as she nears a wall while you drive her forward.
  • The Python controller unlocks her with her PIN when it connects (it didn't before, so every drive command came back LOCKED).

Hardware

  • Small metal tank robot chassis
  • UNO + WiFi R3 style board (ATmega328P + integrated ESP8266)
  • 2S 18650 battery pack
  • L298N motor driver
  • MG95 servo
  • HC-SR04 ultrasonic sensor
  • 2× 5 V DC motors
  • RGB LED
  • AHT10 temperature & humidity sensor (I2C)
  • Photoresistor module (digital out)
  • Buzzer
  • NEC IR receiver + remote

Pinout (scripts/MILA/MILA.ino)

Signal Pin Signal Pin
Ultrasonic TRIG / ECHO 2 / 3 Servo 11
Right motor IN1 / IN2 / ENA 4 / 5 / 9 Buzzer 8
Left motor IN3 / IN4 / ENB 6 / 7 / 10 Light sensor (DO) 12
RGB LED R / G / B A0 / A1 / A2 IR receiver A3
AHT10 SDA / SCL A4 / A5 ESP8266 link Serial (DIP 1 + 2)

Warning

Speed control and the servo share a timer. ENA and ENB are on pins 9 and 10, which the Servo library takes over (Timer1). At 100 % speed the motors are switched fully on and work. At 75 / 50 / 25 % the PWM is lost, so the motors may barely move or stop, and the servo can twitch. Fix it by swapping four wires and the matching constants in MILA.ino: ENA ↔ IN2 (ENA → 5, IN2 → 9) and ENB ↔ IN3 (ENB → 6, IN3 → 10). Pins 5 and 6 are Timer0 PWM, which nothing else uses.


Desktop controllers

Every controller lives in scripts/ and has a build-and-run script in .sh, .bat and .ps1 form.

Language UI toolkit Run Radar & graphs Gamepad
Python pygame run.sh
C++ Win32 / GDI scripts/run-cpp.* ✓ ✓
C# WinForms scripts/run-csharp.* ✓ ✓
F# (preview) WinForms scripts/run-fsharp.* ✓ ✓
Rust egui scripts/run-rust.*
Go Ebitengine scripts/run-go.*
Julia GLMakie scripts/run-julia.*

Controls

Every key, gamepad button and remote button. The same list opens in its own window: F1 in the Python controller, KEYS on her web page (/keys), or python scripts/keymap.py.

Keyboard: web page and every desktop controller

Key Does
1 / 2 / 3 WASD / TANK / OBSTACLE mode
F FOLLOW mode: she keeps your hand 30 cm from her sonar (Python controller; a button on her web page)
H / N HELLO / DANCE, on the spot, in WASD or TANK (Python controller; buttons on her web page)
W / Up, S / Down forward / back (WASD mode, hold)
A / Left, D / Right turn left / right (WASD mode, hold)
W+A, W+D, S+A, S+D curve: one track drives (Python controller; the stick too)
Python controller: driving in OBSTACLE takes over (WASD); she stops when you let go
Q / A left track forward / back (TANK mode, hold)
E / D right track forward / back (TANK mode, hold)
Space stop, in any mode (controllers; the STOP button too)
EMULATE / VOICE / THEME Python controller: a pretend MILA on this PC / her voice / colours
M her MIND mode on / off: the LLM on this PC drives her (Python controller; x86 PC, GTX 1050+)
X speed preset: 100, 75, 50, 25 % (web page)
F1 this window (Python controller; KEYS on the web page)
Esc quit (controllers)
driving from the web page needs her PIN first (UNLOCK)

Gamepad or joystick (ARM's flight stick works too)

Key Does
Left stick / D-pad drive, half-way round curves (WASD mode; takes over from OBSTACLE)
LB / RB, right stick, twist turn left / right (Python controller)
A (button 0) stop (any mode)
B (1) / X (2) / Y (3) OBSTACLE / WASD / TANK mode (Python controller)
Start (7) speed + 25 % (Python controller)
C++, C# and F# controllers: stick or D-pad drive, triggers speed, A stops, bumpers change mode

Her IR remote

Key Does
1 / 2 / 3 WASD / TANK / OBSTACLE mode
4 / 5 / 6 FOLLOW mode / HELLO / DANCE (codes guessed: 0x3, 0x4, 0x5)
Up / Down / Left / Right drive (WASD mode, hold)
Red / Green left track forward / back (TANK mode)
Yellow / Blue right track forward / back (TANK mode)
OK speed preset: 100, 75, 50, 25 %

NORA's IR link (her web page, controller or Bluetooth)

Key Does
arrows / I J K L drive
STOP stop, and out of obstacle mode
AUTO / MANUAL OBSTACLE / WASD mode
SPEED speed preset
LOCK / UNLOCK lock her wheels (nothing drives her) / free them
the same lock over WiFi: /motorlockOn, /motorlockOff?pw= (RIFT's Lock all / Unlock all)

The C++, C# and F# controllers also have a proximity radar, live distance / temperature / humidity graphs with CSV export, an exact speed slider, network discovery (--host auto), remembered host / port / mode, and Xbox gamepad support (stick or D-pad to drive, triggers for speed, A to stop, bumpers to change mode).


Quick start

DIP switches (UNO + WiFi R3 board)

The board's 8 DIP switches choose what the USB port and the two chips are connected to. Switch 8 is unused.

What you're doing 1 2 3 4 5 6 7 8
Running MILA (Arduino ↔ ESP8266) ON ON off off off off off off
Upload scripts/MILA (USB → ATmega328) off off ON ON off off off off
Upload scripts/esp8266 (USB → ESP8266, flash mode) off off off off ON ON ON off
ESP8266 Serial Monitor (USB ↔ ESP8266) off off off off ON ON off off
USB ↔ ATmega328 ↔ ESP8266 all linked ON ON ON ON off off off off
  1. Set 3 + 4 ON and upload scripts/MILA.
  2. Set 5 + 6 + 7 ON and upload scripts/esp8266 (press reset if the upload won't start).
  3. Set 1 + 2 ON, everything else off, and power-cycle to drive her.

In the running setting the Arduino's serial goes to the ESP, not USB, so the Serial Monitor shows nothing. Use 3 + 4 to read her DIST: / IR: lines over USB.

Note

Check this against the table printed on your board, as some clones differ slightly.

Flash and connect

The ESP8266's WiFi passwords and her control PIN are in scripts/esp8266/secrets.h (the fleet's: RIFT and NORA mmvbkbak, PIN 1234; secrets.example.h shows the layout). Flash the firmware from scripts/esp8266 and scripts/MILA with the Arduino IDE (set the DIP switches above first):

Sketch Board (Tools > Board) Needs
scripts/MILA Arduino UNO Libraries: IRremote (4.x), Servo
scripts/esp8266 Generic ESP8266 Module (Flash Size 4MB, FS 2MB OTA) The esp8266 boards: add https://arduino.esp8266.com/stable/package_esp8266com_index.json under File > Preferences > Additional boards manager URLs, then install esp8266 in the Boards Manager

Then power the robot on, and join her WiFi network. Then open the dashboard in a browser, or run a desktop controller:

./run.sh                      # macOS / Linux / Git Bash
run.bat                       # Windows (cmd)
.\run.ps1                     # Windows (PowerShell)

./run.sh --host 192.168.4.1 --port 5010
./run.sh --emulate            # a pretend MILA on this PC (scripts/fake_mila.py), no robot

The Python controller (run.*, scripts/mila_controller.py):

  • Two keys curve her: W+A drives only her right track, so she arcs forward-left (W+D, S+A, S+D the same way), and so does the stick half-way round. Her UNO has no speed control yet (section 12 of the fleet TODO), so a curve is one track on, the other off.
  • Driving takes over: driving in OBSTACLE puts her in WASD, and she stops when you let go. STOP (Space, the pad's A) works in any mode, TANK too.
  • Settings stick: mode and speed are sent again until her /status says so.
  • EMULATE: a pretend MILA in a pretend room: her sonar reading drops as she drives at a wall, and her UNO's guard stops her within 20 cm. VOICE: she talks through this PC (Zira, quick and cheeky): modes, walls, her wheel lock, speed. THEME: DEFAULT, LIGHT or CUSTOM, from colour_scheme.xml.
  • Pads: Xbox, PlayStation and Switch pads all have the same buttons (gamepad_drive.py, SDL's controller layer). Her MIND log: ~/.robot_minds/logs/MILA-<date>.log.

If MILA joined the RIFT or NORA network (fleet mode) she won't be at 192.168.4.1: RIFT's dashboard lists her address. Press SCAN NETWORK in the C++, C# or F# controller, or start it with --host auto.

Talking (in Brainfuck)

The robots talk in Brainfuck: every phrase is a Brainfuck program that prints its words (hello is ++++++++[>+++++++++++++<-]>.+. → hi), and a robot says it by beeping the program on its buzzer, one tone per symbol, pitched to its own voice. The phrasebook (talk_bf.h) is generated by RIFT's Fleet/brainfuck_talk.py: phrases 0–6 (hello, how are you, happy, curious, sleepy, let's play, bye) and their replies 7–13. RIFT runs the conversations between robots over WiFi and logs them on its dashboard; it asks MILA to speak with GET /chirp?u=<0-13>. Her UNO plays TALK:<u> (from the ESP8266) through her own beep(), a little higher than NORA, and respects MUTE; she only talks while stopped outside OBSTACLE mode. The ESP8266 forwards /chirp to the UNO and registers talk:5010, so RIFT includes her in the conversations.

Driven by NORA (fleet IR link)

NORA can drive MILA through her IR transmitter, from her web page, Python controller or Bluetooth. The frames are Samsung-format IR at address 0x0DA2, with the fleet link's commands: 0x48 forward, 0x49 back, 0x4A left, 0x4B right, 0x4C stop, 0x4D obstacle mode, 0x4E manual, 0x4F speed. Driving switches her into WASD mode; stop also leaves OBSTACLE mode, and speed cycles the speed like the OK button. She stops once the link has been quiet for 500 ms. Link frames print as LINK:0x...


Analyzing a session (Fortran)

The EXPORT CSV button saves the whole session to Documents\MILA-telemetry. scripts/fortran/telemetry_stats.f90 turns an export into a report: sample rate, time in each drive mode, per-sensor min / max / mean / standard deviation, outliers (beyond 3σ), connection gaps and close calls (front distance under 20 cm). It needs gfortran and picks the newest export by default:

scripts/run-fortran.sh              # or run-fortran.bat / run-fortran.ps1
scripts/run-fortran.sh my_run.csv

Training data: teach MILA to drive herself

Drive her around by hand (IR remote, keyboard, gamepad or the dashboard), press EXPORT CSV, and scripts/fortran/make_dataset.f90 turns the sessions into a dataset for imitation learning: each row pairs what the ultrasonic sensor saw with what the human was doing.

scripts/run-dataset.sh      # every export -> Documents\MILA-telemetry\datasets\drive_policy.csv
scripts/run-dataset.sh out.csv session1.csv session2.csv
  • Only human driving is kept (wasd and tank modes). Obstacle mode (she drives herself) and moments the collision guard overrode you are dropped.
  • The label is the real motor state. The Arduino now reports MOTOR:<left>,<right> (each -1 / 0 / +1), so the label is the same whichever input you used. The IR remote moves the motors directly and never shows up as a web command, which is why this is needed. It maps to 9 classes: STOP, FORWARD, BACKWARD, LEFT, RIGHT, L_FWD, L_BWD, R_FWD, R_BWD.
  • Features: front distance, how it is changing (d_dist, 5-sample average and minimum), speed and mode. dist_valid is 0 when the sensor returned no echo, in which case dist is carried over from the last good reading.
  • No data leakage: every export is its own session, and the last 20 % of each is marked split=val.
  • Left/right distances are not features: the firmware only measures them during the obstacle-mode scan, so in human-driven data they are stale.

Columns: session,t_s,split,mode,mode_id,dist,dist_valid,d_dist,dist_avg5,dist_min5,speed,motor_l,motor_r,action,action_id,next_action,next_action_id. action is what you were doing at that sample and next_action what you did one sample (about 0.4 s) later, so use whichever suits your model. Load it with pandas, or anything that reads CSV.

Reflash both boards (scripts/MILA and scripts/esp8266) to get the motor columns. Exports from older firmware still work, but their labels come from the last web command only, so IR driving is invisible in them. The same reflash also makes the front sensor sample continuously in manual modes, so the distance no longer goes stale while you are stopped or turning.


Testing without a robot

scripts/sim holds a mock MILA that serves the same endpoints as the firmware with simulated sensor data:

scripts/run-sim.sh
scripts/run-cpp.sh --host 127.0.0.1

WiFi signal

Her /status has wifi_rssi (her signal, 0 while she hosts), which RITA's STATS shows. Her ESP8266 has no Bluetooth: Bluetooth controllers reach her through NORA's IR link.


Who's nearby (ESP-NOW + Bluetooth LE)

Every robot sends a small "I'm here" beacon twice a second and listens for the others'. From the signal strength it knows roughly how close each one is, and from how that changes over time whether it's coming closer, steady or leaving:

Signal Zone
above −45 dBm very close
−45 to −60 dBm near
−60 to −75 dBm medium
below −75 dBm far

It's coarse (walls, bodies and antenna angle all change it): for "who's around", not distance. Precise collision avoidance stays with the ultrasonic and ToF sensors.

They tell each other what they're doing, because a rising signal looks the same from both sides even when only one robot moves. Over ESP-NOW they say it in Brainfuck, like the fleet's conversations: each beacon carries a program that prints park, go, wait or hand (a human is driving), and the receiver runs it. Bluetooth adverts are too small for a program, so BLE carries the same state as one byte.

Who makes way, in self-driving modes only:

The other robot... So this one...
is parked is the one closing in: steers away
is yielding carries on, carefully
is driven by a human makes way (it's unpredictable)
drives itself follows the alphabet: KIDA00, KIDA01, NORA, WHIP; everyone makes way for MILA, who can't hear the others
is leaving carries on

Making way = stop for 2 s, turn away, then drive on (and not yield again for 5 s, so two robots that stay close don't take turns forever). While another robot is near, or coming closer, it drives slower with wider margins.

MILA's ESP8266 sends the beacon over ESP-NOW, with what she's doing said in Brainfuck (go in obstacle mode, hand when you drive her, park when she's stopped), so NORA and WHIP know where she is. Her ESP8266 can't report the signal strength of what it receives, so she can't avoid the others: they always make way for her. Code: scripts/esp8266/esp8266.ino.


Screenshots

C++ controller C# controller Python controller Web dashboard

C++ controller, C# controller, Python controller, Web dashboard. Running against MockMila (scripts/sim), so the telemetry is live.



© Cursed Entertainment 2026

The DREAM Robotics ecosystem

Minds — DREAM companion · TINA engineer · NINA caretaker · RIFT fleet hub · LYCEA training school
Robots — NORA · MILA · WHIP · KIDA-00 · KIDA-01 · IDA · ARM · NIKA · ARIA · AURA weather · FLORA gardener
Remotes — RITA handheld remote

About

MILA (Miniature Integrated Logic Automaton): a small WiFi tank robot on an Arduino Uno + ESP8266. Web dashboard, live telemetry, obstacle avoidance and desktop controllers in C++, C#, F#, Python, Rust, Go and Julia, plus a simulator and a Fortran telemetry and dataset toolkit.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages