ThermoPro is a company selling digital thermometers and hygrometers. They're supposedly a Canadian company with R&D in North America and products manufacturered in China but there are no public records that I can find that verify an actual address anywhere.
They have a number of products that have wireless capability such as RF and Bluetooth. I'll be focusing on a couple of their RF products, namely the TP200C and the TP65C.
I first bought the TP200C monitor and remote sensor a few months prior to using an SDR to decode its signals. I am based in the United Kingdom and the frequency listed on the cover of the product is 868 MHz. Apparently they also make units that transmit on 433 MHz but are discontinued, and make units on 915 MHz for the American market. I suspect the RF protocol is the same.
I did most of the decoding process below with the transmitter that came with the temperature-only TP200C, the TX-2C. ThermoPro list the transmitter-only listing for TX-2C as being a dual thermometer and hygometer so I was a little confused because the transmitter I had was not fitted with a hygrometer sensor inside it.
Curious, I bought the TP65C, a thermometer and hygrometer display, and repeated the process. The TX-2C transmitter that came with this one was fitted with a hygrometer sensor. It's trivial but I imagined the exact same product model would be built the exact same no matter what it's paired with but I guess not.
Visual Decoding Process
Tuning into 868 MHz with a Software Defined Radio (SDR) shows a very distinctive signal in SDR#:

The signal is strong with a fresh set of AAA batteries and stays mostly around 867.990 MHz so far between -1 °C and 25 °C.
Focusing on that signal alone in Audacity (hey) we can quickly see what it's made of:

It's On-Off Keying (OOK) with a total burst duration of ~1.15 seconds and repeats every 50 seconds. This sits at 2.3% duty cycle already exceeding the permitted duty cycle of 1% on 868 MHz.
Looking closer at the OOK signal, I can make some assumptions:

- The frame repeats the data eight (8) times.
- The last frame omits that one final pulse.
- The pulses are fixed-width, and the gaps vary.
- There are 46 pulses per frame, 45 on the last frame.
- The pulse is the shortest quantifiable data (a quanta) and is 474 microseconds in length.
- The gaps have a duration in multiples of that length.
Here is an exerpt that shows 4 different gap lengths:

I can see that the bulk of the data is made up of "4" and "8" gaps, whereas "5" and "18" gaps are only used 2 and 1 times respectively. The "4" gaps were easily confused with "5" gaps and it did throw me off for a while before I caught on.
It's safe to assume that the pulse can be used as a start of piece of data called a symbol and I will name these as the following:
- Symbol "0" = 10000 (4 zeros)
- Symbol "1" = 100000000 (8 zeros)
- Symbol "S" = 100000 (5 zeros, appears twice)
- Symbol "L" = 1000000000000000000 (18 zeros, appears once)
Now we can go through the whole frame and condense the signal down into a string:

