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
nmap scans 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 (CA pinning) and SNI checks.
Locked Factory Silicon (eFuses): Direct hardware interrogation of the on-board Espressif ESP32-WROVER-E (MGN8R8) over its internal 6-pin 1.27mm UART bootloader interface proved that FireBoard permanently burned all hardware security eFuses at the factory:
FLASH_CRYPT_CNT = 127): All 7 bits of FLASH_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 (MTDO Strapped Low + ESP_LOG_NONE): Both the ROM bootloader (ets_printf) and application firmware (NVS encrypted at 0x9000) emit 0 bytes on UART0 during normal boot.
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:
0.000 mA Standby Drain): The MOSFET Gate is driven by the FireBoard's [Pin 35: TXD0] debug pad (3.3V when the FireBoard is ON, 0.0V when the FireBoard is OFF). Turning the FireBoard 2 on or off automatically powers the internal XIAO ESP32-C3 on or off with zero parasitic battery drain.
BLE-to-Wi-Fi Bridge (~/git/grill-sync): Within 3 seconds of boot, the XIAO ESP32-C3 connects to the FireBoard 2 over BLE GATT (2 cm away inside the shell), sends the 0x01 start command, reads the 1S Li-Ion battery voltage via A0 (GPIO2), and POSTs live probe temperatures every 5 seconds over Wi-Fi to http://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)
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)
[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:
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
94:54:c5:f7:1c:fa (or any device matching prefix 94:54:c5:f7:1c: / name FBX). 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) to c2f780ec-45e1-452b-a879-327e3140d1e8.
Parse JSON Notifications: The FireBoard immediately begins emitting UTF-8 JSON notifications for each connected thermistor probe (channel 1 through 6):
{
"channel": 1,
"temp": 225.4,
"degreetype": 2,
"date": "2026-09-25T13:30:00Z"
}
channel: Probe port number (1..6).
temp: Floating-point temperature (null or <= 0 when unplugged).
degreetype: 2 = Fahrenheit (°F), 1 = Celsius (°C).
4. Internal Hardware Mod: Seeed Studio XIAO ESP32-C3 + IRLB8721PBF Power Switch
Bill of Materials (BOM)
21 mm x 17.5 mm, built-in Li-Ion battery pads on underside + external U.FL antenna 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: IRLB8721PBF TO-220 Logic-Level N-Channel MOSFET (Vgs(th) = 1.35V–2.35V, Rds(on) ≈ 0.013 Ω at Vgs = 3.3V).
Looking at the front label of the IRLB8721PBF with the metal tab facing away from you, the pins from left to right are:
Gate (G)
Pin 2 (Middle): Drain (D)
Pin 3 (Right): Source (S)
Resistors (1/4W 1% Metal Film):
1x 100kΩ: Gate pull-down resistor between MOSFET Pin 1 (Gate) and Pin 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 MOSFET Pin 1 (Gate).
2x 100kΩ (optional): 50/50 voltage divider across BAT+ -> XIAO A0 (GPIO2) -> XIAO GND so the dashboard displays exact battery percentage (0–100%) and enters deep sleep below 3.25V.
Why Power Goes to the Underside BAT+ / BAT- Pads (Not the 5V Pin)
Seeed Studio XIAO ESP32-C3, the top 5V (VBUS) pin passes through a protection Schottky diode (~0.34V voltage drop) before feeding the XC6206P332MR 3.3V LDO (~0.25V dropout), requiring at least ~3.85V on 5V before 3.3V sags.
By contrast, the underside BAT+ pad bypasses the VBUS Schottky diode and feeds the 3.3V LDO directly (3.3V + 0.25V = 3.55V full regulation down to 3.25V empty), allowing the XIAO to run across the entire usable 3.5V–4.2V discharge 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 targeting seeed_xiao_esp32c3 (huge_app.csv 3.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
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)
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/
