A lightweight Matter-over-Wi-Fi bridge that exposes a BedJet (via BedJetWebSchedule) as a Matter Thermostat + Matter Fan.
This project runs on an ESP32 and:
- Connects to your Wi-Fi (STA mode)
- Provides a setup portal (SoftAP) to configure Wi-Fi + BedJetWebSchedule base URL
- Exposes two Matter endpoints: Thermostat and Fan
- This bridge does not talk to the BedJet over BLE directly. Instead, it talks to an existing BedJetWebSchedule instance over HTTP on your LAN.
- BedJetWebSchedule is responsible for the BLE link to the BedJet and exposes an HTTP API.
- This bridge translates Matter commands into those HTTP calls and keeps Matter state in sync by polling BedJetWebSchedule.
- Matter Thermostat endpoint
- Modes: OFF / HEAT / COOL / EMERGENCY_HEAT (mapped to BedJet EXT-HEAT)
- Setpoint control (BedJet range is clamped to 62–110°F)
- Local temperature reflected from BedJet
air=reading when available
- Matter Fan endpoint
- Speed as 0–100%, mapped to BedJet fan steps 0–19
- ON/OFF behavior (0% -> OFF)
- Adaptive polling
- 30s steady-state polling
- 5s polling for 60s after a command (faster convergence for voice assistants)
- Automatic BLE connect attempt
- If BedJetWebSchedule reports
ble_connected=false, the bridge POSTs/api/ble/connectbefore sending commands
- If BedJetWebSchedule reports
- Built-in status page (HTTP :80)
- Shows IP, BedJet base URL, commissioning status, pairing code, QR URL
- Field-safe recovery controls
- Hold BOOT at power-on to force the setup portal
- Hold BOOT for ~5s while running to decommission Matter and reboot
- An ESP32 that supports the Arduino
Matter.hlibrary (via the Arduino-ESP32 core), plus Wi-Fi. - A working BedJetWebSchedule instance reachable on your LAN.
- BedJetWebSchedule is commonly run on another ESP32 that connects to the BedJet over BLE.
- Arduino IDE or PlatformIO
- Espressif Arduino-ESP32 core that includes Matter (
#include <Matter.h>)
- The bridge and BedJetWebSchedule must be on the same routable network (no captive portals)
- HTTP access from the bridge to the BedJetWebSchedule base URL
- Flash the sketch (
BedJetMatterBridge.ino) to your ESP32. - Open Serial Monitor at 115200.
- On first boot (or if config is missing), the device starts a setup AP:
- SSID:
BedJetBridge-<XXXX> - Connect and browse to
http://192.168.4.1/
- SSID:
- Enter:
- Wi-Fi SSID + password
- BedJetWebSchedule base URL (example:
http://192.168.1.253) - Default mode used when the BedJet is OFF and you set temp/fan:
HEATorCOOL
- The device reboots, joins Wi-Fi, and prints its Matter pairing code on Serial.
- (Optional) Visit the bridge status page:
http://<bridge-ip>/for commissioning + pairing details. - Use your Matter controller to pair using the code.
Matter Controller -> ESP32 Bridge -> BedJetWebSchedule -> BedJet (BLE)
^ |
| v
+------------ State poll (/api/state) --------------+
-
Configuration
- Stored in ESP32 NVS using
Preferencesnamespacebjbridge(ssid,pass,base,defmode). - If SSID/base are missing, or BOOT is held at power-on, the bridge starts a SoftAP and serves a minimal HTML config portal.
- Stored in ESP32 NVS using
-
Matter endpoints
- A
MatterThermostatand aMatterFanare created and started, thenMatter.begin()is called. - If not commissioned, pairing code and QR URL are printed on Serial (and exposed via the status page).
- A
-
Matter -> BedJetWebSchedule (commands)
- The bridge detects controller writes by polling Matter getters and comparing them to cached values.
- Writes are turned into a single queued command (
PendingCmd), executed via HTTP POST:- Mode:
POST /api/cmd/button?name=<MODE> - Temp:
POST /api/cmd/button?name=<MODE>&temp=<F> - Fan:
POST /api/cmd/button?name=<MODE>&fan=<STEP>
- Mode:
- Fan percent is mapped to BedJet fan steps 0–19.
- If the BedJet is OFF and you set temp or fan > 0, the bridge uses your configured default mode (
HEATorCOOL) to turn it on.
-
BedJetWebSchedule -> Matter (state sync)
- The bridge polls
GET /api/stateand parsesstatus_summaryfor:mode=...fan=...%target=...Fair=...F
- Parsed values update Matter mode, setpoints, fan speed, and local temperature.
- Polling is adaptive: slower when idle, faster for ~60s after sending a command.
- The bridge polls
-
BLE auto-connect
- If
ble_connectedis false/unknown, the bridge attemptsPOST /api/ble/connectonce before issuing a command.
- If
-
Recovery controls
- Hold BOOT ~5 seconds while running to decommission Matter and reboot.
This bridge expects the following endpoints on the configured base URL:
| Method | Path | Purpose | Required fields |
|---|---|---|---|
| GET | /api/state |
Returns current state as JSON | status_summary (string), optional ble_connected (bool) |
| POST | /api/ble/connect |
Requests BedJetWebSchedule to connect to the BedJet over BLE | JSON body containing ok is helpful but not strictly required |
| POST | /api/cmd/button?name=<...>&fan=<...>&temp=<...> |
Issues a BedJet command | JSON body containing ok is helpful but not strictly required |
Notes:
/api/cmd/buttonis POST-only in BedJetWebSchedule.- The bridge does not use a full JSON parser. It relies on the
status_summarystring format (e.g.mode=heat fan=55% target=89F air=85F ...).
The bridge runs a small HTTP server on port 80:
GET /- Plain-text status (Wi-Fi, IP, base URL, last mode, commissioning status, pairing code, QR URL)POST /button- Queue a mode button (form field:name=HEAT|COOL|TURBO|EXT-HEAT|DRY|OFF...)
This is primarily for bring-up and diagnostics.
This is intended for trusted home networks.
- The setup portal and status endpoint have no authentication.
- Wi-Fi credentials are stored in ESP32 NVS (
Preferences). - Commands to BedJetWebSchedule are sent over plain HTTP.
If you care about segmentation:
- Put BedJet devices on an IoT VLAN and only allow the minimum required east-west traffic.
- Avoid exposing the bridge or BedJetWebSchedule ports to the internet.
-
Bridge never joins Wi-Fi
- Hold BOOT while powering on to force the setup portal.
- Verify SSID/password; watch Serial output for
STA_DISCONNECTED reason=....
-
Bridge joins Wi-Fi but never controls BedJet
- Verify the BedJetWebSchedule base URL (no trailing slash required; the bridge will normalize it).
- Confirm you can hit
http://<bedjetwebschedule-ip>/api/statefrom another device.
-
BLE not connected
- The bridge will try
POST /api/ble/connectautomatically before commands. - If it still fails, BedJetWebSchedule may be out of BLE range, not running, or already paired with another controller.
- The bridge will try
-
Matter pairing issues
- Pairing code prints repeatedly when not commissioned.
- If previously paired and you want to start over: hold BOOT for ~5s to decommission, then reboot and re-pair.
This repo is the original Arduino sketch split into modules for easier debugging and version control:
BedJetMatterBridge.ino— thin wrapper callingApp::setup()/App::loop()App.{h,cpp}— main orchestration (setup/loop)Globals.{h,cpp}— shared state and constantsUtils.{h,cpp}— small helpers (HTML escape, temperature conversion, clamping, fan step conversion, URL normalization)HttpUtil.{h,cpp}— HTTP GET/POST helpersConfigPortal.{h,cpp}— Preferences load/save + AP config portal + Wi-Fi STA connectBedJetClient.{h,cpp}— BedJetWebSchedule API client + parser + command queueMatterIntegration.{h,cpp}— Matter endpoints and write-back logicStatusWeb.{h,cpp}— minimal HTTP endpoint on port 80 (root + /button)
See LICENSE.