Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ThermoWatch

Unattended BBQ monitoring for ThermoPro Bluetooth thermometers. Runs on a Mac or a Raspberry Pi, alerts your phone via Telegram, and tells you when the meat will actually be ready.

Not affiliated with, endorsed by, or connected to ThermoPro. This is an independent project that talks to their hardware over Bluetooth.

The phone app is great until you want to leave the house. This runs on a machine that stays near the grill and pushes to a phone that doesn't.

Thermometer ──BLE──> Mac or Pi ──┬─> Telegram    alerts + commands, anywhere
                                 ├─> menu bar    live temps on macOS
                                 ├─> browser     http://<host>:8787/
                                 └─> SQLite      every reading, for later

What you get

  • Alerts that don't cry wolf. Opening the lid drops a pit 50 °C in a minute. Naive monitoring reads that as a dying fire and wakes you at 3am. This doesn't.
  • A dead-man switch. The failure that ruins an overnight cook isn't a missed threshold, it's silence — the process dies and no news reads as good news. A heartbeat says "still alive, here are the numbers", so silence is abnormal.
  • Time estimates that survive the stall. Linear extrapolation says six hours mid-stall and forty minutes an hour later. This fits the actual heat transfer.
  • Remote commands. /temp, /status, /eta, change thresholds, all from your phone.

Hardware

Works with ThermoPro BBQ thermometers using the FFF0 BLE protocol — the multi-probe wireless models. Confirmed on TP25 / TP25W / TP902 / TP920 / TP960, and anything else advertising service 1086FFF0-3343-4817-8BB2-B32206336CE8.

Run ./setup.sh — if it finds your thermometer, you're supported.

You'll also need a Mac (macOS 14+) or a Raspberry Pi with Bluetooth, within about 10 m of the grill.

Quick start

git clone https://github.com/YOUR_USER/ThermoWatch.git
cd ThermoWatch
./setup.sh

Setup scans for your thermometer, connects, and shows you live readings from each port so you can say which is the meat and which is the air — no need to know your port numbers, and the wire's numbering doesn't always match the app's.

Live probes:
  Port 2: 22.3 C
  Port 4: 135.2 C

Which port is in the grill (air temperature)? [4]
Which port is in the meat? [2]
What are you cooking? [brisket]

Then:

./run.sh

Quit the phone app first. The thermometer accepts exactly one Bluetooth connection and won't even advertise while the app holds it. See below.

The one constraint that matters

The thermometer allows a single BLE connection, first-come-first-served. Whoever has the link keeps it; a second client is refused rather than allowed to take over.

  • Quit the phone app before starting ThermoWatch, or it will sit there scanning and finding nothing.
  • Once ThermoWatch is running, opening the phone app is harmless — it simply won't connect. Your monitoring keeps going.
  • To use the phone app again, stop ThermoWatch first.

This is the device's behaviour, not a limitation of this code.

Telegram

