Navigation
Strategy: RC-First
The rulebook permits remote-controlled robots, and remote control is the only mode this firmware has. The autonomous FSM was deleted — with no encoders and no IMU it could never have been coordinate-accurate, and an operator tagging sectors by eye produces better-labelled data than a dead-reckoned guess.
| Mode | How to enter | What it does |
|---|---|---|
| RC (the only mode) | Boots straight into RC — no build flag, no mode select | RobotAP + phone joystick page; operator drives, samples, and tags sectors. An ultrasonic collision guard cuts forward drive before an impact |
The WiFi AP and web server run in both modes so judges can always pull /data immediately after a run.
RC Run Procedure
- Power on → join
RobotAP→ open the RC page (192.168.4.1/envirobot.local). - Drive to a zone with the D-pad — the run clock starts on the first drive/sample and shows in the posbar ⏱ (amber 7:00, red 8:00). Select the current sector (S1–S4) on the page.
- Stop next to the zone (never over a water slot). Press Sample Water or Sample Soil (~6s, blocking).
- The arm deploys, reads a 3s median window, retracts; the reading is logged with your sector tag, printed to serial, and appears in the live table.
- Repeat for all 12 zones (1 water + 2 soil per sector). Press End Run to freeze the clock and finalise
results.json.
Full step-by-step with failsafes and the live data station: Operating Guide.
Position Estimation (and its limits)
The robot has no wheel encoders and no IMU. Both heading and position are estimated open-loop from the commanded wheel speeds (differential-drive model):
- Heading — integrated turn rate
ω = (cmdR − cmdL) / WHEEL_BASE_MM. No gyro to correct wheel slip, so it drifts. - Distance — commanded motor speed × time (
MM_PER_SEC_AT_DRIVE, calibrated by timing a 1m drive). Rough.
// position.cpp — dead-reckoning update, every 20ms
float omega = (cmdR_mm_s - cmdL_mm_s) / WHEEL_BASE_MM; // commanded, not measured
pos.heading_rad += omega * dt;
float v = (cmdL_mm_s + cmdR_mm_s) / 2.0f;
pos.x_mm += v * dt * cosf(pos.heading_rad);
pos.y_mm += v * dt * sinf(pos.heading_rad);
The estimate feeds the path[] log for visualisation only. It is never used for safety decisions — the collision guard is always a live ultrasonic read, and zones are identified by the operator's eyes.
If the robot gets stuck, officials place it at the arena centre. With no IMU, both heading and position are lost on a reposition — send REZERO (RC page ⌖ button) with the eyeballed heading to re-zero the pose at the new position. Quadrant-level sector estimates survive; the fine path plot is best-effort.
Collision Guard
The HC-SR04 is the robot's only obstacle sensor, and its only job is to stop a crash. It does not steer, and it does not detect zones.
every loop (wifi_server.cpp -> updateCollisionGuard)
|
| one ping every OBSTACLE_PING_MS (100ms)
|
| reading < obstacleStopMm ?
| yes -> count a hit (capped at OBSTACLE_TRIP_HITS)
| no, and >= obstacleStopMm + OBSTACLE_HYST_MM (40mm) -> reset hits
| in between -> change nothing (this is the sticky band)
|
| hits reached OBSTACLE_TRIP_HITS (2) ?
| on the rising edge only: brake -> stop, cancel any timed cal run
| while blocked: POST /rc FWD or CAL_FWD -> HTTP 409 {"error":"obstacle","mm":N}
| BWD, LEFT, RIGHT all still pass
Why each part exists
| Mechanism | Constant | Why |
|---|---|---|
| 2-hit debounce | OBSTACLE_TRIP_HITS |
One glitched HC-SR04 read must not halt a scored run |
| Hysteresis band | OBSTACLE_HYST_MM |
A bare threshold chatters on and off at the boundary |
| Edge-triggered cut | — | Motors are cut once, when the guard trips. Cutting every tick would brake the reverse you are using to escape |
| Reverse/turns never blocked | — | A guard that also blocks escaping strands the robot, which costs more than the collision |
| Master switch | collisionGuardOn |
The guard is optional. Uncheck it in the Diag config editor for deliberate close work; distance is still measured and reported |
| Ping rate | OBSTACLE_PING_MS |
pulseIn blocks up to 25ms on a missing echo — 10Hz keeps RC latency low |
The decision logic is a pure function in collision.h, unit-tested on the host by arduino/tools/test_collision.cpp.
What it does NOT protect against
Recessed water slots. The ultrasonic looks forward, not down, so it cannot see a slot at all. Nothing in the firmware keeps a wheel out of one — that is entirely the operator's job: stop short, sample, reverse away, never drive across.
Angled or soft walls also reflect the ping away and read as "nothing in range". Treat the guard as a backstop for the operator's eyes, not a replacement for them.
Sector labels
The operator selects S1–S4 on the RC page before each sample, and each button carries its terrain name (S1 URBAN … S4 STONE). There is no estimation involved — which is exactly why this is the accuracy path. REZERO re-zeroes the dead-reckoned pose after an official re-centres a stuck robot.