Building a Custom SwiftUI Application for my Bluetooth Meat Probe
- Feb 22
- 4 min read
This Thanksgiving, it was my first time ever cooking a turkey. Our old meat thermometer that's been with us for years now is finally starting to go, and we wanted to upgrade it, especially with Thanksgiving coming around.
We bought this meat thermometer on Amazon and everything worked great.. except for the app (and it was no secret, either) - it was not well received by the community at all.
To highlight a few fun reviews of this app:
"Only works sometimes" (1/5 stars)
"Have a brisket in the smoker and the probe won't even connect to my phone" (1/5 stars)
"Constantly disconnects and probe is not accurate" (1/5 stars)
"Won't connect" (1/5 stars)
It would've been nice to know these things before ordering, but the Amazon listing has 4.7 stars so it can't really be that bad, right?

Myth #1: The Probe Temperature Readings are Inaccurate



We tossed the probe into some ice water and boiling water, because we know very precisely what those temperatures should be without any equipment. Our ice water experiment pictured above wasn't perfect, I would have loved to fill the glass with tiny chunks of ice to get the water as close to 0C as possible, but it sufficed for our comparison. We did the same thing with boiling water, placing both our old meat probe and the new bluetooth probe in the boiling water to check how close not only they would get to each other, but also to 100C (we're in Boston at sea level).
Ice Water Results:
Our old probe: ~1.9 C
New bluetooth probe: ~2.0 C
Boiling Water Results:
Our old probe: ~96 C
New bluetooth probe: ~98 C
These measurements, along with the probe's listed +/- 1 C tolerance fall right into the expected temperature range. I'd say this probe is close enough!
Myth #2: The App is Hit-or-Miss
I do have to unfortunately agree with many of the reviews on this one.
The interface is clunky
The probe would frequently disconnect from the app
Whenever the probe did reconnect, all of the historical data was completely lost
That last bullet point was probably the worst of them all. I didn't want to be mid-cook of my thanksgiving turkey and lose all my tempearture vs time data (Sure, I could start a secondary timer, but why buy a $40 meat probe if it won't do it for me?).
It looks like I have some key elements I need for my app:
Simple, responsive interface
Handle probe connection (and potential disconnection) gracefully
Save my historical cooking data

My App - Step #1: Figure out the Bluetooth
I used an app called LightBlue, which allows you to scan and connect to any peripheral BLE devices around you. After some trial and error, I was able to connect directly to the probe through LightBlue.
After connecting, I saw that I was getting packets of bytes every ~0.5 seconds, they looked like this:
0x00000014DE00F000E9


I figured this was giving me temperature data from the probe but had no idea how it was structured. I broke it apart and scanned multiple packets while the temperature on the handheld device showed 25C:
Packet #1 00 01 B7 0D 01 FF FF FF
Packet #2 00 00 00 64 F5 00 FB 00 DD
Packet #3 00 01 B7 0D 01 FF FF FF
Packet #4 00 00 00 64 F5 00 FA 00 DD
Packet #5 00 01 B7 0D 01 FF FF FF
It's very clear that packets 1, 3, and 5 are identical and packets 2 and 4 differ only slightly. These packets all happened in the span of ~2 seconds, so it would make sense that the temperature of the probe in my ambient room temperature wasn't changing all that much. I wanted to check to see if any of these bytes are anywhere near my room temp reading of 25C or 77F (I was sitting next to a space heater). Through some trial and error, I was able to determine that I could disregard packets like 1, 3, and 5 as I assumed them to be some sort of status packet (they didn't change), but packets with 9 bytes held my temperature data:
func parsePacket(data: Data) { guard data.count >= 9 else { return } // Decode probe temp let probeRaw = UInt16(data[4]) | (UInt16(data[5]) << 8) let probeCVal = Double(probeRaw) / 10.0 // Decode ambient temp let otherRaw = UInt16(data[6]) | (UInt16(data[7]) << 8) let ambientCVal = Double(otherRaw) / 10.0 // Battery let bat = Int(data[8]) let batteryPct = Int(round(Double(bat)/255.0*100)) |
If we run through packet #2 as an example:
Index: [0] [1] [2] [3] [4] [5] [6] [7] [8]
Packet #2 00 00 00 64 F5 00 FB 00 DD
Indices [4] and [5]: Probe Temperature in Celsius
Indices [6] and [7]: Ambient Temperature in Celsius
Index [8]: Probe Battery
[4][5]: 0xF5, 0x00 --> Little Endian --> 0x00F5 Hex to Decimal --> 245 --> 24.5C (Probe)
[6][7]: 0xFB, 0x00 --> Little Endian --> -0x00FB Hex to Decimal --> 251 --> 25.1C (Ambient)
[8]: 0xDD --> Hex to Decimal --> 221 --> (221/255) * 100% = 86.7%
My App - Step #2: Make a UI and Test
I made this UI the night before our Friendsgiving (November 16) and wanted it to just.. work. I always wanted to know the status of the probe connection, so I put the status in the top right which updated continuously.

Pros:
The probe literally never disconnected. I didn't do anything special with my BLE protocol class, but it worked flawlessly
My temperature vs time graph continuously updated and was really cool to see throughout the cook
Logged my cook time perfectly
Future Improvements:
Would love to have a widget or live activity with live data updates, but Apple doesn't let you update widgets or live activities that frequently
Estimate time to cook completion based on historical data
Alarm that goes off when thermometer hits a specified temperature
Graph ambient temperature alongside probe temperature


So cool! Looks like the turkey turned out well!