This is what lets you leave. Outbound HTTPS only — no port forwarding, no exposed service, no cloud account.

  1. Message @BotFather, send /newbot, follow the prompts
  2. Copy the token it gives you
  3. Open your new bot and press Start (bots can't message you first)
  4. Paste the token when ./setup.sh asks

Commands

Command What it does
/temp current temperatures
/status temperatures, trend, battery, link state, estimates
/eta time to the wrap point and the finishing temperature
/wrapped tell it you've wrapped — ends the stall in the estimate
/set Pit high 160 change a threshold live
/set Pit falling off clear a threshold
/mute 60 silence alerts for an hour (heartbeat continues)
/help list commands

There's no fire control here. A kettle has no actuator, so "remote control" means querying and changing alerts, not adjusting vents.

macOS menu bar app

cd menubar && ./scripts/build.sh
cp -R build/ThermoWatch.app /Applications/
open /Applications/ThermoWatch.app

Shows 🔥 72° · 131° in your menu bar, with per-probe detail, trend and battery in the dropdown.

Point it at wherever the monitor is running:

# monitor on this same Mac
defaults write com.thermowatch.menubar statusPath ~/ThermoWatch/data/status.json

# monitor on a Pi (needs enabled = true under [http])
defaults write com.thermowatch.menubar statusPath http://192.168.1.50:8787/status.json

It never shows a number it can't vouch for. A frozen-but-plausible temperature is worse than an obvious error, so it distinguishes:

Display Meaning
flame + 72° · 131° live
⚠ + "no data for 3m" monitor running, Bluetooth link dropped
⚠ + "monitor not running" the monitor process itself is gone
⚠ + connected, but no probe is reporting

The app deliberately does not speak Bluetooth — it would fight the monitor for the thermometer's single connection.

Browser view

With enabled = true under [http], visit http://<host>:8787/ from any device on your network. Big digits, auto-refreshing, greys out when data goes stale. Works fine on a phone.

Running on a Raspberry Pi

Better than a laptop: always on, no sleep to fight, and it can live closer to the grill.

git clone https://github.com/YOUR_USER/ThermoWatch.git ~/ThermoWatch
cd ~/ThermoWatch && ./setup.sh

Then install the service — no root needed if user lingering is on (loginctl show-user $USER --property=Linger; enable with sudo loginctl enable-linger $USER):

mkdir -p ~/.config/systemd/user
sed "s|/home/YOUR_USER|$HOME|g; s|AA:BB:CC:DD:EE:FF|$(grep '^address' config.toml | cut -d'"' -f2)|" \
    systemd/thermowatch.service > ~/.config/systemd/user/thermowatch.service
systemctl --user daemon-reload
systemctl --user enable --now thermowatch

Check on it:

systemctl --user status thermowatch
tail -f ~/ThermoWatch/data/monitor.log

Signal strength matters more than you'd think. Below about −80 dBm a scan still finds the device but a sustained connection drops repeatedly. Aim for −75 dBm or better; ./setup.sh warns you if it's weak.

Running on a Mac unattended

caffeinate prevents idle sleep, but closing the lid still sleeps the machine — that's a separate mechanism. For an overnight cook, lid open and plugged in:

caffeinate -dimsu ./run.sh

Honestly, use a Pi.

How the alerting thinks

Threshold alarms are the easy part. Everything below exists because a real cook broke a simpler version.

  • Silence is the enemy. A heartbeat every 30 minutes, plus an alert if no reading arrives for 3 minutes. The dead-man is driven by frame arrival, not connection state — a BLE link can sit there looking healthy while notifications have quietly stopped.
  • Lid-opens are not dying fires. Thermal mass means a dying fire cannot drop a pit tens of degrees in a couple of minutes; only opening the lid (or spritzing the probe) can. Drops that steep are treated as disturbances, and the downward alerts pause while the pit recovers. It's a grace period, not an amnesty — still cold when it expires, and it alerts.
  • A gap in the data is also a disturbance. Nothing was recorded, so nothing about the change can be attributed.
  • Declines are judged by where they're heading, not by slope. A pit falling 0.8 °C/min at 150 °C is fine; the same slope at 110 °C is not. It alerts when a fall is projected to breach your floor, and says when.
  • Thresholds latch with hysteresis. A pit parked on its limit is one event, not one alert per cooldown, forever.
  • Empty ports stay quiet; probes that fall out don't. A slot that never reported is just an unused socket. A slot that was reading and stops means a probe came out of the meat.

Time estimates

/eta fits Newton's law of cooling — d(meat)/dt = k · (pit − meat) — to your actual data, then simulates forward.

Validated against a real 6-hour brisket: fitting k on the first 45 minutes predicted the next hour to within 1 °C. k held between 0.0047 and 0.0061 while the raw rate halved, which is why rate-based extrapolation misleads and this doesn't.

The stall is detected as k collapsing to about a third of its fitted value — a physical parameter changing, rather than a flat line being guessed at.

Estimates are ranges, because a stall lasting 1–4 hours doesn't support a single number, and they state the pit temperature they assume:

wrap point (75C): 8m-13m
target (95C): 72m-2.2h
phase: in the stall, assumes pit holds 133C

Wrapping ends the stall by stopping evaporative cooling, and can't be inferred reliably — so tell it with /wrapped.

Cook profiles

Set profile on the meat probe: brisket, pork_butt, beef_short_rib, chicken, turkey, pork_loin, lamb_leg, beef_joint.

Where a target is a food-safety figure (poultry, pork) it's stated as such. Where doneness is a preference (lamb, beef joints) the profile says so rather than implying an authority it doesn't have. Override with final_target.

Troubleshooting

Nothing found when scanning. The phone app is almost certainly connected. Force-quit it — backgrounding isn't enough on iOS — and scan again.

It found the device but won't connect, or reconnects forever. A killed process can leave the Bluetooth link open on Linux; the thermometer then stops advertising because it thinks it's still connected. ThermoWatch falls back to connecting by address, and the systemd unit clears the link on start and stop. To fix by hand: bluetoothctl disconnect <MAC>.

Menu bar app shows a warning triangle. It can't reach the monitor. Check the monitor is running, and that statusPath points at the right place. For an HTTP path, check the firewall (below).

Can't reach http://<pi>:8787/ from another machine. Raspberry Pi OS may run ufw. Note ufw lives in /usr/sbin, which isn't in a non-login SSH PATH, so command -v ufw misleadingly reports nothing — use systemctl is-active ufw. Open it to your LAN only:

sudo ufw allow from 192.168.1.0/24 to any port 8787 proto tcp

macOS blocks the HTTP connection. App Transport Security blocks plain HTTP. The bundled Info.plist carries NSAllowsLocalNetworking, which permits local destinations only — if you rebuilt it and dropped that, put it back.

Temperatures look wrong / the wrong probe is named. Re-run ./setup.sh; it shows live readings per port so you can reassign them.

The address in config doesn't work on another machine. macOS reports a per-machine UUID rather than a MAC address, so a macOS address is meaningless on a Pi and vice versa. Re-run setup on the new machine.

Data

Everything lands in data/cook.db (SQLite):

  • frames — timestamp, battery, raw hex, so a cook can be re-decoded if any decoding assumption later turns out to be wrong
  • samples — per-probe temperature or sentinel state
  • events — alerts and heartbeats

Finished cooks are archived to data/archive/cook-YYYY-MM-DD-<profile>.db and each new cook starts clean. The trigger is a gap in the data, not process startup — the service restarts itself on crash, and splitting a cook's history would break the trend and estimate calculations.

Protocol

Reverse-engineered by others, re-validated live. See thermowatch/protocol.py.

  • Service 1086FFF0-3343-4817-8BB2-B32206336CE8
  • Write a handshake to FFF1, then frames stream from FFF2 about every 1.5s. Without the handshake, nothing streams.
  • Command 0x30 carries battery and four probe slots, two bytes each, BCD-packed as hundreds/tens/ones/tenths with sign in the top bit
  • Sentinels: FFFF no probe, DDDD error, EEEE over range — states, not temperatures, and never treated as numbers
  • The wire unit is Celsius regardless of what the phone app displays

Prior art, without which this would have taken far longer: thermopro-cli and ha-thermopro-meat.

What it doesn't do

  • Control the fire. No actuator, no vent servo, no blower.
  • Work beyond Bluetooth range. The host machine must be near the grill.
  • Run on Windows. bleak supports it; nobody has tried.
  • Replace judgement. Brisket is done when it's probe-tender, not at 95 °C.

Tests

venv/bin/python -m pytest

The decoder tests run against a frame captured off real hardware. The alert tests replay 30 minutes of an actual cook — including two lid-openings and a vinegar spritz that dropped the pit to 48 °C — and assert that none of it produces a false alarm, while a genuine decline still does.

That fixture found three separate bugs that synthetic test data did not.

Licence

MIT — see LICENSE.

About

Unattended BBQ monitoring for ThermoPro Bluetooth thermometers. Telegram alerts, macOS menu bar, browser dashboard, and time-to-target estimates. Runs on a Mac or a Raspberry Pi.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages