Skip to main content

Local Grill Monitoring Setup

System Overview: The Local Grill Monitoring Setup provides a 100% self-hosted, cloud-free telemetry and historical graphing pipeline for the FireBoard 2 (FBX2-GJ8RC3JRD) 6-probe grill thermometer. An internal Seeed Studio XIAO ESP32-C3 (switched on/off automatically by an IRLB8721PBF N-Channel MOSFET driven from the FireBoard's [Pin 35: TXD0] pad) reads all probe channels over Bluetooth Low Energy GATT and posts live updates over Wi-Fi to https://grill.drheck.dev (http://10.0.0.3:8088/api/ingest).

1. Bench Wiring & Flashing Guide (Seeed Studio XIAO ESP32-C3 + IRLB8721PBF)

1.1 Bill of Materials (BOM)

  • Microcontroller: Seeed Studio XIAO ESP32-C3 ($21\text{ mm} \times 17.5\text{ mm}$, built-in Li-Ion battery pads on underside + external U.FL antenna socket).
  • External Antenna: Included Seeed U.FL (IPEX) 2.4 GHz flexible dipole sticker antenna (snap onto the XIAO's ANT socket before powering on; adhere inside the plastic FireBoard shell away from the metal Li-Ion pouch cell).
  • Power Switch MOSFET: IRLB8721PBF TO-220 Logic-Level N-Channel MOSFET ($V_{\text{GS(th)}} = 1.35\text{ V–}2.35\text{ V}$, $R_{\text{DS(on)}} \approx 0.013\text{ }\Omega$ at $V_{\text{GS}} = 3.3\text{ V}$).
  • Resistors (1/4W 1% Metal Film):
    • 1x 100kΩ (Required): Gate pull-down resistor between MOSFET Pin 1 (Gate) and Pin 3 (Source / GND) so the MOSFET snaps 100% shut ($0.000\text{ mA}$ standby drain) the instant the FireBoard turns off.
    • 2x 100kΩ (Optional — for Battery % Telemetry): 50/50 voltage divider (BAT+ $\longrightarrow$ XIAO A0 $\longrightarrow$ XIAO GND).

1.2 Step-by-Step Pin Connections

MOSFET Pin Orientation (IRLB8721PBF): Hold the MOSFET with the black printed label facing you and the metal heatsink tab facing away (legs pointing down). From left to right, the 3 legs are:
• Pin 1 (Left Leg): Gate (G) — Trigger input ($3.3\text{ V}$ ON / $0.0\text{ V}$ OFF)
• Pin 2 (Middle Leg): Drain (D) — Switched ground going to XIAO BAT-
• Pin 3 (Right Leg): Source (S) — True ground going to FireBoard Battery - (GND)

Required Connections (4 Wires + 1x 100kΩ Resistor)

  1. FireBoard Battery + (Red) $\longrightarrow$ XIAO underside BAT+ pad.
  2. FireBoard Battery - (GND) $\longrightarrow$ MOSFET Pin 3 (Right Leg, Source).
  3. MOSFET Pin 2 (Middle Leg, Drain) $\longrightarrow$ XIAO underside BAT- pad.
  4. FireBoard 6-pin header top-left pad ([Pin 35: TXD0]) $\longrightarrow$ MOSFET Pin 1 (Left Leg, Gate), AND solder 1x 100kΩ resistor between MOSFET Pin 1 (Gate) and MOSFET Pin 3 (Source / GND).

Optional Battery % Sense Divider (2x 100kΩ Resistors on Pin A0)

  • Solder 1x 100kΩ resistor (R1) from FireBoard Battery + (BAT+) to XIAO Pin A0 (top-left pin next to the USB-C port, GPIO2).
  • Solder 1x 100kΩ resistor (R2) from XIAO Pin A0 to XIAO GND (or BAT-).
  • (If omitted, the firmware automatically detects $<1.5\text{ V}$ on A0, disables the software low-battery sleep cutoff, and reports 100% battery to the dashboard).

1.3 Complete Wiring Schematic

  FIREBOARD 2 MAIN PCB / BATTERY                   IRLB8721PBF MOSFET              SEEED XIAO ESP32-C3
  ==============================                   (Label Facing You)              ===================

  1S Li-Ion Battery (+) [3.7V-4.2V] ---------------------------------------------> Underside [BAT+] Pad
                                                       |
                                                    [100kΩ] (Optional R1)
                                                       |
                                                       +-------------------------> Top-Left Pin [A0 / GPIO2]
                                                       |
                                                    [100kΩ] (Optional R2)
                                                       |
                                                       v
  6-Pin Header [35 TXD0] (3.3V ON) ------------> Pin 1 [Gate (G) - Left]
                                                       |
                                                    [100kΩ] (Pull-Down to GND)
                                                       |
  1S Li-Ion Battery (-) / [GND] ---------------> Pin 3 [Source (S) - Right]
                                                       
                                                 Pin 2 [Drain (D) - Middle] -----> Underside [BAT-] Pad

Why Power Connects to the Underside BAT+ / BAT- Pads (Not the Top 5V Pin): On the Seeed Studio XIAO ESP32-C3, the top 5V (VBUS) pin passes through a protection Schottky diode ($\Delta V_{\text{diode}} \approx 0.34\text{ V}$) before feeding the XC6206P332MR $3.3\text{ V}$ LDO ($\Delta V_{\text{drop}} \approx 0.25\text{ V}$), requiring at least $3.85\text{ V}$ on 5V before the $3.3\text{ V}$ rail sags. Soldering to the underside BAT+ pad bypasses the Schottky diode and feeds the LDO directly ($V_{\text{min}} = 3.30\text{ V} + 0.25\text{ V} = 3.55\text{ V}$), running cleanly across the entire $3.5\text{ V–}4.2\text{ V}$ discharge curve of the 1S Li-Ion cell.

$$V_{\text{ADC}} = V_{\text{BAT}} \times \frac{R_2}{R_1 + R_2} = \frac{V_{\text{BAT}}}{2.0}, \qquad I_{\text{divider}} = \frac{4.2\text{ V}}{200\text{ k}\Omega} = 21\text{ }\mu\text{A}$$

1.4 Building, Flashing & Bench Testing (PlatformIO)

  1. Snap the U.FL antenna onto the XIAO ESP32-C3 before powering on.
  2. Copy include/secrets.example.h to include/secrets.h in ~/git/grill-sync and set your 2.4 GHz Wi-Fi credentials:
    #define WIFI_SSID "Your_2.4GHz_WiFi_SSID"
    #define WIFI_PASSWORD "Your_WiFi_Password"
    
  3. Compile, flash over USB-C, and open the live serial monitor (115200 baud):
    $ pio run -t upload -t monitor
    
  4. Turn on the FireBoard 2 nearby with a probe plugged into Channel 1. Within 5 seconds, the serial console outputs:
    === FireBoard 2 BLE -> Wi-Fi Sync (XIAO ESP32-C3) ===
    [WiFi] Connected, IP: 10.0.0.x
    [BLE] Found FireBoard (94:54:c5:f7:1c:fa, RSSI -42 dBm)
    [BLE] Connected to FireBoard 2
    [BLE] Subscribed and sent 0x01 start command
    [BLE] Ch 1: 72.5 F
    [HTTP] POST http://10.0.0.3:8088/api/ingest -> 200
    

2. FireBoard 2 Hardware Forensics & PCB Pinout Reference

2.1 Internal ESP32-WROVER-E Photograph & Pin Labels

FireBoard 2 ESP32-WROVER-E Pinout Figure 1: FireBoard 2 internal ESP32-WROVER-E (MGN8R8) module and unpopulated 6-pin 1.27mm UART bootloader header with verified pin numbers.

2.2 Unpopulated 6-Pin 1.27mm (0.050") Debug Header Pinout

Located immediately below the ESP32-WROVER-E module on the main PCB. All pads were 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)

   * [Pin 35: TXD0] (top-left pad) outputs 3.30V when FireBoard is ON
     and 0.00V when FireBoard is OFF — wire this pad to MOSFET Pin 1 (Gate).
Header Pad ESP32 Module Pin Function Voltage (FireBoard ON) Voltage (FireBoard OFF) Notes
[35 TXD0] Pin 35 (Right #23) GPIO1 / U0TXD $3.30\text{ V}$ (Idle High) $0.00\text{ V}$ Used as the $3.3\text{ V}$ ON/OFF trigger for MOSFET Pin 1 (Gate)
[34 RXD0] Pin 34 (Right #22) GPIO3 / U0RXD $3.30\text{ V}$ $0.00\text{ V}$ UART0 receive input
[25 IO0] Pin 25 (Right #14) GPIO0 (Boot Strap) $3.30\text{ V}$ $0.00\text{ V}$ Hold at $0\text{ V}$ (GND) while pulsing [3 RST] to enter UART Download Mode
[3 RST] Pin 3 (Left #3) CHIP_PU (EN / RST) $3.30\text{ V}$ $0.00\text{ V}$ Active-low hardware reset ($0\text{ V}$ resets ESP32)
[24 IO2] Pin 24 (Right #13) GPIO2 (Boot Strap) $0.00\text{ V}$ $0.00\text{ V}$ Must be low/floating to enter serial bootloader
[GND] Pins 1, 15, 38, 39 Common Ground $0.00\text{ V}$ $0.00\text{ V}$ Connected to ESP32 RF shield can and battery negative (-)

Additional Module Reference Pad: Left #2 (Pin 2: 3V3) carries the regulated $3.30\text{ V}$ rail when the FireBoard is powered on and drops to $0.00\text{ V}$ when powered off.

2.3 Recovered Device & Silicon Identity (esptool / espefuse)

Property Value Notes
Device Hostname / Serial FBX2-GJ8RC3JRD FireBoard 2 6-Channel Thermometer
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\text{ MHz}$, $40\text{ 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.7\text{ V}$ nominal, $4.20\text{ V}$ full) Charged via USB-C 5V charging circuit on main board

2.4 Why the Stock Firmware Could Not Be Redirected or Replaced

  1. Closed Local Network Surface: Full 65,535-port TCP/UDP nmap scans and packet captures against 10.0.0.71 confirmed the stock firmware listens on zero local ports and initiates outbound TLS 1.2 connections exclusively to FireBoard Cloud (fireboard.io) with strict CA certificate verification and SNI checks.
  2. Locked Factory Silicon (eFuses): Interrogation over the 6-pin UART bootloader header proved all hardware security eFuses are permanently burned:
    • Flash Encryption (FLASH_CRYPT_CNT = 127 / 0b1111111): All 7 bits burned and write-protected; plaintext flashing renders the chip unbootable.
    • UART Crypto Disabled (DISABLE_DL_ENCRYPT = True, DISABLE_DL_DECRYPT = True): The ROM bootloader is physically barred from encrypting new images or decrypting flash over UART.
    • Secure Boot v1 (ABS_DONE_0 = True): Only images signed with FireBoard's private key (BLOCK2, read/write protected) are booted.
    • Silent UART Console (MTDO Strapped Low + ESP_LOG_NONE): Zero application log bytes are emitted on UART0 during normal boot.
Click to expand full ESP32-D0WD-V3 Security eFuse Register Dump (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

Zero Cloud Dependency: The FireBoard 2 BLE GATT server remains active regardless of Wi-Fi state, including after an on-device Factory Reset that wipes Wi-Fi credentials.

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

  1. Scan & Connect: Connect to BLE MAC 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.
  2. Subscribe: Enable GATT notifications on c2f780ec-45e1-452b-a879-327e3140d1f1.
  3. Start Stream: Write 0x01 (1 byte, with response) to c2f780ec-45e1-452b-a879-327e3140d1e8.
  4. 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-28T20: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. Firmware Repository (~/git/grill-sync) & Magic Constants (include/config.h)

All hardware, BLE protocol, ADC resistor, and network constants are isolated in 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.

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 ports 8088 and 8000). Change if ul's LAN IP 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 ($100\text{ k}\Omega$) Upper resistor from BAT+ to A0.
VOLTAGE_DIVIDER_R2_OHMS 100000.0f ($100\text{ k}\Omega$) 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.25\text{ V}$) that puts the XIAO into deep sleep if the MOSFET switch is not present.

5. 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 / http://10.0.0.3:8000/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 /data/docker/grill/docker-compose.yml, /data/docker/grill/app.py Docker container grill running FastAPI + SQLite (WAL mode) + Chart.js live/historical multi-channel dashboard
Persistent SQLite Database /data/docker/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 and 10.0.0.3:8000 mapped on IPv4 LAN for the XIAO ESP32-C3)
DNS Configuration ~/git/router_config/unbound.conf & update_dns.py Local Unbound (10.0.0.100 / fd77:7777:5010::1337:f1ac:d00d:aaaa) on danknasty + public deSEC IPv6 GUA sync
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/