Live control panel plus the full setup reference — eight relays wired as four H-bridges, so every motor runs forward, reverse, or off.
http://192.168.4.1
directly; it is served by the ESP32 itself and has no such restriction.| Same network | Internet | |
|---|---|---|
| Path | browser → drone | browser → Worker → drone |
| Works from | same WiFi / hotspot | anywhere with internet |
| Round trip | a few ms | ~0.3–1.1 s |
| Needs | insecure content allowed | the device key |
| Drone must | be reachable on the LAN | have internet access |
Internet mode works because the drone sits behind NAT and cannot accept inbound connections. Instead it calls out to a Cloudflare Worker roughly twice a second, pushing telemetry up and collecting whatever command is being held. Nothing is forwarded, nothing is exposed, and both ends speak HTTPS — so unlike the direct path, there is no mixed content to unblock.
401 to everything, which is what keeps
a public URL from being a public throttle. Treat it like a password; if it leaks, run
npx wrangler secret put DRONE_KEY in worker/ and reflash with the new
value.
stop. Independently, if the drone's own internet drops, no command arrives at all
and its local 2 s failsafe cuts the relays. Both halves fail safe on their own, so a dead
phone, a dropped Wi-Fi link, or a Cloudflare outage all end with the motors off.
The drone runs its hotspot and your home network at the same time
(WIFI_AP_STA), so all of these work simultaneously — the hotspot is always there
as a fallback when you are out of router range.
| Route | Address | Requires |
|---|---|---|
| On-board page, hotspot | http://192.168.4.1 | joined to DroneESP32 |
| On-board page, home WiFi | http://drone.local | same network as the drone |
| This hosted panel | address box above | insecure content allowed |
drone.local, not the raw IP. The router hands out the drone's address
by DHCP, so it can change after a reboot and any bookmarked IP goes stale. The firmware
publishes an mDNS record on both interfaces, so the name follows it either way. Resolves
natively on iOS, macOS, Windows 10/11 and Android 12+. On anything older, fall back to the IP
shown on the drone's WiFi Setup page — or give it a DHCP reservation in your router.
Each motor takes two adjacent relays. The motor's + lead goes to the COM of the first relay, the − lead to the COM of the second. On both relays, NC ties to battery − and NO ties to battery +. Energising one relay but not the other puts the two motor leads on opposite rails, and which relay is energised decides which way the current — and the prop — turns.
| Relay A (+ lead) | Relay B (− lead) | Motor leads | Result |
|---|---|---|---|
| off | off | both on BATT− | OFF — braked |
| ON | off | + on BATT+, − on BATT− | FORWARD |
| off | ON | + on BATT−, − on BATT+ | REVERSE |
| ON | ON | both on BATT+ | braked — never commanded |
| Motor | Position | + lead relay | − lead relay | Rotation |
|---|---|---|---|---|
| M1 | Front-left | IN1 · GPIO 18 | IN2 · GPIO 19 | CW |
| M2 | Front-right | IN3 · GPIO 32 | IN4 · GPIO 33 | CCW |
| M3 | Rear-left | IN5 · GPIO 25 | IN6 · GPIO 26 | CCW |
| M4 | Rear-right | IN7 · GPIO 14 | IN8 · GPIO 27 | CW |
#defines at the top of main.cpp.
| Command | M1 front-L | M2 front-R | M3 rear-L | M4 rear-R | Effect |
|---|---|---|---|---|---|
| fly | FWD | FWD | FWD | FWD | Climb |
| land | REV | REV | REV | REV | Powered descent |
| front | REV | REV | FWD | FWD | Nose dips forward |
| back | FWD | FWD | REV | REV | Nose lifts |
| left | FWD | REV | FWD | REV | Rolls left |
| right | REV | FWD | REV | FWD | Rolls right |
| stop | OFF | OFF | OFF | OFF | Hard kill |
Only /stop and the failsafe de-energise the relays. Every other command
reverses polarity rather than cutting power, so land is an active powered descent
that keeps running until you press STOP or the heartbeat stops.
land and the reversed side of each turn push noticeably harder in one direction
than the other.
Flipping a spinning motor straight from forward to reverse is plugging: back-EMF adds
to the supply voltage and the surge can approach twice stall current. That welds relay contacts
and browns out the pack. The firmware routes every flip through OFF first and applies the new
direction 60 ms later, without blocking the loop. Tune with
REVERSE_DEADTIME_MS.
| Endpoint | Effect |
|---|---|
| GET / | Flight dashboard |
| GET /cmd?c=fly | All four forward |
| GET /cmd?c=land | All four reverse |
| GET /cmd?c=front | Front pair reverse, rear forward |
| GET /cmd?c=back | Rear pair reverse, front forward |
| GET /cmd?c=left | Left pair forward, right pair reverse |
| GET /cmd?c=right | Right pair forward, left pair reverse |
| GET /stop | All relays de-energised, clears active command |
| GET /status | {"cmd":"left","m":[1,-1,1,-1]} |
| GET /wifi | WiFi setup page |
| GET /wifi/scan | Async scan; poll until scanning:false |
| GET /wifi/save | ?ssid=&pass= — save to NVS and join |
| GET /wifi/status | Link state, station IP, RSSI |
| GET /wifi/forget | Erase stored credentials |
In /status, each entry of m is 1 forward, −1
reverse, 0 off. Unknown c values return HTTP 400 and leave the motor
state untouched. All responses carry Access-Control-Allow-Origin: * so this page can
read them cross-origin.
192.168.4.1.pinMode()
makes them outputs, and before the radio starts. Motors cannot spin during boot.timeout, checked on every loop pass.RELAY_ACTIVE_LOW is the only place polarity is
decided. Every write goes through setRelay().ledcWrite(), and add an MPU-6050 for
stabilisation. A dual H-bridge driver such as the DRV8833 or TB6612FNG gives you reverse
and proportional speed on the same two pins per motor. The WiFi, dashboard,
provisioning, and failsafe all carry over unchanged.
Put the assembled drone on a kitchen scale, tare it, hold it down and run FLY. The
reading drops by however much thrust you are making. Weigh the craft too: you need thrust above
total weight just to leave the ground, and roughly 2× before it is controllable. If the
reading goes up on FLY, forward and reverse are swapped — flip
DIR_FWD/DIR_REV for that motor, or swap its two relay leads.