000000SL100111110110000000000001010000110101S0
Hopefully this means something to us.
During this burst, the receiver displays the temperature as 2.0 °C. I raised and lowered the temperature of the remote sensor in order to see the change in data and figured out which bits relates to that. I also removed the batteries, changed the "channel" switch on the sensor, pressed the transmit button on the back of the sensor, put nearly dead batteries in, put the sensor in the freezer and in front of the heater, and I was able to split that data up in to the following fields:
000000SL1001 11110110 0 0 00 000000010100 00110101 S0
-
000000SL1001never changed -
11110110(8-bit) is the pairing code -
0is the battery low indicator -
0is the force transmit button used -
00is the channel -
000000010100is the temperature in Celsius -
00110101is the humidity -
S0never changes
Now it's time to experiment. I wanted to transmit my own RF signal armed with the information above. I used an ESP32 microcontroller and a CC1101 module for transmitting/receiving RF signals. I programmed the ESP32 to send a packet of data that replicates everything above and played around with the values.
Here is what I've found:
Pairing Code
The pairing code is a seemingly "random" 8-bit integer generated when batteries are put into the sensor. There is some kind of counting sequence that appears when you remove and insert the batteries repeatedly, but sometimes the number goes up, sometimes down, sometimes it's set to a noticeable bit pattern like 10101010, 00000000, 11000000, etc.. I think for a very low powered device, it just does what it can to pick something seemingly random so there's probably not much to it.
When a receiver is paired with a sensor, the receiver will not respond to any other number. This is expected. I wondered if there was maybe a "universal" bit pattern that all receivers allow, or some kind of test pattern. Unfortunately, I did not find anything.
Bits like 11111111, 00000000, 01111111, 10000000, 10101010, 01010101 and so on were all tested and the receiver did not care. It only responded to the pairing code it was told to.
Battery Low Indicator
This bit is a simple 1-bit field. Zero means the battery is fine. When set, the little "battery low" icon on the display is visible indicating the batteries should be recharged or replaced. I've yet to test the threshold voltage that makes the sensor set this bit but it's not that important.
Force Transmit
This bit is set when the user presses the button on the back of the unit to force it to transmit instead of waiting 50-something seconds. This is useful when pairing the sensor with the receiver. I haven't determined if the receiver actually makes use of this bit. Maybe it influences how often the receiver should poll the radio for a signal. I haven't tested that theory yet.
Channel Number
This sensor and receiver can be set up with channels from 1 to 3. Some receiver units can display up to 3 remote sensors. There is a 3-position switch on the sensor for this. The signal transmits a 2-bit integer for this. A bit of 00 is channel 1, 01 is channel 2, and 10 is channel 3.
Obviously I tried 11 (a theoretical channel 4) and the receiver responded as if it were channel 3.
Temperature
This 12-bit field is the meat of the subject; this is what makes the product exist in the first place.
Upon first glance and reading through the product documentation, these units have a temperature range between -20 °C and 70 °C which has been tested.
With the RF signal showing 000000010100 for the 12-bit temperature, and the receiver showing 2 °C (it's winter time), that integer is twenty (20) in decimal. After several captures, I can confirm this number needs to be devided by ten (10) to give you the decimal point. This means 21 is actually 2.1 C and so on.
Below 0 °C, it's apparent this field is actually a signed integer meaning it conveys negative numbers using twos-compliment and all the bits are mostly set which again confirms these all 12 bits are used for this.
Here's an example of some values:
-
000000000100= 4 = 0.4 °C -
000000000011= 3 = 0.3 °C -
000000000010= 2 = 0.2 °C -
000000000001= 1 = 0.1 °C -
000000000000= 0 = 0.0 °C -
111111111111= 4095 = -0.1 °C -
111111111110= 4094 = -0.2 °C -
111111111101= 4093 = -0.3 °C -
111111111100= 4092 = -0.4 °C
This means this bit field supports values between -2048 °C and +2047 °C.
Unsurprisingly, the receiver isn't impressed when I give it such extreme values as it displays "LL.L" for values below -99.9, and "HH.H" for values above 69.9.
I did wonder why they limited the upper value to 69.9 specifically. Maybe the lack of accuracy isn't gauranteed that far from its tested calibration values or something, or to dissuade people from putting them in ovens as an oven thermometer. Perhaps they didn't think the sensor would ever see negative values ever to bother limiting it.
It's fun that they mentioned it can work at +70 °C. They lied!
Humidity
This one is simple enough. It's an 8-bit value. Unsigned, no operations needed. The receiver will limit this value on its display from 10% to 99% humidity. Nothing more, nothing less.
Setting this value to extremes like 0 or 255 does nothing but make the display show 10% and 99% respectively.
As mentioned at the beginning, the TX-2C transmitter I received with the temperature-only model was not made with a hygrometer. This did not change the protocol. The transmitter was sending the humidity value as a fixed value of ten (10) in decimal. This suggests the firmware reads the "sensor" input and clamps it between 10 and 99 before sending the signal out.
Curious, as some units ship without a hygrometer sensor, I removed these 8 bits as if they were truly optional, but unfortunately the receiver did not recognise the packet and did not trigger any update.
Test mode
Holding the "force transmit" button in on the transmitter while inserting batteries puts the device into some kind of test mode where it emits 100% duty cycle of packets non-stop. This basically becomes a radio jammer.
The initial data it sends eight (8) times is this:
000000SL1001 00000000 0 1 00 001010111100 01111001
- The first 12 bits again never change.
- The 8-bit pairing code is set to all zeros.
- Battery is always ok, even if they're nearly dead.
- Forced button bit is always set.
- Channel is always
00. - The temperature value is always 700 (or 70.0 °C, making the receiver show "HH.H").
- The humidity value is always 121%, making the receiver show "99%".
These frames are sent 8 times and then immediately change reflecting the real battery status, temperature, and humidity only. All other fields remain the same as bulleted above. Even though it's now constantly emitting packets of data, the actual sensor values do not update from frame-to-frame, or packet-to-packet. The values still only change every 50+ seconds as originally intended.
The only use I can see for this behaviour is aiding manufacturing to make sure the units are emitting a signal quickly and correctly for more than 1 second. Accidentally holding that button while inserting the batteries is a fairly likely action and would drain the batteries quickly, interfere with other or identical devices on that frequency.
Before we finish...
I decided to play with the bits near the beginning of the frames where it's 1001 and changing these made no difference at all. The receiver seems to almost ignore these but it's noteworthy to say these are not part of the pairing code immediately after to it. Perhaps this is a version number for the protocol used.
Now I can put these devices to rest and hopefully somebody found this useful.
Other products
They sell other products like the TP211B, TP280B, and TP260B claim to operate on 915 MHz (America) or 868 MHz (Europe) using Frequency Shift Keying (FSK) instead of OOK. Maybe it will be interesting to decode those one day.
Top comments (0)