ThermoPro reverse engineering 4: Trying to take control

Summary
- The TP25 requires at least one
0x01
command before it'll send temperatures- Just sending
0x300030
leads to what I believe is an error code.
- Just sending
- After that it'll happily send updates seemingly forever. I'm not sure what's different to the app-driven data flow.
More observations
Since my last post, I've noticed a couple more things about responses being sent by the device
- They seem to always be 20 bytes long
- The last two bytes are often
0x0200
(most commands) or0x0140
(0x30 command). The exceptions are responses0x01
and0x23
.
A basic Rust controller program
I left the last post feeling like I ought to try and control the thermometer myself. To that end, I wrote a basic
controller program in Rust. You can see it in my
thermopro-tools
repo.
As it stands, it's not very much - it simply connects to the thermometer, sends a command, and then prints out any notifications it receives.
Why Rust? Basically I thought I ought to try and learn at least the basics. I like that it's memory-safe without needing a garbage collector, that it's fully typed, and that it compiles to machine code. It seems like it's easy enough to compile to WebAssembly as well, which might be useful for my purposes...
Sending a 0x30
command
Take a look at commit 94243b9 to see the program in this state.
My hypothesis was that you could send a 0x30 command and receive temperature responses. What actually happened was this:
Write / notify? | Data |
---|---|
Write | 300030 |
Notify | E00230041600917D0000E9190020480000200200 |
And that was it! Not exactly what I was expecting... I assume the 0xe0 response is some kind of error code? At least it
meets all the other usual constants - it's 20 bytes long, and finishes 0200
.
Sending a 0x01
command
Commit 66b4f05 does this.
My original theory was that the 0x01 command is some kind of preamble / setup command. What happens if we send that?
Write / notify? | Data |
---|---|
Write | 01097032e2c1799db4d1c7b1 |
Notify | 01010a0ce2c1799db4d1c7b10020c1799db4d1c7 |
Notify | 300f5a0c00ffffffffffff0222ffffffffbf0140 |
Notify | 300f5a0c00ffffffffffff0222ffffffffbf0140 |
Notify | 300f5a0c00ffffffffffff0222ffffffffbf0140 |
Notify | 300f5a0c00ffffffffffff0222ffffffffbf0140 |
Notify | 300f5a0c00ffffffffffff0222ffffffffbf0140 |
Notify | 300f5a0c00ffffffffffff0263ffffffff000140 |
Notify | 300f5a0c00ffffffffffff0291ffffffff2e0140 |
Notify | 300f5a0c00ffffffffffff0305ffffffffa30140 |
Notify | 300f5a0c00ffffffffffff0305ffffffffa30140 |
Notify | 300f5a0c00ffffffffffff0318ffffffffb60140 |
Notify | 300f5a0c00ffffffffffff0318ffffffffb60140 |
Notify | 300f5a0c00ffffffffffff0318ffffffffb60140 |
Notify | 300f5a0c00ffffffffffff0318ffffffffb60140 |
Notify | 300f5a0c00ffffffffffff0328ffffffffc60140 |
Notify | 300f5a0c00ffffffffffff0328ffffffffc60140 |
Notify | 300f5a0c00ffffffffffff0328ffffffffc60140 |
Notify | 300f5a0c00ffffffffffff0328ffffffffc60140 |
Notify | 300f5a0c00ffffffffffff0331ffffffffcf0140 |
Notify | 300f5a0c00ffffffffffff0331ffffffffcf0140 |
... | seemingly forever |
The most interesting things are:
- We get a continuous set of responses without needing to send a continuous set of commands - so it's not clear why the app keeps sending 0x30 commands
- The thermometer does not seem to need anything other than 0x01 to get started.
Deliberately sending a wrong checksum
Notice how the last nybble has changed from one to zero:
Write / notify? | Data |
---|---|
Write | 01097032e2c1799db4d1c7b0 |
Notify | e0020102e5c1799db4d1c7b00020480000200200 |
I wondered if this would be the same response as sending a 0x30 command straight away, but it is not. It is still a 0x0e response though, which solidifies my belief that 0x0e is an 'error' response of some kind.
Sending a slightly different 0x01
command
This time I changed the second-to-last byte, but made sure the checksum was still correct:
Write / notify? | Data |
---|---|
Write | 01097032e2c1799db4d1c6b0 |
Notify | 01010f11e2c1799db4d1c6b00020c1799db4d1c7 |
Unlike the original 0x01 command snooped from the app, this one did not automatically trigger a sequence of 0x30 responses.
There is still a lot of repetition in the middle of the response. The beginning is subtly different, and the last few bytes exactly match the response from the snooped 0x01 command. I'm not sure what to make of this - perhaps:
- The beginning few bytes are some flags indicating the current state
- The last few bytes are the magic code that needs to be sent to kick things to life?
Conclusions
This hasn't been hugely enlightening. The main oddity is that the thermometer will send 0x30 notifications without needing to wait for an 0x30 command, and I'm not sure why. I assume that when the app is driving the thermometer, one of the commands must turn off the automatic 0x30 notifications. To check this, I need to expand my controller app.
Next steps:
- Expand the rust controller to send more commands.
- After that, record some more 'real' data flows to look for patterns.