# 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 ```plain 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](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: ```json { "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 ```bash 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`): ```bash export THERMOPRO_BRIDGE_TOKEN="your-secret-token" ``` ### 3. Run the bridge ```bash 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 ```bash 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.