This post sums up my steps for understanding the Maverick Redi Check MAV222 (aka Redi Check Model ET-732) checksum algorithm. I'm posting this, hoping it'll help others that are interested in figuring out, how the algorithm works. Note: this is not a generic recipe on how to proceed when reverse engineering something like a checksum algorithm.
Thanks to the previous posts in this thread, we already know, that the temperature values are tranmitted more or less in plain text. However, there wasn't much information available on what information the checksum contains, and how it behaves. I started by making a list of questions regarding the checksum.
Step1 - brainstorming- does it cover only temperature nibbles?
- does it also cover other data nibbles?
- is the checksum always the same for a particular temperature value?
- does it contain other information (e.g. display in °F/°C, battery status, retransmission count, device id, ...)?
- has it other purposes (e.g. error detection / error correction?)
- most importantly: when and how does the checksum change?
Step 2 - Gathering dataIn order to figure out, how the checksum behaves, it is beneficial to gather as much data as possible as a starting point. For deterministic results I tried to set up an environment, which is completely under control, including
I decided to replace the thermometers with variable resistors. In case anyone wants to reproduce this: the resistance is non-linear. At low temperatures (0..25°C) it takes several 100 KOhm, (around 0°C even MOhm) to increase / decrease the temperature by 1°C. At higher temperatures it takes just several KOhm to increase / decrease the temperature by 1°C. Furthermore, I used a stabilized external power supply, instead of batteries.
For gathering the actual data, I wrote a simple Linux kernel module, which prints the received data and checksum bits on the console. The output looks like this:
- Code: Select all | TOGGLE FULL SIZE
t0=0,t1=575,1111,1010,1000,0010,1000,1111,1100,0000,0000,0101,1010,0010,1001
t0=0,t1=576,1111,1010,1000,0010,1001,0000,0000,0000,0000,1011,1010,1010,1110
t0=0,t1=577,1111,1010,1000,0010,1001,0000,0100,0000,0000,0110,0110,0110,1110
t0=0,t1=578,1111,1010,1000,0010,1001,0000,1000,0000,0000,0001,0011,0000,1111
t0=0,t1=579,1111,1010,1000,0010,1001,0000,1100,0000,0000,1100,1111,1100,1111
t0=0,t1=580,1111,1010,1000,0010,1001,0001,0000,0000,0000,1111,1001,1100,1101
t0=0,t1=581,1111,1010,1000,0010,1001,0001,0100,0000,0000,0010,0101,0000,1101
t0=0,t1=582,1111,1010,1000,0010,1001,0001,1000,0000,0000,0101,0000,0110,1100
t0=0,t1=583,1111,1010,1000,0010,1001,0001,1100,0000,0000,1000,1100,1010,1100
t0=0,t1=584,1111,1010,1000,0010,1001,0010,0000,0000,0000,0011,1100,0110,1000
t0=0,t1=585,1111,1010,1000,0010,1001,0010,0100,0000,0000,1110,0000,1010,1000
t0=0,t1=586,1111,1010,1000,0010,1001,0010,1000,0000,0000,1001,0101,1100,1001
t0=0,t1=587,1111,1010,1000,0010,1001,0010,1100,0000,0000,0100,1001,0000,1001
t0=0,t1=588,1111,1010,1000,0010,1001,0011,0000,0000,0000,0111,1111,0000,1011
t0=0,t1=589,1111,1010,1000,0010,1001,0011,0100,0000,0000,1010,0011,1100,1011
t0=0,t1=590,1111,1010,1000,0010,1001,0011,1000,0000,0000,1101,0110,1010,1010
t0=0,t1=591,1111,1010,1000,0010,1001,0011,1100,0000,0000,0000,1010,0110,1010
t0=0,t1=592,1111,1010,1000,0010,1001,0100,0000,0000,0000,1010,0111,0000,0011
t0=0,t1=593,1111,1010,1000,0010,1001,0100,0100,0000,0000,0111,1011,1100,0011
t0=0,t1=594,1111,1010,1000,0010,1001,0100,1000,0000,0000,0000,1110,1010,0010
t0=0,t1=595,1111,1010,1000,0010,1001,0100,1100,0000,0000,1101,0010,0110,0010
t0=0,t1=596,1111,1010,1000,0010,1001,0101,0000,0000,0000,1110,0100,0110,0000
t0=0,t1=597,1111,1010,1000,0010,1001,0101,0100,0000,0000,0011,1000,1010,0000
t0=0,t1=598,1111,1010,1000,0010,1001,0101,1000,0000,0000,0100,1101,1100,0001
t0=0,t1=599,1111,1010,1000,0010,1001,0101,1100,0000,0000,1001,0001,0000,0001
t0=0,t1=600,1111,1010,1000,0010,1001,0110,0000,0000,0000,0010,0001,1100,0101
t0=0,t1=601,1111,1010,1000,0010,1001,0110,0100,0000,0000,1111,1101,0000,0101
t0=0,t1=602,1111,1010,1000,0010,1001,0110,1000,0000,0000,1000,1000,0110,0100
t0=0,t1=603,1111,1010,1000,0010,1001,0110,1100,0000,0000,0101,0100,1010,0100
t0=0,t1=604,1111,1010,1000,0010,1001,0111,0000,0000,0000,0110,0010,1010,0110
t0=0,t1=605,1111,1010,1000,0010,1001,0111,0100,0000,0000,1011,1110,0110,0110
t0=0,t1=606,1111,1010,1000,0010,1001,0111,1000,0000,0000,1100,1011,0000,0111
t0=0,t1=607,1111,1010,1000,0010,1001,0111,1100,0000,0000,0001,0111,1100,0111
t0=0,t1=608,1111,1010,1000,0010,1001,1000,0000,0000,0000,1000,0001,1111,0100
Step 3 - analyze dataSince the microcontroller used in the transmitter is a quite simple device (4 bit Microcontroller - WindRiver: W541E260), it is likely, that the checksum calculation is a relatively simple operation, as well (e.g. using logical AND, OR, XOR, bitshift, etc.). A
(Chinese) datasheet containing the assembler instruction set reinforces this assumption. Although it's in Chinese, it still contains the english acronyms of the instructions, including the registers they work on and whether they set carry-over or zero flags.
My first attempt in deciphering the checksum was to look at checksum as a whole. I was looking for patterns, e.g. what happens to checksum, if temperature is increased by 1°C:
- Code: Select all | TOGGLE FULL SIZE
t0=0,t1=601,...,1111,1101,0000,0101
t0=0,t1=602,...,1000,1000,0110,0100
t0=0,t1=603,...,0101,0100,1010,0100
However, to me there was no obvious pattern. Therefore, I started to look at the behaviour of individual checksum bits:
As one can see in this example, the bold checksum bits in column csum[2] seem to toggle with the bold data bits in column data[8]. Therefore, it's likely, that this checksum bit depends on the data bit. However there's an irregularity at t=592, as the bit doesn't toggle there. If you encounter such irregularities, don't scrap your assumption immediaely. Instead, try to figure out, what the root cause of the irregularity might be. Upon closer inspection of this row, it becomes clear, that another data bit (which was previously always zero) toggled to one (data[7], bold bit). Therefore, it's likely that the considered checksum bit not only depends on one but several data bits, which are XORd with each other.
From this point on, it makes sense to systematically analyze, which checksum bit depends on which data bit. In case you want to retrace the steps for determining the dependencies, I recommend to look at the following temperature changes:
T=576->577 (toggle in least significant bit for temperature change)
T=577->578 (toggle in 2nd bit for temperature change)
T=579->580
T=583->584
T=591->592
T=607->608
T=575->576
T=639->640
T=767->768 (toggle in most significant bit for temperature change)
Furthermore, plug and unplug the thermometer probes to toggle an additional bit.
After a systematic evaluation, it turns out that each checksum bit depends on approximately 7-9 data bits. In total this makes over 120 individual XOR operation. At this point it's possible to calculate the checksum. However, there are still three issues
- 1) Compared to the checksum transmitted by the sender, several bits of the checksum, which we can calculate are inverted. However, the "error" is constant and doesn't depend on the temperature. As mentioned in the previous post, this is not an issue, since the error is caused by a random initialization by the sender (the random "seed" changes after each power cycle or after pressing the reset button). Therefore eliminating this error, is just a matter of proper initialization.
- 2) There is still an issue regarding the errored bits, as the pattern is different straight after initialization (when the 3rd nibble = 0x7, instead of 0x2). We'll address this problem later.
- 3) Using 120 XOR operations is cumbersome, and it's relatively safe to assume that there is an easier approach to do the calculation.
To address issue 3), I decided to see, if there's a pattern in how the XOR operations are applied. My approach was to make a table for better visualization:
If you check the green column and have a look at the column next to it, it seems, that the XOR bit pattern is shifted "upwards". However this pattern changes at column data[8], bit 3. Therefore it's not a simple bitshift operation. Upon closer inspection, it becomes clear, that the bit pattern changes, whenever bit 3 of csum[0] is "1" and the bit pattern is shifted left ("up" in the picture). This is a behaviour typical for a linear feedback shift register. I decided to verify this assumption, by implementing a shift register in software. The "green" column is the initial bitmask (0x3331). When executing the shift register step by step, one can determine, which bit positions should be fed back. The final result is that the feedback pattern is 0001 0000 0010 0001 (= 0x1021).
To address issue 2, I decided not only to generate the XOR mask for data nibbles data[8]...data[4], but also to generate the XOR mask for nibble data[3]. By also including nibble data[3], issue 2 could be resolved. This means that the checksum not only includes the temperature (contained in nibbles data[4] ... data[8]), but also nibble data[3].
With all this informaiton, it's now relatively easy to write code for generating the checksum:
- iterate over all data bits covered by the checksum
- for each data bit: determine which checksum bits are depending on the current data bit (determined by applying bit mask)
- If the currently considered data bit is "1": XOR all affected checksum bits with "1". If the currently considered data bit is "0", XOR all affected checksum bits with "0".
- Once done with current data bit, shift bit mask for checksum via linear feedback shift register and continue with next data bit
The C code provided in the previous post performs exactly aforementioned steps with some optimizations :)
Regards,
Sebastian