Local Grill Monitoring Setup
1. Executive Summary & Architecture Overview
The Local Grill Monitoring Setup provides a 100% self-hosted, cloud-free telemetry and historical graphing pipeline for the FireBoard 2 (FBX2-GJ8RC3JRD) multi-probe grill thermometer, feeding the live web dashboard hosted on ul at https://grill.drheck.dev (http://10.0.0.3:8088/api/ingest).
Why an Internal BLE-to-Wi-Fi Bridge Was Required
- Closed Local Network Surface: Packet capture and full 65,535-port TCP/UDP
nmapscans against the FireBoard 2 (10.0.0.71) confirmed the stock firmware exposes zero local listening ports and initiates outbound TLS 1.2 connections exclusively to FireBoard Cloud (fireboard.io) with strict server certificate verification (CApinning) andSNIchecks. - Locked Factory Silicon (
eFuses): Direct hardware interrogation of the on-board EspressifESP32-WROVER-E (MGN8R8)over its internal 6-pin1.27mmUART bootloader interface proved that FireBoard permanently burned all hardware security eFuses at the factory:- Flash Encryption (
FLASH_CRYPT_CNT = 127): All 7 bits ofFLASH_CRYPT_CNT(0b1111111) are burned (Write-Protected), permanently locking flash encryption on and preventing plaintext re-flashing. - Download Mode Crypto Disabled (
DISABLE_DL_ENCRYPT = True,DISABLE_DL_DECRYPT = True): The ROM bootloader is physically barred by silicon eFuse from encrypting new plaintext images or decrypting existing flash contents over UART (esptool). - Secure Boot v1 (
ABS_DONE_0 = True): Only firmware images signed with FireBoard's private ECDSA key (BLK2, read/write protected) are executed by the ROM bootloader. - Silent UART Console (
MTDOStrapped Low +ESP_LOG_NONE): Both the ROM bootloader (ets_printf) and application firmware (NVSencrypted at0x9000) emit0bytes onUART0during normal boot.
- Flash Encryption (
- Unauthenticated Local BLE GATT Stream: Even when Wi-Fi is completely unconfigured (e.g., after an on-device Factory Reset) or blocked at the router, the FireBoard 2 continuously advertises a Bluetooth Low Energy (BLE) GATT service that streams real-time JSON temperatures for all 6 probe channels (
c2f780ec-45e1-452b-a879-327e3140d1f1) as soon as a single byte (0x01) is written to its control characteristic (c2f780ec-45e1-452b-a879-327e3140d1e8).
Solution Architecture
A thumb-sized Seeed Studio XIAO ESP32-C3 (with an external U.FL flexible 2.4 GHz antenna and an IRLB8721PBF N-Channel logic-level MOSFET automatic power switch) is mounted inside the FireBoard 2 enclosure:
- Automatic Power Sync (
0.000 mAStandby Drain): The MOSFET Gate is driven by the FireBoard's[Pin 35: TXD0]debug pad (3.3Vwhen the FireBoard is ON,0.0Vwhen the FireBoard is OFF). Turning the FireBoard 2 on or off automatically powers the internalXIAO ESP32-C3on or off with zero parasitic battery drain. - BLE-to-Wi-Fi Bridge (
~/git/grill-sync): Within 3 seconds of boot, theXIAO ESP32-C3connects to the FireBoard 2 over BLE GATT (2 cmaway inside the shell), sends the0x01start command, reads the 1S Li-Ion battery voltage viaA0(GPIO2), andPOSTs live probe temperatures every 5 seconds over Wi-Fi tohttp://10.0.0.3:8088/api/ingest(https://grill.drheck.dev).
2. FireBoard 2 Hardware Forensics & PCB Pinout Reference
Internal ESP32-WROVER-E Photograph & Pin Labels
Recovered Device & Silicon Identity (esptool / espefuse)
| Property | Value | Notes |
|---|---|---|
| Device Hostname / Serial | FBX2-GJ8RC3JRD |
FireBoard 2 Extreme / Standard 6-Channel Controller |
| MCU Module | ESP32-WROVER-E (MGN8R8) |
8 MB Quad-SPI Flash (a1:4017) + 8 MB PSRAM |
| Silicon Die Revision | ESP32-D0WD-V3 (revision v3.1) |
Dual-Core Xtensa LX6 @ 240 MHz, 40 MHz crystal |
Wi-Fi STA MAC (Base) |
94:54:c5:f7:1c:f8 |
Reserved as 10.0.0.71 in ~/git/router_config/dhcpd.conf on danknasty |
Wi-Fi SoftAP MAC (Base + 1) |
94:54:c5:f7:1c:f9 |
Used only during initial mobile app Wi-Fi setup |
Bluetooth LE MAC (Base + 2) |
94:54:c5:f7:1c:fa |
Always active whenever the FireBoard 2 is powered on |
| Battery Pack | 1S Li-Ion Pouch Cell (3.7V nominal, 4.20V full) |
Charged via USB-C PD/5V charging circuit on main board |
Unpopulated 6-Pin 1.27mm (0.050") Debug Header Pinout
Located immediately adjacent to the ESP32-WROVER-E module on the main PCB. All pads are verified by continuity against the castellated ESP32-WROVER-E pins:
TOP ROW (Closer to ESP32 bottom edge)
+----------------+----------------+----------------+
| [Pin 35] | [Pin 25] | [GND] |
| UART0 TXD0 | GPIO0 (BOOT) | Ground |
| (Right #23) | (Right #14) | (Shield/1/15) |
+----------------+----------------+----------------+
| [Pin 34] | [Pin 3] | [Pin 24] |
| UART0 RXD0 | EN / RST | GPIO2 (Strap) |
| (Right #22) | (Left #3) | (Right #13) |
+----------------+----------------+----------------+
BOTTOM ROW (Closer to PCB edge)
| Header Pad | ESP32 Module Pin | Function | Voltage (FireBoard ON) | Voltage (FireBoard OFF) | Notes |
|---|---|---|---|---|---|
[35 TXD0] |
Pin 35 (Right #23) |
GPIO1 / U0TXD |
3.30 V (Idle High) |
0.00 V |
Ideal 3.3V logic trigger for the IRLB8721PBF MOSFET Gate |
[34 RXD0] |
Pin 34 (Right #22) |
GPIO3 / U0RXD |
3.30 V |
0.00 V |
UART0 receive input |
[25 IO0] |
Pin 25 (Right #14) |
GPIO0 (Boot Strap) |
3.30 V |
0.00 V |
Hold at 0V (GND) while pulsing [3 RST] to enter UART Download Mode |
[3 RST] |
Pin 3 (Left #3) |
CHIP_PU (EN / RST) |
3.30 V |
0.00 V |
Active-low hardware reset (0V resets ESP32) |
[24 IO2] |
Pin 24 (Right #13) |
GPIO2 (Boot Strap) |
0.00 V |
0.00 V |
Must be low/floating to enter serial bootloader |
[GND] |
Pins 1, 15, 38, 39 | Common Ground | 0.00 V |
0.00 V |
Connected to ESP32 RF shield can and battery negative (-) |
Additional Module Reference Pad: Left #2 (Pin 2: 3V3) carries the regulated 3.30 V rail when the FireBoard is powered on and drops to 0.00 V when powered off.
Hardware Security eFuses (espefuse summary)
FLASH_CRYPT_CNT (BLOCK0) = 127 R/- (0b1111111) [Flash encryption permanently enabled; odd bit count = 7; Write-Protected]
FLASH_CRYPT_CONFIG (BLOCK0) = 15 R/W (0xf) [Full AES flash encryption tweak configuration]
ABS_DONE_0 (BLOCK0) = True R/W [Secure Boot V1 permanently enforced on boot]
DISABLE_DL_ENCRYPT (BLOCK0) = True R/W [UART bootloader flash encryption disabled]
DISABLE_DL_DECRYPT (BLOCK0) = True R/W [UART bootloader flash decryption disabled]
DISABLE_DL_CACHE (BLOCK0) = True R/W [UART bootloader MMU flash cache disabled]
JTAG_DISABLE (BLOCK0) = True R/W [Hardware JTAG debugging permanently disabled]
CONSOLE_DEBUG_DISABLE (BLOCK0) = True R/W [ROM BASIC fallback console disabled]
BLOCK1 (Flash Key) = ?? ?? ... -/- [256-bit AES Flash Encryption Key: Read & Write Protected]
BLOCK2 (Secure Boot Key) = ?? ?? ... -/- [256-bit Secure Boot Key: Read & Write Protected]
3. FireBoard 2 BLE GATT Protocol Reference
The FireBoard 2 exposes an unauthenticated Bluetooth Low Energy GATT service that remains active regardless of Wi-Fi state:
| GATT Attribute | UUID / Value | Properties | Description |
|---|---|---|---|
FIREBOARD_DATA_CHAR_UUID |
c2f780ec-45e1-452b-a879-327e3140d1f1 |
Notify |
Streams UTF-8 JSON temperature packets every 3–5s per active probe channel |
FIREBOARD_CONTROL_CHAR_UUID |
c2f780ec-45e1-452b-a879-327e3140d1e8 |
Write |
Writing single byte 0x01 starts the notification stream on d1f1 |
FIREBOARD_START_STREAM_BYTE |
0x01 (b'\x01') |
1 Byte |
Must be written after enabling notifications (CCCD) on d1f1 |
BLE Handshake & Notification Payload Format
- Scan & Connect: Connect to BLE MAC
94:54:c5:f7:1c:fa(or any device matching prefix94:54:c5:f7:1c:/ nameFBX). No pairing PIN or bond is required. - Subscribe: Enable GATT notifications on
c2f780ec-45e1-452b-a879-327e3140d1f1. - Start Stream: Write
0x01(1 byte, with response) toc2f780ec-45e1-452b-a879-327e3140d1e8. - Parse JSON Notifications: The FireBoard immediately begins emitting UTF-8 JSON notifications for each connected thermistor probe (
channel1through6):{ "channel": 1, "temp": 225.4, "degreetype": 2, "date": "2026-09-25T13:30:00Z" }channel: Probe port number (1..6).temp: Floating-point temperature (nullor<= 0when unplugged).degreetype:2= Fahrenheit (°F),1= Celsius (°C).
4. Internal Hardware Mod: Seeed Studio XIAO ESP32-C3 + IRLB8721PBF Power Switch
Bill of Materials (BOM)
- Microcontroller: Seeed Studio XIAO ESP32-C3 (
21 mm x 17.5 mm, built-in Li-Ion battery pads on underside + externalU.FLantenna connector). - External Antenna: Included Seeed
U.FL(IPEX) 2.4 GHz flexible dipole sticker antenna (adhered to the inside of the plastic FireBoard shell away from the metal battery pouch for strong Wi-Fi range outdoors). - Power Switch MOSFET:
IRLB8721PBFTO-220 Logic-Level N-Channel MOSFET (Vgs(th) = 1.35V–2.35V,Rds(on) ≈ 0.013 ΩatVgs = 3.3V). Looking at the front label of theIRLB8721PBFwith the metal tab facing away from you, the pins from left to right are:- Pin 1 (Left):
Gate (G) - Pin 2 (Middle):
Drain (D) - Pin 3 (Right):
Source (S)
- Pin 1 (Left):
- Resistors (
1/4W 1%Metal Film):1x 100kΩ: Gate pull-down resistor between MOSFETPin 1 (Gate)andPin 3 (Source / GND). Ensures the MOSFET snaps 100% shut (0.000 mA) the instant the FireBoard turns off.1x 1kΩ–10kΩ(optional): Series current-limiting resistor between FireBoard[35 TXD0]and MOSFETPin 1 (Gate).2x 100kΩ(optional): 50/50 voltage divider acrossBAT+->XIAO A0 (GPIO2)->XIAO GNDso the dashboard displays exact battery percentage (0–100%) and enters deep sleep below3.25V.
Why Power Goes to the Underside BAT+ / BAT- Pads (Not the 5V Pin)
- On the
Seeed Studio XIAO ESP32-C3, the top5V(VBUS) pin passes through a protection Schottky diode (~0.34Vvoltage drop) before feeding theXC6206P332MR3.3VLDO (~0.25Vdropout), requiring at least~3.85Von5Vbefore3.3Vsags. - By contrast, the underside
BAT+pad bypasses theVBUSSchottky diode and feeds the3.3VLDO directly (3.3V + 0.25V = 3.55Vfull regulation down to3.25Vempty), allowing the XIAO to run across the entire usable3.5V–4.2Vdischarge curve of the FireBoard's 1S Li-Ion battery—and automatically recharge whenever USB-C is plugged into the FireBoard!
Complete Wiring Schematic
FIREBOARD 2 MAIN PCB / BATTERY IRLB8721PBF MOSFET SEEED XIAO ESP32-C3
============================== ================== ===================
1S Li-Ion Battery (+) [3.7V-4.2V] ---------------------------------------------> Underside [BAT+] Pad
|
[100kΩ] (Optional R1)
|
+-------------------------> Pin [A0 / GPIO2]
|
[100kΩ] (Optional R2)
|
v
6-Pin Header [35 TXD0] (3.3V ON) ---[10kΩ]---> Pin 1 [Gate (G)]
|
[100kΩ] (Pull-Down)
|
1S Li-Ion Battery (-) / [GND] ---------------> Pin 3 [Source (S)]
Pin 2 [Drain (D)] --------------> Underside [BAT-] Pad
5. Firmware Repository (~/git/grill-sync) & Magic Constants Reference
The firmware and verification tools live in /usr/local/google/home/luket/git/grill-sync.
All hardware, BLE protocol, ADC resistor, and network "magic" constants are isolated in a single, self-documenting header file: include/config.h (with Wi-Fi credentials in gitignored include/secrets.h).
Repository Layout
platformio.ini— PlatformIO configuration targetingseeed_xiao_esp32c3(huge_app.csv3.1 MB partition table,NimBLE-Arduino,ArduinoJson).include/config.h— Central configuration file containing all network URLs, MAC addresses, BLE GATT UUIDs, start byte (0x01), resistor divider values, and voltage thresholds.include/secrets.example.h/include/secrets.h— Local Wi-Fi credentials (WIFI_SSID,WIFI_PASSWORD) and optional local overrides.src/main.cpp— Non-blocking BLE GATT client, JSON parser, battery monitor, and HTTP POST transmitter.tools/verify_ble.py— Standalone Python (bleak) BLE client for Linux hosts to test the FireBoard 2 BLE stream and dashboard ingestion before soldering the microcontroller.
Complete Reference of Constants in include/config.h
| Constant | Default Value | Purpose & When to Change |
|---|---|---|
INGEST_URL |
"http://10.0.0.3:8088/api/ingest" |
Target HTTP POST URL on host ul (10.0.0.3 port 8088). Change if ul's LAN IP (10.0.0.3) or container port (8088) ever changes, or set to "https://grill.drheck.dev/api/ingest". |
HTTP_POST_INTERVAL_MS |
5000UL (5s) |
How frequently the XIAO posts temperature snapshots to ul. |
WIFI_CONNECT_TIMEOUT_MS |
10000UL (10s) |
Maximum time spent waiting for Wi-Fi association per attempt. |
FIREBOARD_DEVICE_ID |
"FBX2-GJ8RC3JRD" |
Device ID reported in JSON payloads to ul. |
FIREBOARD_WIFI_MAC |
"94:54:c5:f7:1c:f8" |
Base / Wi-Fi MAC of the FireBoard 2 (assigned static IP 10.0.0.71 in ~/git/router_config/dhcpd.conf on danknasty). |
FIREBOARD_BLE_MAC |
"94:54:c5:f7:1c:fa" |
Bluetooth LE MAC of the FireBoard 2 (Base MAC + 2). Change if pairing with a replacement FireBoard unit. |
FIREBOARD_MAC_PREFIX |
"94:54:c5:f7:1c:" |
OUI/prefix matched during BLE scanning. |
FIREBOARD_LOCK_TO_MAC |
0 |
Set to 1 to only connect to FIREBOARD_BLE_MAC, or 0 to also connect to any nearby device advertising "FBX" / "FireBoard". |
FIREBOARD_DATA_CHAR_UUID |
"c2f780ec-45e1-452b-a879-327e3140d1f1" |
FireBoard 2 BLE GATT Notify characteristic that outputs JSON temperature readings. |
FIREBOARD_CONTROL_CHAR_UUID |
"c2f780ec-45e1-452b-a879-327e3140d1e8" |
FireBoard 2 BLE GATT Write characteristic that starts the stream when 0x01 is written. |
FIREBOARD_START_STREAM_BYTE |
0x01 |
Magic wake byte written to FIREBOARD_CONTROL_CHAR_UUID upon BLE connect. |
FIREBOARD_MAX_CHANNELS |
6 |
Maximum probe channel index (Channels 1..6). |
FIREBOARD_PROBE_STALE_MS |
30000UL (30s) |
Time without a BLE notification before a channel is considered unplugged. |
PIN_BATTERY_ADC |
A0 (GPIO2) |
XIAO pin reading the midpoint of the battery voltage divider. |
VOLTAGE_DIVIDER_R1_OHMS |
100000.0f (100kΩ) |
Upper resistor from BAT+ to A0. Update if using e.g. 220kΩ. |
VOLTAGE_DIVIDER_R2_OHMS |
100000.0f (100kΩ) |
Lower resistor from A0 to GND. |
ADC_CALIBRATION_FACTOR |
1.000f |
Multiplier to fine-tune the ADC reading against a multimeter measurement. |
BATTERY_FULL_VOLTS |
4.15f |
Voltage mapped to 100% battery on the dashboard. |
BATTERY_EMPTY_VOLTS |
3.30f |
Voltage mapped to 0% battery on the dashboard. |
BATTERY_LOW_CUTOFF_VOLTS |
3.25f |
Software safety threshold (3.25V) that puts the XIAO into deep sleep if the MOSFET switch is not present. |
6. Dashboard Infrastructure Reference on Host ul (https://grill.drheck.dev)
| Component | Path / Address | Details |
|---|---|---|
| Public URL (SSO Protected) | https://grill.drheck.dev (https://fireboard.drheck.dev) |
Protected by Authentik ForwardAuth (Grill Monitor (FireBoard 2) application tile in user portal) |
| Unauthenticated Ingest API | http://10.0.0.3:8088/api/ingest / https://grill.drheck.dev/api/ingest |
Accepts POST JSON ({"device_id": "FBX2-GJ8RC3JRD", "battery": 95, "rssi": -42, "probes": {"1": 225.4, "2": 165.2}}) |
| Docker Compose & App Source | /srv/grill/docker-compose.yml, /srv/grill/app/main.py |
FastAPI + SQLite (WAL mode) + Chart.js live/historical multi-channel dashboard |
| Persistent SQLite Database | /srv/grill/data/grill.db |
Stores historical temperature series (1h, 3h, 6h, 12h, 24h, 48h views) |
| Dedicated Docker ULA IPv6 | fd77:7777:5010:0:1337:f1ac:d00d:fb02 (Port 8000) |
Bound on eth77 (10.0.0.3:8088 mapped on IPv4 LAN for the ESP32-C3) |
| Nginx Site Config | /etc/nginx/sites-available/grill |
Proxies / with Authentik ForwardAuth and /api/ingest with auth_request off |
| Backup & Restore Scripts | ~/git/backups/ul/scripts/grill_backup, grill_restore |
shellcheck-verified backups with par2 create -r10 parity archives in /data/backups/services/grill/ |
