Files
ESP32-ThermoPro-Bridge/README.md
T
Keith Solomon 801e16f7ff 📄 docs: Add readme
2026-06-06 10:31:18 -05:00

3.8 KiB

ESP32 "CYD" ThermoPro Bridge

A proof-of-concept bridge that reads temperature data from a ThermoPro TP930 (among others) Bluetooth BBQ thermometer and ships it to a self-hosted HTTP API for storage and display.

Architecture

ThermoPro TP930 (BLE)
        │
        ▼
thermopro_cli.py  ←── BLE reader (reverse-engineered protocol, Linux only)
        │
        ▼
  bridge_poc.py   ←── polls CLI, normalizes payload, POSTs to API
        │
        ▼
  receiver.php    ←── stores latest reading + NDJSON log, serves status page

Components

thermopro_cli.py

Local copy of the thermopro-cli tool. Communicates directly with the ThermoPro thermometer over BLE using a reverse-engineered protocol. Outputs probe temperatures, battery level, and unit as JSON.

bridge_poc.py

Polling bridge that:

  1. Calls thermopro_cli.py on a configurable interval
  2. Normalizes the raw CLI output into a structured payload
  3. POSTs the reading to the configured HTTP endpoint with a shared secret token

receiver.php

Minimal PHP API server that:

  • POST /api/thermopro/readings — accepts and validates a reading, writes it to data/latest.json and appends to data/readings.ndjson
  • GET /api/thermopro/latest — returns the most recent reading as JSON
  • GET / or GET /status — HTML status page (auto-refreshes every 10 seconds)

Data Format

Each reading payload looks like:

{
  "deviceId": "tp930-smoker",
  "device": "ThermoPro TP930",
  "mac": "C9:48:1D:B1:E1:E7",
  "connected": true,
  "battery": 90,
  "unit": "F",
  "probeCount": 4,
  "probes": [
    { "id": 1, "name": "Probe 1", "temperature": 266.2, "connected": true },
    { "id": 2, "name": "Probe 2", "temperature": null, "connected": false },
    { "id": 3, "name": "Probe 3", "temperature": null, "connected": false },
    { "id": 4, "name": "Probe 4", "temperature": null, "connected": false }
  ],
  "readingTime": "2026-06-06T12:00:00+00:00",
  "bridgeTime": "2026-06-06T12:00:01+00:00",
  "source": "thermopro_cli"
}

Requirements

  • Bridge host: Linux with Bluetooth (BlueZ), Python 3.8+, bleak library
  • API server: PHP 8.x with write access to the data/ directory

Setup

1. Install Python dependencies

pip install bleak

2. Configure the receiver

Deploy receiver.php to a PHP-capable web server. Set the THERMOPRO_BRIDGE_TOKEN environment variable (or it defaults to dev-secret):

export THERMOPRO_BRIDGE_TOKEN="your-secret-token"

3. Run the bridge

python bridge_poc.py \
  --cli thermopro_cli.py \
  --endpoint https://your-server/api/thermopro/readings \
  --token your-secret-token \
  --device-id tp930-smoker \
  --mac C9:48:1D:B1:E1:E7 \
  --interval 15

Options:

Flag Default Description
--cli thermopro_cli.py Path to thermopro_cli.py
--endpoint http://127.0.0.1:8000/api/thermopro/readings API endpoint
--token dev-secret Shared token (X-Bridge-Token header)
--device-id tp930-smoker Stable identifier for this device
--mac C9:48:1D:B1:E1:E7 ThermoPro BLE MAC address
--interval 15 Polling interval in seconds
--once Read and POST once, then exit

Finding your device's MAC address

python thermopro_cli.py scan

Data Storage

File Contents
data/latest.json Most recent reading (overwritten each poll)
data/readings.ndjson Append-only log of all readings (newline-delimited JSON)

Security

  • The API endpoint is protected by a shared secret sent as the X-Bridge-Token request header.
  • Use HTTPS for the endpoint in production to keep the token confidential.
  • The token is compared with hash_equals() to prevent timing attacks.