Open-source ESP32 firmware that turns a handful of relays into a WiFi and Bluetooth garage door controller — provisioned in a browser, driven over HTTP or BLE, and updated over the air whenever you decide it's time.
A single ESP32 drives up to four independent relay-triggered doors, each with an optional open/closed sensor, addressable over a bearer-token HTTP API or a passkey-paired BLE service — no cloud dependency, no hardcoded secrets in source.
A REST-style HTTP API for automation and a low-energy BLE GATT service for direct phone control — both gated behind credentials generated during provisioning, never shipped in firmware.
First boot opens a temporary SoftAP with a captive setup page. Enter WiFi, a BLE passkey, and door count — the device validates the WiFi connection before saving anything.
Firmware ships as Gitea Releases, hosted and maintained by us. Updates are never pushed automatically — each device checks and installs only when you trigger it, so a release can't break a system that's working fine for you. Downloads are TLS-verified against a pinned root cert and checksum-verified before the new firmware is ever activated, so a MITM'd connection or a corrupted download gets rejected rather than installed.
Five wrong bearer tokens in a row from the same IP trigger a lockout for that IP alone, with a global fallback if an attack spreads across more IPs than can be tracked individually — and blocked attempts are logged in a rate-limited way (first hit, then every 20th, then a summary) so a flood can't flood the log buffer too. Tokens are compared in constant time so response speed can't leak them a byte at a time. Repeated failed BLE pairing pauses advertising, and each door enforces its own trigger cooldown.
Every Serial line is mirrored to a 40-entry ring buffer, readable via GET /logs or pushed to your own webhook every ~15s — no USB cable required after install.
A background check restarts BLE advertising if it silently stops, plus a periodic bounce every 5 minutes — aimed squarely at "only a reboot fixed it" failure modes.
Wire an optional sensor to any door, independently of the others — add one where it's useful, skip it where it isn't. Status is available over HTTP (GET /doors, GET /door/<n>/status), BLE (H/O/T fields on the status characteristic), and right in the Android app on doors that have one configured. Full details in the API reference →
A real build: an ESP32 DevKit V1, a 5V relay wired into an opener's dry-contact wall-button terminals, and a Hall-effect sensor for live door status — plus the Android client in action.






The build shown above — a relay wired straight into an opener's dry-contact wall-button terminals, plus an optional Hall-effect sensor for live open/closed status.
| From | To |
|---|---|
| Relay GND | ESP32 GND |
| Relay VCC | ESP32 VIN |
| Relay Signal (IN) | ESP32 GPIO 23 (Door 1's default relay pin) |
| Relay COM | Opener terminal 2 |
| Relay NC | Opener terminal 1 |
| Sensor VCC | ESP32 3.3V (optional) |
| Sensor GND | ESP32 GND (optional) |
| Sensor D0 | ESP32 GPIO 27 (optional — Door 1's default sensor pin) |
Terminal numbering and labeling vary by opener brand and model — confirm against your own opener's wiring diagram (usually printed right next to the terminal block) before connecting anything. The sensor is a linear Hall-effect sensor mounted near the door track so a magnet on the door trips it at the closed position — entirely optional and configured per door during provisioning.
Happens once per device — or again after a factory reset. Nothing is stored until the WiFi connection is verified.
Connect to OpenSesame-Setup-XXXXXX. The password is always OS- followed by the same six characters shown at the end of the network name — no calculator or pre-printed label needed.
That value is public the moment you see the SSID in a WiFi picker, so this password only protects the window before first provisioning — after that the SoftAP disables itself until a factory reset, and the real API token / BLE passkey are freshly random or user-chosen.
Visit http://192.168.4.1/ and fill in the form.
The device connects to your WiFi before writing anything to flash. On success it reboots into normal operating mode.
This is shown exactly one time. Copy it into a notepad before the device reboots — it will not be shown again.
Every HTTP call below needs Authorization: Bearer <token>. Doors are 1-based in the URL path. This is the condensed version — the full API reference covers every field, error code, and the provisioning portal's own routes.
| Endpoint | Does |
|---|---|
| GET /doors | Lists configured doors: number, relay pin, lock state, and sensor status if one's installed. |
| POST /door/<n>/trigger | Pulses door n's relay, subject to cooldown and lock state. |
| GET /door/<n>/status | Lock state, cooldown info, and open/closed sensor reading (if installed) for door n. |
| POST /door/<n>/lock · /unlock | Toggles the software lock flag for a door. |
| POST /door/<n>/set_relay_pin | Reassigns door n's relay GPIO (0–33, excluding 6–11); reboots to apply. |
| POST /door/<n>/set_sensor_pin | Adds or reassigns door n's optional open/closed sensor GPIO; reboots to apply. |
| POST /door/<n>/disable_sensor | Removes door n's sensor config. No reboot needed. |
| POST /rotate_token | Mints a new random API token and returns it once, immediately. Rate-limited to one rotation per 60s. |
| POST /set_wifi_credentials | Switches to a new WiFi network without a factory reset; automatically falls back to the previous network if the new one fails. |
| POST /factory_reset | Wipes NVS and BLE bonds; returns to provisioning mode. |
| GET /version | Returns the running FIRMWARE_VERSION and uptime since boot. |
| POST /ota_update_meta | Updates only if the latest Gitea release differs from the running version. |
| POST /ota_update | Force-installs the latest Gitea release regardless of version. |
| GET /logs | Last ~40 lines of Serial output, as JSON. |
| POST /set_log_webhook_url | Sets (or clears) a remote endpoint that log lines are pushed to. |
One service handles all configured doors, encrypted and passkey-paired.
Command char → write [cmd, doorIndex]
cmd: 0x01 trigger · 0x02 lock · 0x03 unlock
doorIndex is 0-based (door 1 in the API = index 0)
Status char → N:<doorCount>,C:<cooldownMs>,U:<uptimeMs>;D0:L<0/1>,S<msSinceTrigger>,P<relayPin>,H<0/1>[,O<0/1>,T<msSinceChange>];D1:...
U = ms since boot (wraps ~49.7 days) · H = sensor installed · O = door closed (only present if H=1) · T = ms since O last changed
Sensor and uptime fields are new — a companion app that parses by key (splitting on commas/colons) rather than assuming fixed field positions keeps working unchanged, since U, H, and O/T (when present) are added fields, not reordered ones.
Connect an ESP32 over USB and install a build straight from the opensesame Gitea releases — no PlatformIO install required. Uses ESP Web Tools and the browser's Web Serial API. Targets ESP32 DevKit V1 / WROOM boards specifically — other ESP32 variants (S2, S3, C3, different flash sizes) aren't supported by this build.
Firmware-only write to an already-provisioned chip — matches the OTA path.