main
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:
- Calls
thermopro_cli.pyon a configurable interval - Normalizes the raw CLI output into a structured payload
- 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 todata/latest.jsonand appends todata/readings.ndjsonGET /api/thermopro/latest— returns the most recent reading as JSONGET /orGET /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+,
bleaklibrary - 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-Tokenrequest header. - Use HTTPS for the endpoint in production to keep the token confidential.
- The token is compared with
hash_equals()to prevent timing attacks.
Description
Languages
Python
90.4%
PHP
9.6%