# Protocol notes What we know about each probe vendor, and how much to trust it. Everything here is community reverse engineering unless marked otherwise — none of it is vendor documentation except where linked. --- ## MEATER / MEATER+ / MEATER 2 — implemented, unverified Source: [`sources/meater.py`](../src/smokescreen/sources/meater.py) Derived from [nathanfaber/meaterble](https://github.com/nathanfaber/meaterble) and the Home Assistant MEATER BLE work. | Item | Value | |---|---| | Service UUID | `a75cc7fc-c956-488f-ac2a-2dbc08b63a04` | | Temperature char | `7edda774-045e-4bbf-909b-45d1991a2876` | | Battery char | `2adb4877-68d8-4884-bd3c-d83853bf27b8` | Temperature payload is little-endian `uint16` fields: ``` [0:2] tip raw → tip_c = (raw + 8) / 16 [2:4] ambient raw [4:6] ambient offset → ambient_counts = tip + max(0, ((ra - min(48, oa)) * 16 * 589) // 1487) → ambient_c = (ambient_counts + 8) / 16 ``` Battery is `uint16 * 10`, clamped to 0–100. **Confidence:** tip decode is simple and well corroborated across projects — trust it after one bench check. The **ambient decode is an empirical fit** that its original authors flagged as hard to reproduce across tip/ambient combinations. Treat ambient as advisory. We drive the grill from `tip_c` only. **Operational limit:** a MEATER probe accepts **exactly one** BLE connection. While the Pi holds it, your phone and the Block cannot see it, and vice versa. Plan on the Pi owning the probe for the duration of a cook. **Before trusting it:** put the probe in ice water and boiling water, compare against the MEATER app. --- ## ThermoWorks RFX — implemented, cloud-only Source: [`sources/thermoworks.py`](../src/smokescreen/sources/thermoworks.py) RFX probes transmit **sub-1 GHz RF** to an **RFX Gateway**, which forwards over WiFi to ThermoWorks Cloud. The probes never speak BLE, so the nRF52840 dongle cannot see them at all. Implemented against [`thermoworks-cloud`](https://github.com/a2hill/python-thermoworks-cloud) (a2hill), an unofficial library built from the observed behaviour of the ThermoWorks web client. Explicitly tested with RFX by its author, and confirmed working by Home Assistant users through a 24-hour brisket cook. **Licensing:** it is GPLv3, so it is an optional extra (`pip install '.[thermoworks]'`) rather than a core dependency. That keeps the GPL off this project unless you opt in. ### API shape ``` get_user() -> user.account_id get_devices(account_id) -> [Device(serial, label, battery, ...)] get_device_channel(serial, "1") -> DeviceChannel(value, units, last_telemetry_saved, ...) ``` There is **no channel listing endpoint**. Channels are discovered by requesting ids 1..9 until one 404s — the same approach the Home Assistant integration takes. We cache the result per device rather than re-probing every cycle, since each attempt is a request against an unofficial API. ### The thing that will bite you **ThermoWorks Cloud returns a dead probe's last known value with a perfectly healthy HTTP 200.** A probe that fell off an hour ago still reports a temperature, and nothing in the response status hints that anything is wrong. So a successful fetch tells you nothing about whether the number is current. We date every reading by the cloud's own `last_telemetry_saved` / `last_seen` timestamp and backdate `ProbeReading.timestamp` accordingly, so the normal staleness path measures the age of the *measurement* rather than the age of our HTTP request. Readings older than `max_cloud_age_s` (default 300 s) are dropped outright. Without that backdating, the fail-cold policy would never trip for a cloud source, and a jack would sit there confidently displaying an hour-old temperature. ### Practical limits | | | |---|---| | Polling | 60 s works; the HA integration ships a useless 1800 s default | | Latency | probe → gateway → cloud → here. Fine for low-and-slow, poor for searing | | Dependency | your internet is in the loop mid-cook; if it drops, jacks fail cold | | Free tier | up to 10 cloud devices; 11+ needs a paid plan | ### Direct RF — not available Receiving the sub-1 GHz signal directly with an RTL-SDR would remove both the cloud and the gateway. It was [requested in rtl_433 (#3041)](https://github.com/merbanan/rtl_433/issues/3041) in August 2024 and **closed with no decoder written**. No prior art to build on. One Home Assistant user noticed the RFX Gateway enumerates as an **Espressif device — it is an ESP32**, which hints at a possible local API. Nobody has explored it. That is the highest-value lead if the cloud dependency becomes annoying. ### Other ThermoWorks families Not handled by this driver. **Node** is BLE and would want something shaped like the MEATER driver. **Signals** is a different WiFi/cloud device — the same `thermoworks-cloud` library covers it, but channel semantics differ. --- ## Combustion Inc — not implemented, but the easiest of the three Source: [`sources/combustion.py`](../src/smokescreen/sources/combustion.py) The only vendor here with an **open published protocol** and first-party SDKs: Best-fit for this project for one specific reason: probes broadcast temperatures in **BLE advertisements**, so you can read them without connecting. That sidesteps MEATER's one-connection-at-a-time problem entirely — the Pi can listen while your phone stays connected to the same probe. Notes to verify against the spec before implementing: - Service UUID `00000100-CAAB-3792-3D44-97AE51C1407A` - 8 temperature sensors (T1 tip → T8 handle), packed as 8 × 13-bit LE = 13 bytes - `celsius = raw * 0.05 - 20` - Use the **virtual core** sensor, not raw T1 — core is Combustion's corrected food temperature, T1 is just the physical tip Deliberately left raising `NotImplementedError` rather than shipping a parser written from memory. A 13-bit unpack that's off by one bit boundary produces confident garbage, and the grill acts on these numbers. --- ## The wireless Traeger probes — the road not taken Documented so it isn't re-litigated later. The 2022 Ironwood also supports "Traeger Wireless Meat Probes", which are **rebranded MEATER hardware** — Traeger acquired Apption Labs (MEATER) in 2021. But Traeger deliberately changed the pairing: plain MEATER probes will not pair to an Ironwood, and Traeger probes will not pair to the MEATER app. That is an intentional auth/whitelist boundary, not an accident. Reasons this project drives the wired jacks instead: 1. **It's a real auth boundary.** The cross-pairing block is deliberate, so expect challenge-response, not just a different service UUID. 2. **Firmware updates.** A wired PT1000 is a physical standard; a BLE pairing handshake is a moving target Traeger can change at will. 3. **One dongle can't MITM.** A true man-in-the-middle needs two radios — one central to the real probe, one peripheral to the grill. A single nRF52840 is one radio. You'd need two dongles, or the Pi's onboard BLE as the second side. 4. **MITM was never the goal anyway.** Sitting between a real Traeger probe and the grill only relays temperatures you already have. To inject *your* probes you want **emulation**, and emulation is exactly what the wired jack gives you for free. If you ever do revisit it, the nRF52840 dongle's real job there is [nRF Sniffer for BLE](https://www.nordicsemi.com/Products/Development-tools/nRF-Sniffer-for-Bluetooth-LE) — capture a genuine probe pairing to Wireshark and start from the handshake. In the meantime the dongle earns its place as the BLE central for your third-party probes, with a better antenna than the Pi's onboard radio.