Guide to Wireless Mesh AV Fleets: Coordinating a Multi-Vehicle Swarm Using ESP-NOW Protocol and 3-Wheel Chassis
Wireless Mesh AV Fleets: Coordinating a Multi‑Vehicle Swarm Using ESP‑NOW and 3‑Wheel Chassis
In this tutorial you learn how to build, configure, and program a wireless mesh of autonomous vehicles (AVs) that communicate through the ESP‑NOW protocol. The guide focuses on a lightweight 3‑wheel chassis, making the swarm agile, easy to maintain, and cost‑effective for indoor or outdoor deployments.
“A mesh network behaves like a living organism – every node talks, learns, and adapts in real time.”
Table of Contents
- Overview of Mesh AV Fleets
- Hardware Selection – 3‑Wheel Chassis & ESP‑32
- Getting Started with ESP‑NOW
- Designing the Mesh Protocol
- Full Code Example
- Testing & Debugging Tips
- Conclusion & Next Steps
Overview of Mesh AV Fleets
A wireless mesh AV fleet consists of multiple autonomous vehicles that share state information without a central router. Each vehicle can forward packets, so the network self‑heals when a node leaves or fails. Using ESP‑NOW, the fleet achieves sub‑millisecond latency, low power draw, and encryption—all critical for real‑time swarm coordination.
Why Choose ESP‑NOW?
- Operates on the 2.4 GHz band with direct device‑to‑device messaging.
- No Wi‑Fi router required – perfect for remote or RF‑restricted zones.
- Supports up to 20 peers per ESP‑32, enough for small‑to‑medium swarms.
- Built‑in ACK and retransmission handling ensures reliability.
Hardware Selection – 3‑Wheel Chassis & ESP‑32
The 3‑wheel chassis offers a stable base while allowing tight turning radii. Pair it with an ESP‑32 development board (e.g., WROOM‑32) for seamless ESP‑NOW integration.
| Component | Key Specs | Recommended Model |
|---|---|---|
| Microcontroller | Dual‑core, 240 MHz, 520 KB SRAM | Espressif ESP‑32 WROOM‑32 |
| Chassis | 3‑wheel, 150 mm wheelbase, aluminum frame | Tamiya 3‑Wheel Mini‑Kit |
| Motor Driver | Continuous 12 A, PWM compatible | DRV8833 Dual H‑Bridge |
| Power | 7.4 V Li‑Po, 2000 mAh | Turnigy Nano‑Tech 7.4V 2000 mAh |
Wiring Quick‑Start
- Connect motor driver INA/INB pins to ESP‑32 GPIO 14, 27, 33, 25.
- Link motor power terminals to the Li‑Po pack (respect polarity).
- Attach a 5 V regulator to power the ESP‑32 from the same battery.
- Mount a small 2 cm ultrasonic sensor on the front for obstacle avoidance (optional).
Getting Started with ESP‑NOW
First, install the ESP‑Now library via the Arduino IDE. The following snippet initializes ESP‑NOW and registers a peer.
#include <WiFi.h>
#include <esp_now.h>
#define CHANNEL 1
#define MAX_PEERS 20
// Structure for transmitting vehicle state
typedef struct __attribute__((packed)) {
uint8_t id;
float x;
float y;
float heading;
uint8_t battery; // percent
} VehicleState;
// Global variables
VehicleState myState = {0};
uint8_t broadcastAddress[] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
void onDataRecv(const uint8_t *mac, const uint8_t *incomingData, int len) {
VehicleState incoming;
memcpy(&incoming, incomingData, sizeof(VehicleState));
// Handle received state (e.g., update local map)
}
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.disconnect(); // Ensure clean start
if (esp_now_init() != ESP_OK) {
Serial.println("ESP‑NOW init failed");
return;
}
esp_now_register_recv_cb(onDataRecv);
// Add broadcast peer (all nodes listen to this address)
esp_now_peer_info_t peerInfo = {};
memcpy(peerInfo.peer_addr, broadcastAddress, 6);
peerInfo.channel = CHANNEL;
peerInfo.encrypt = false;
if (!esp_now_add_peer(&peerInfo)) {
Serial.println("Failed to add broadcast peer");
}
// Set device ID (unique per vehicle)
myState.id = random(1, 250);
}
void loop() {
// Example: update position from dead‑reckoning (replace with real sensors)
myState.x += 0.01;
myState.y += 0.00;
myState.heading = fmod(myState.heading + 1.0, 360.0);
myState.battery = 95; // dummy value
// Broadcast state to the mesh
esp_now_send(broadcastAddress, (uint8_t*)&myState, sizeof(VehicleState));
delay(100); // 10 Hz update rate
}
Designing the Mesh Protocol
The mesh protocol decides how each vehicle shares its state and reacts to peers. Below is a simple flowchart described in prose:
- Heartbeat – Every 100 ms each node broadcasts its
VehicleState. - Neighbour Table – Each vehicle keeps a table of the last 10 messages per peer, using a timestamp to discard stale entries.
- Collision Avoidance – If two vehicles predict a trajectory overlap within 30 cm, the one with the higher
idyields. - Dynamic Leader Election – The vehicle with the lowest
batterylevel becomes a temporary “relay” to forward messages for nodes at the edge of the mesh.
Implement the neighbour table with a fixed‑size array; it stays memory‑light on the ESP‑32.
Sample Neighbour Table Structure
typedef struct {
uint8_t id;
float x;
float y;
float heading;
uint8_t battery;
unsigned long lastSeen; // millis()
} PeerInfo;
PeerInfo peers[MAX_PEERS];
Full Code Example – Swarm Coordination
The complete sketch below integrates the neighbour table, simple obstacle avoidance, and leader‑relay logic.
#include <WiFi.h>
#include <esp_now.h>
#define CHANNEL 1
#define MAX_PEERS 20
#define UPDATE_RATE 100 // ms
#define COLLISION_DIST 0.30f // meters
// ---------- Data Structures ----------
typedef struct __attribute__((packed)) {
uint8_t id;
float x;
float y;
float heading;
uint8_t battery;
} VehicleState;
typedef struct {
VehicleState state;
unsigned long lastSeen;
} PeerInfo;
// ---------- Global Variables ----------
VehicleState myState = {0};
PeerInfo peers[MAX_PEERS];
uint8_t broadcastAddr[] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
void onDataRecv(const uint8_t *mac, const uint8_t *data, int len) {
if (len != sizeof(VehicleState)) return;
VehicleState inc;
memcpy(&inc, data, sizeof(VehicleState));
// Update or insert peer
int idx = -1;
for (int i = 0; i < MAX_PEERS; i++) {
if (peers[i].state.id == inc.id) { idx = i; break; }
}
if (idx == -1) {
// Find empty slot
for (int i = 0; i < MAX_PEERS; i++) {
if (peers[i].state.id == 0) { idx = i; break; }
}
}
if (idx != -1) {
peers[idx].state = inc;
peers[idx].lastSeen = millis();
}
}
// ---------- Helper Functions ----------
float distance(float x1, float y1, float x2, float y2) {
return sqrt(sq(x2 - x1) + sq(y2 - y1));
}
// Simple avoidance: stop if any peer is too close ahead
bool shouldYield() {
for (int i = 0; i < MAX_PEERS; i++) {
if (peers[i].state.id == 0) continue;
if (millis() - peers[i].lastSeen > 500) continue; // stale
// Predict relative position in 0.5 s (linear assumption)
float aheadX = myState.x + 0.5f * cos(radians(myState.heading));
float aheadY = myState.y + 0.5f * sin(radians(myState.heading));
float d = distance(aheadX, aheadY,
peers[i].state.x,
peers[i].state.y);
if (d < COLLISION_DIST && myState.id > peers[i].state.id) {
return true; // lower‑ID vehicle has priority
}
}
return false;
}
// ---------- Setup ----------
void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.disconnect();
if (esp_now_init() != ESP_OK) {
Serial.println("ESP‑NOW init failed");
while (true);
}
esp_now_register_recv_cb(onDataRecv);
esp_now_peer_info_t broadcastPeer = {};
memcpy(broadcastPeer.peer_addr, broadcastAddr, 6);
broadcastPeer.channel = CHANNEL;
broadcastPeer.encrypt = false;
esp_now_add_peer(&broadcastPeer);
myState.id = random(1, 250);
myState.x = 0.0;
myState.y = 0.0;
myState.heading = random(0, 360);
myState.battery = 100;
}
// ---------- Main Loop ----------
void loop() {
static unsigned long lastUpdate = 0;
if (millis() - lastUpdate >= UPDATE_RATE) {
// Update simulated position
if (!shouldYield()) {
myState.x += 0.02 * cos(radians(myState.heading));
myState.y += 0.02 * sin(radians(myState.heading));
} else {
// Simple stop – real robots could turn instead
}
// Simulated battery drain
if (myState.battery > 0) myState.battery--;
// Broadcast current state
esp_now_send(broadcastAddr, (uint8_t*)&myState, sizeof(VehicleState));
lastUpdate = millis();
}
// Optional: implement leader‑relay logic here
}
Testing & Debugging Tips
- Serial Monitor – Print received
VehicleStateIDs every second to verify peer discovery. - LED Indicator – Connect an LED to GPIO 2; toggle it on every successful broadcast to spot packet loss visually.
- Range Test – Place two vehicles 5 m apart. If messages fail, reduce Wi‑Fi channel interference (pick channel 1, 6, or 11).
- Power Profiling –
Comments
Post a Comment