ESP32 DEVKIT V1 / WROOM · 1–4 DOORS

Open
sesame.

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.

DOOR 1
GPIO 23
DOOR 2
GPIO 22
DOOR 3
GPIO 21
DOOR 4
— idle
MAX_DOORS: 4 LINK: WIFI + BLE ONLINE

Dry-contact openers only

This firmware triggers doors through a relay wired the same way as a wall button — a simple momentary contact closure. It has no way to control openers that require a proprietary rolling-code or encrypted RF handshake, such as Chamberlain/LiftMaster's Security+ and Security+ 2.0 models. Check your opener for a dry-contact terminal block (often labeled for a wired wall button) before building — most standalone/older openers have one, but many newer "smart" openers don't expose it.

WHAT IT DOES

One board, up to four doors

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.

01 CONTROL

Dual control paths

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.

02 SETUP

Browser-based provisioning

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.

03 UPDATES

OTA, on your schedule

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.

04 SAFETY

Lockouts & cooldowns

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.

05 VISIBILITY

Remote diagnostics

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.

06 RESILIENCE

Self-healing BLE

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.

07 SENSING

Know if it's actually closed

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 →

FIRST BOOT

Provisioning walkthrough

Happens once per device — or again after a factory reset. Nothing is stored until the WiFi connection is verified.

Join the setup network

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.

Open the setup page

Visit http://192.168.4.1/ and fill in the form.

WiFi SSID / password — required BLE passkey (6 digits) — required Door count 1–4 — default 1 Relay pin per door — default 23/22/21/19 Optional open/closed sensor per door + pin BLE device name — default OpenSesame-XXXX

Save & verify

The device connects to your WiFi before writing anything to flash. On success it reboots into normal operating mode.

Copy the confirmation page — once

This is shown exactly one time. Copy it into a notepad before the device reboots — it will not be shown again.

WiFi MAC / IP API token BLE name / MAC / passkey Door count + relay/sensor pins BLE service/characteristic UUIDs
REFERENCE

HTTP & BLE control

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.

EndpointDoes
GET /doorsLists configured doors: number, relay pin, lock state, and sensor status if one's installed.
POST /door/<n>/triggerPulses door n's relay, subject to cooldown and lock state.
GET /door/<n>/statusLock state, cooldown info, and open/closed sensor reading (if installed) for door n.
POST /door/<n>/lock · /unlockToggles the software lock flag for a door.
POST /door/<n>/set_relay_pinReassigns door n's relay GPIO (0–33, excluding 6–11); reboots to apply.
POST /door/<n>/set_sensor_pinAdds or reassigns door n's optional open/closed sensor GPIO; reboots to apply.
POST /door/<n>/disable_sensorRemoves door n's sensor config. No reboot needed.
POST /rotate_tokenMints a new random API token and returns it once, immediately. Rate-limited to one rotation per 60s.
POST /set_wifi_credentialsSwitches to a new WiFi network without a factory reset; automatically falls back to the previous network if the new one fails.
POST /factory_resetWipes NVS and BLE bonds; returns to provisioning mode.
GET /versionReturns the running FIRMWARE_VERSION and uptime since boot.
POST /ota_update_metaUpdates only if the latest Gitea release differs from the running version.
POST /ota_updateForce-installs the latest Gitea release regardless of version.
GET /logsLast ~40 lines of Serial output, as JSON.
POST /set_log_webhook_urlSets (or clears) a remote endpoint that log lines are pushed to.

BLE quick reference

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.

USB · WEB SERIAL

Flash firmware from this page

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.

Checking browser support…
⚠ Couldn't reach the release feed from the browser. Try again in a moment, or check the Releases page directly.

Firmware-only write to an already-provisioned chip — matches the OTA path.

Choose Update or First flash, then look up the latest release.