0

Help: Micro-controller + BBQ Thermometer
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

Re: Help: Micro-controller + BBQ Thermometer

by Nibbler on Mon Nov 17, 2014 8:37 pm

orangeshasta wrote:...
Nibble 18-25: ??? TBD
...
So that's where I am. I'm assuming that nibbles 18 through 25 contain some kind of checksum or other error checking mechanism. For the life of me, I can't figure it out. If anyone has any ideas, I'm all ears.
-Bob


I think I figured out, how the checksum in Nibble 18-25 works.
Short background: I know, that the BBQ thermometers have a mechanism to pair a sender and a receiver. This allows using multiple BBQ thermometers in parallel (however you need a dedicated receiver for each sender). The "checksum" provides a mechanism distinguish between senders. I don't know yet, whether it has another purpose beyond that.

The checksum is basically an XOR over the data nibbles 6-17. The XOR pattern is derived from a linear feedback shift register. Here's the code for the shift register and for calculating the actual checksum:
Code: Select all | TOGGLE FULL SIZE
uint16_t shiftreg(uint16_t currentValue) {
    uint8_t msb = (currentValue >> 15) & 1;
    currentValue <<= 1;
    if (msb == 1) {
        // Toggle pattern for feedback bits
        // Toggle, if MSB is 1
        currentValue ^= 0x1021;
    }
    return currentValue;
}

//data = binary representation of nibbles 6 - 17
//e.g. xxxx:xxxx:xxxx:0010:1000:1010:0110:0101:0101:xxxx:xxxx:xxxx:xxxx
//  -> uint32_t data = 0x28a655
uint16_t calculate_checksum(uint32_t data) {
    uint16_t mask = 0x3331; //initial value of linear feedback shift register
    uint16_t csum = 0x0;
    int i = 0;
    for(i = 0; i < 24; ++i) {
        if((data >> i) & 0x01) {
           //data bit at current position is "1"
           //do XOR with mask
          csum ^= mask;
        }
        mask = shiftreg(mask);
    }
    return csum;
}


Whenever the sender is turned on (or you press the reset button), it generates a random 16 bit pattern, which is XORed with the checksum. This random pattern allows to distinguish between multiple senders.

If you calculate the checksum using the algorithm above and do an XOR with the checksum transmitted by the sender, you end up with a random ID, uniquely identifying the sender. This ID is persistent, until you turn off, or reset the sender.

If I have time, I'll post a detailed description, how I figured out the checksum algorithm, tomorrow.

Regards,
Sebastian

Nibbler
 
Posts: 4
Joined: Mon Nov 17, 2014 8:10 pm

Re: Help: Micro-controller + BBQ Thermometer

by Nibbler on Thu Nov 20, 2014 3:19 pm

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 data
In 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
  • power supply
  • temperature

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 data
Since 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:

checksum_bit_dependency.png
checksum_bit_dependency.png (58.95 KiB) Viewed 2057 times

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:
checksum_bits.png
checksum_bits.png (39.02 KiB) Viewed 2057 times

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

Nibbler
 
Posts: 4
Joined: Mon Nov 17, 2014 8:10 pm

Re: Help: Micro-controller + BBQ Thermometer

by todc on Mon Nov 24, 2014 10:47 am

Sebastian-
Brilliant work! Thanks for posting!

Discovered this thread a while back, but finally ordered parts to actually build a "Maverick sniffer" last week.

todc
 
Posts: 6
Joined: Mon Jan 28, 2013 8:47 am

Re: Help: Micro-controller + BBQ Thermometer

by todc on Wed Dec 24, 2014 11:50 pm

For everyone that posted on this thread,
Thanks for all the helpful info!!! Finally got around to getting this project done and it was a LOT easier with the info here.

For my own whims, I wanted
  • To use an Arduino Uno and Ethernet shield
  • Temperature information to be readable from across the room, so I don't even have to open the web browser on my phone
  • Information viewable on a local webpage served by the Arduino, just in case I decide I want to look at the BBQ temperatures on my phone
  • Filter out bad packets using the checksum algorithm. For some reason, there's a lot of 433MHz noise in my neighborhood. Couldn't be my other projects, could it? :-)

Some pictures and code are at https://github.com/btodcox/BBQduino.

todc
 
Posts: 6
Joined: Mon Jan 28, 2013 8:47 am

Re: Help: Micro-controller + BBQ Thermometer

by martinr63 on Fri Mar 20, 2015 11:06 am

Hi,

first thank you all for this information.

I got a maverick ET-33. With the information here, I can receive the temperature from both probes. But the checksum don't work fully.

This is what I got:
Code: Select all | TOGGLE FULL SIZE
MAV AA99956A959A59599A55556625 011110001011001000101011 chk_xor: 8d65 (8d35 50)
MAV AA999559959A59599A6A666916 001010001011001000101011 chk_xor: 8d6e (f80e 7560)
MAV AA999559959A995999A9AAAA16 001010001011101000101010 chk_xor: 8d6e (629e eff0)
MAV AA99955995A569599A6A569915 001010001100011000101011 chk_xor: 8d66 (fcc6 71a0)
MAV AA99955995A559599A99A66915 001010001100001000101011 chk_xor: 8d66 (2006 ad60)
MAV AA999559959AA9599A55555516 001010001011111000101011 chk_xor: 8d6f (8d6f 0)
MAV AA999559959A99599AA6A5A516 001010001011101000101011 chk_xor: 8d6f (51af dcc0)
MAV AA999559959A9959A56566662A 001010001011101000101100 chk_xor: 8d68 (c838 4550)
MAV AA999559959A695A5956556525 001010001011011000110010 chk_xor: 8d65 (9d25 1040)
MAV AA999559959A695A556A695915 001010001011011000110000 chk_xor: 8d67 (fb47 7620)
MAV AA999559959A69559996A56A2A 001010001011011000001010 chk_xor: 8d69 (1119 9c70)
MAV AA99955995A59955995A565629 001010001100101000001010 chk_xor: 8d60 (bc70 3110)
MAV AA99955995A55959AA69569919 001010001100001000101111 chk_xor: 8d62 (ecc2 61a0)
MAV AA999559959AA959A9AAAAAA1A 001010001011111000101110 chk_xor: 8d6a (729a fff0)


until now I can't figure out, what to do with the last nibbles of the checksum sent by the device.

The number in the brackets are calculated checksum and the received checksum without the last 2 nibbles.

Regards
Martin

martinr63
 
Posts: 3
Joined: Fri Mar 20, 2015 10:48 am

Re: Help: Micro-controller + BBQ Thermometer

by todc on Fri Mar 27, 2015 10:48 pm

Martin,
You wrote ET-33, but did you mean an ET-733? I searched Maverick's website, but could not find an ET-33 model.

Aside from that, I see three possible sources of your problem:
[1] The thermometer you are using could have a different checksum calculation. I have only personally verified the models listed on my github page (ET-732 and Ivation model listed).
[2] Your receiver is not functioning as expected. I had issues with a couple of receivers tossing out junk data even when they were only a few feet away from the sending unit. I switched to a superheterodyne receiver to solve this problem.
[3] Something is wrong with your code. I have a test unit that has been continuously receiving live data from a 732 sender for over 2 months. Aside from the occasional bad packet, the system is finding the correct checksum on virtually all packets.

I would hazard a guess that [2] or [3] is the issue: The only valid nibble values are A, 9, 6, and 5 for a 732 and guessing that the second column is the data your code has received, I see 1's and 2's present.

todc
 
Posts: 6
Joined: Mon Jan 28, 2013 8:47 am

Re: Help: Micro-controller + BBQ Thermometer

by martinr63 on Tue Mar 31, 2015 12:19 pm

Hi,

you're right, I mean an ET-733, sorry for this. The second column is every time 1 or 2. I think they did this, so it is not possible to pair the ET-732 with an ET-733.
I did a little bit of calculating and figured out how to calculate the checksum. The first 3 nibble are like the ET-732. The last nibble is nibble 24 and 25, in reverse order, the LSB is ignored

Code: Select all | TOGGLE FULL SIZE
    chksum_sent = (uint16_t) quart(_str[18] ) << 14;
    chksum_sent |= (uint16_t) quart(_str[19]) << 12;
    chksum_sent |= (uint16_t) quart(_str[20]) << 10;
    chksum_sent |= (uint16_t) quart(_str[21]) << 8;
    chksum_sent |= (uint16_t) quart(_str[22]) << 6;
    chksum_sent |= (uint16_t) quart(_str[23]) << 4;

    if(_str[24]=='1' || _str[24]=='2')
    {
        chksum_sent |= (uint16_t) ((quart(_str[25]))&1)<<3;
        chksum_sent |= (uint16_t) ((quart(_str[25]))&2)<<1;

        if(_str[24]=='1')
            chksum_sent |= 0x02;
    }
    else
    {
        chksum_sent |= (uint16_t) quart(_str[24]) << 2;
        chksum_sent |= (uint16_t) quart(_str[25]);
    }

  chk_xor = (chksum_data & 0xfffe) ^ chksum_sent;



You can find my code for the Raspberry Pi on my GitHub

Regards
Martin

martinr63
 
Posts: 3
Joined: Fri Mar 20, 2015 10:48 am

Re: Help: Micro-controller + BBQ Thermometer

by orangeshasta on Tue Mar 31, 2015 9:08 pm

Wow, I haven't checked on this thread in a while but it looks like it's been active! I actually just *finally* got around to writing up my process for decoding the protocol over at hackaday.io, and was going to drop by here with a link for anyone who was interested: http://hackaday.io/project/4690-reverse ... ick-et-732

So it looks like Sebastian figured out the checksum - really nice work, and a great writeup! I'm going to give it a thorough read and possibly give my code an update; I've been meaning to dig back into it.

Also, I made a separate project on hackaday.io that describes what I actually DID with the data (though in much less detail than the one about the protocol):
http://hackaday.io/project/4538-interne ... hermometer

orangeshasta
 
Posts: 4
Joined: Fri Aug 10, 2012 4:49 pm

Re: Help: Micro-controller + BBQ Thermometer

by todc on Wed Apr 01, 2015 4:31 pm

orangeshasta/Bob,
Any chance you could post your code for creating nice webpage graphs of your smokes to your github page?

Thanks,
Tod

todc
 
Posts: 6
Joined: Mon Jan 28, 2013 8:47 am

Re: Help: Micro-controller + BBQ Thermometer

by orangeshasta on Thu Apr 02, 2015 11:26 pm

Hi Tod,

My website code isn't in a state where I'm ready to post it on github quite yet (though I might in the future). I can give you a high-level overview though:

Basically, my PIC microcontroller sends csv temperature and time data to a PHP script on my site. That script validates and parses the data, and inserts it into a MySQL database. The main page of the site loads all of the data from a session in the database and passes it, JSON formatted, to the graph. For my graphs, I use a javascript library called Flot - they've got some great examples on their site which show how to use it.

I'm happy to answer any specific questions you might have, too!

-Bob

orangeshasta
 
Posts: 4
Joined: Fri Aug 10, 2012 4:49 pm

Re: Help: Micro-controller + BBQ Thermometer

by BjoernS on Sun Aug 16, 2015 10:09 am

Hello everyone,
just to add something to this Thread:
I used your examples and code to write a receiver Program on my Raspberry Pi (using Python and pigpio).
Wonderful work! It was very very helpful.
But I had some Issues:
My ET-733 doesn´t transmit the "1" or "2" in the 24. nibble.
The ET-732 code seems to work, but I don´t have to ignore the last Bit of the calculated checksum or else the result is not always the same.
I will check with the ET-732 if I get one.

The Back of the Transmitter says:
MAVERICK MAV221
Model# ET-733

Maybe this will help someone with the same problems.

BjoernS
 
Posts: 2
Joined: Sun Aug 16, 2015 9:53 am

Re: Help: Micro-controller + BBQ Thermometer

by Nibbler on Mon Sep 07, 2015 5:56 pm

todc wrote:Martin,
You wrote ET-33, but did you mean an ET-733? I searched Maverick's website, but could not find an ET-33 model.
...
[2] Your receiver is not functioning as expected. I had issues with a couple of receivers tossing out junk data even when they were only a few feet away from the sending unit. I switched to a superheterodyne receiver to solve this problem.
...


Hello,
I had a similar problem with one of my ET-732 transmitters. It seems, that they rely on cheap electronics. This results in poor ManchesterEncoding, as depicted below. The duration of short pulses varies a lot depending, whether it's a "short low" or "short high". Same applies to the long pulses.

bad_transmitter.png
bad_transmitter.png (18.12 KiB) Viewed 1576 times


To address this issue, I decided to write a more robust ManchesterDecoder. It takes advantage of the fact, that the transmission always starts with a well known bit pattern (AA, ...). In short it works like this: whenever a transmission begins, the decoder measures the duration of long and short pulses (seperately for high and low). We know what a long/short high/low pulse is, because of the well known bit pattern. After the well known bit pattern is received, the code averages the measured values and uses them to detect short/long high/low pulses, when the actual data is transmitted.

The code below is an excerpt of a working Linux kernel module implementation. I know it's quite ugly, uses too many state variables and what not. There is also some Linux kernel specific code in it, which I didn't strip. Still, it should give you a general idea on how to implement a more robust decoder for the ET-732. Let me know if it works for the ET-733, too :)

Code: Select all | TOGGLE FULL SIZE
/*
 * Copyright (C) 2014, 2015 Sebastian Meier
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FIT-
 * NESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.

 *
 * Module for receiving and decoding of wireless Maverick ET-732
 * sensor data (433MHz).

 * The decoder routine is partially based on code of the Arduino
 * decoder library "433MHzForArduino" for decoding the sensor data,
 * see https://bitbucket.org/fuzzillogic/433mhzforarduino
 *
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/ktime.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/slab.h>
#include <linux/kfifo.h>
#include "maverick_interrupthandler.h"
#include "maverick_netlink.h"
#include "../maverick_common/maverick_common.h"



//manchester_decoder variables
static ktime_t *ts;            // timestamp for current execution
static ktime_t *lastChange;         // timestamp of last execution

static uint8_t  half_bit_position = 1;    
static bool isOne;
static uint8_t  data[MAVERICK_MAXDATA_LEN];


unsigned int start_pulse_counter = 0;
unsigned int detection_state = STATE_AGC;


uint16_t short_high_duration = 0;
uint16_t short_low_duration = 0;
uint16_t long_high_duration = 0;
uint16_t long_low_duration = 0;
uint16_t low_duration_threshold = 0;
uint16_t high_duration_threshold = 0;

uint8_t debug_sh_cnt = 0;
uint8_t debug_sl_cnt = 0;
uint8_t debug_lh_cnt = 0;
uint8_t debug_ll_cnt = 0;


bool high_level = true;


//GPIO & IRQ related
short int cresta_gpio_irq = 0;   // interrupt we're assigned to


struct kfifo_rec_ptr_1 irqtime_kfifo;
struct kfifo_rec_ptr_1 rawdata_kfifo;

static struct workqueue_struct *cresta_workqueue;
struct cresta_work *netlink_work;
struct cresta_work *manchester_work;

#define MIN_EXPECTED_TRAINING_SHORTPULSE 100
#define MAX_EXPECTED_TRAINING_SHORTPULSE 400
#define MIN_EXPECTED_TRAINING_LONGPULSE 4000
#define MAX_EXPECTED_TRAINING_LONGPULSE 6000


bool expecting_short_edge = true;
bool ok_to_decode_next_bit = false;
/*
 * bottom half interrupt tasklet
 * reads time of IRQ and triggers manchester_decoding
 */

void cresta_irq_bh(struct work_struct* work) {
 
 //get time of IRQ from FIFO
  while(kfifo_out(&irqtime_kfifo, ts, sizeof(ktime_t))) {
    //calculate duration (time between last irq and current irq)
    //note: will be incorrect for very first execution, as lastChange = 0
    //but that's no problem for our scenario
    maverick_manchester_decoder((uint32_t) ktime_us_delta(*ts, *lastChange));
    *lastChange = *ts;
  }
}



void reset_manchester_decoder() {
  detection_state = STATE_AGC;
  start_pulse_counter = 0;
  half_bit_position = 1;       //we cannot distinguish the "real" first half bit from the last ~5ms low period of the automatic gain control. hence, we set this manually to one
  ok_to_decode_next_bit = false;   //when transitioning from training towards decoding (data[1] -> data[2]) we cannot immediately give green light to decoding, as we've only seen one short pulse so far
  expecting_short_edge = true;      //because byte[2] ends with short pulse, and we start verifying with byte[3], which starts with a short pulse, we need to initialize to true
 
  short_high_duration = 0;
  short_low_duration = 0;
  long_high_duration = 0;
  long_low_duration = 0;
  high_level = true;
  low_duration_threshold = 0;
  high_duration_threshold = 0; 
 
  debug_sh_cnt = 0;
  debug_sl_cnt = 0;
  debug_lh_cnt = 0;
  debug_ll_cnt = 0;
  isOne = true;


/*
 * A short note, on how the decoder works:
 *  - at beginning of each transmission
 *    - sender sends short "high" pulses (around 180 - 240 us) long,
 *    - sender sends long "low" pulses (around 5 ms) long.
 *    - short and long pulses are interleaved (there seem to be 8 short and 9 long pulses)
 *    - the duration of the short pulses don't seem to coincide with the clock used for bit transmission (it does sometimes, but not always)
 *    - educated guess: purpose of this sequence is to give automatic gain control (AGC) some time to adjust to transmission
 *    - we don't use this preamble to do any clock recovery
 *
 *  - after agc: first data byte of transmission (8 bit/byte, MSB first) well known value = 0xAA
 *    - in manchester_encoding the resulting pulse sequence, is "long high", "long low", "long high", ...
 *    - we use this information to learn, how long a "long low" and a "long high" is.
 *      Typically, those values should be identical. However, we observed that some of the Maverick TX units are of very poor quality,
 *      resulting in significant differences for high and low values (e.g. short low = 334us, short high = 161us - same for long low/high)
 *      Therefore, we need a more robust decoding, which takes the asymmetry into account.
 *  - after first byte of transmission, we know how long a "long high" and "long low" is.
 *    - we now receive 2nd byte with well known value 0x99 to train short pulses
 *    - for now, we assume that a "short high" < 0.75 * "long high", and "short low" < 0.75 * "long low"
 *    - whenever we observe pulses in 2nd byte, which fullfill these criteria, we store their actual duration
 *    - after 2nd byte is completely received, we calculate the average of the observed durations for short high/low pulses (individually for low and high)
 *    - now we calculate a threshold individually for high and low, to distinguish between long and short pulses. formula: thresh = t_short + (t_long - t_short)/2
 *   - after receiving 2nd byte, we have an ide about how bit times should look like. we now start the actual decoding of
 *     the data that follows. we know that 3rd byte has well known value 0x95
 *     we can use this knowledge to see, whether our current parametrization decodes the same value.
 *
 */
void maverick_manchester_decoder (uint32_t duration) {
    if((detection_state != STATE_AGC) && (duration > MIN_EXPECTED_TRAINING_LONGPULSE) && (duration < MAX_EXPECTED_TRAINING_LONGPULSE)) {
      //printk(KERN_INFO "resetting, as got long pulse (%d) while not in AGC\n", duration);
      reset_manchester_decoder();
    }
 
    // if there is no start condition yet detected (8 pulses, spaced 5,2ms)
    if (detection_state == STATE_AGC) {
      if ((duration > MIN_EXPECTED_TRAINING_SHORTPULSE) && (duration < MAX_EXPECTED_TRAINING_SHORTPULSE)) {
   //start_pulse_counter++;
      } else if ((duration > MIN_EXPECTED_TRAINING_LONGPULSE) && (duration < MAX_EXPECTED_TRAINING_LONGPULSE)) {
   start_pulse_counter++;
   if (start_pulse_counter==9) {
     high_level = false;
      //printk(KERN_INFO "got 8 start pulses\n");
     detection_state = STATE_TRAINING;
   }
      } else {
   //printk(KERN_INFO "in agc. resetting as pulse length invalid\n");
   reset_manchester_decoder();   
      }

    } else if (detection_state == STATE_TRAINING) {
      uint8_t currentByte = (half_bit_position >> 1) / 8;
      uint8_t currentBit =  (half_bit_position >> 1) % 8; // eight bits in a byte.

      if(currentByte == 0) {
   //1st of 3 training bytes
   if(high_level) {
     //printk(KERN_INFO "long_high_count = %d\n", ++debug_lh_cnt);
     long_high_duration += duration;
   } else {
     //even bits
     //printk(KERN_INFO "long_low_count = %d\n", ++debug_ll_cnt);
     long_low_duration += duration;
   }
   if(currentBit == 7) {
     //got 4 long high and 4 long low pulses now
     long_low_duration >>= 2;
     long_high_duration >>= 2;
     //printk(KERN_INFO "long_low_duration = %d\n", long_low_duration);
     //printk(KERN_INFO "long_high_duration = %d\n", long_high_duration);
   }
   //printk(KERN_INFO "current byte 0, half_bit_position = %d\n", half_bit_position);
   half_bit_position += 2;
   
      } else if (currentByte == 1) {
   //printk(KERN_INFO "currentByte = 1\n");
   //we use byte1 and 2 to train short pulses
   //assumption is, that a short pulse is smaller than 0.75 *long pulse
   if(high_level) {
     if(duration < ((long_high_duration >> 1) + (long_high_duration >> 2))) {
       //printk(KERN_INFO "short_high_count = %d\n", ++debug_sh_cnt);
       short_high_duration += duration;
       half_bit_position +=1;
       //printk(KERN_INFO "high short\n");
     } else {
       //printk(KERN_INFO "high long\n");
       half_bit_position +=2;
     }

   } else {
     if(duration < ((long_low_duration >> 1) + (long_low_duration >> 2))) {
       //printk(KERN_INFO "short_low_count = %d\n", ++debug_sl_cnt);
       short_low_duration += duration;
       half_bit_position +=1;
       //printk(KERN_INFO "low short\n");
     } else {
       //printk(KERN_INFO "low long\n");
       half_bit_position +=2;
     }
   }
   if(currentBit == 7) {
     //we should have 5 short high and 5 short low pulses by now
     short_high_duration /=4;
     short_low_duration /=3;
     //printk(KERN_INFO "short_low_duration = %d\n", short_low_duration);
     //printk(KERN_INFO "short_high_duration = %d\n", short_high_duration);
    
     low_duration_threshold = short_low_duration + ((long_low_duration - short_low_duration) / 2);
     high_duration_threshold = short_high_duration + ((long_high_duration - short_high_duration) / 2);
    
     //printk(KERN_INFO "high duration threshold = %d\n", high_duration_threshold);
     //printk(KERN_INFO "low  duration threshold = %d\n", low_duration_threshold);
     //printk(KERN_INFO "activating data decoding\n");
     detection_state = STATE_DATA;
   }
      }
     
     
   
    } else if (detection_state == STATE_DATA) {
      //printk(KERN_INFO "in STATE_DATA\n");
      //is edge duration < 0.5 * short_edge or > 2*long edge?
      if(high_level && (duration < (short_high_duration >> 1) || duration > (long_high_duration << 1))) {
   //printk(KERN_INFO "edge too long or too short, resetting\n");
   reset_manchester_decoder();
   goto err;
      }
      if(!high_level && (duration < (short_low_duration >> 1) || duration > (long_low_duration << 1))) {
   //printk(KERN_INFO "edge too long or too short, resetting\n");
   reset_manchester_decoder();
   goto err;
      }
     

   

   if((high_level && duration < high_duration_threshold) || (!high_level && duration < low_duration_threshold)) {
     //printk(KERN_INFO "short edge\n");
     //short edge
     if (expecting_short_edge == false) {
       expecting_short_edge = true;   // set this flag...next bit HAS TO BE a short bit too
       ok_to_decode_next_bit = false;
     } else {
       expecting_short_edge = false;   // 2 short bits have been received -> clear flag, everything ok         
       ok_to_decode_next_bit = true;
     }
     half_bit_position += 1;
    
   } else {
     //long edge
     //printk(KERN_INFO "long edge\n");
     if (expecting_short_edge == true) {
       // start all over...this had to be a short bit but is a long bit
       //printk("expected another short edge, but got long, resetting\n");
       reset_manchester_decoder();
       goto err;
     } else {
       ok_to_decode_next_bit = true;
       isOne = !isOne;
       half_bit_position += 2;
     }
   }
   
   if(ok_to_decode_next_bit) {
   
     uint8_t currentByte = (half_bit_position >> 1) / 8;
     uint8_t currentBit =  (half_bit_position >> 1) % 8; // eight bits in a byte.
     //printk(KERN_INFO "decoding next bit: %d. half_bit = %d, currentByte = %d, currentBit = %d\n", isOne, half_bit_position, currentByte, currentBit);
       //make sure we don't write out of array
     if(currentByte < MAVERICK_MAXDATA_LEN) {
      
       //LSB first decoding
       /*
       if (isOne) {
         // Set current bit of current byte
         data[currentByte] |= 1 << currentBit;
       } else {
         // Reset current bit of current byte
         data[currentByte] &= ~(1 << currentBit);
       }
       */
       //MSB first decoding
       data[currentByte] = (data[currentByte] << 1) + isOne;
       //printk (KERN_INFO "current_byte = %d, isOne = %d, data[current_byte] = %x\n", currentByte, isOne, data[currentByte]);
     }
     if(currentByte == 2 && currentBit == 7) {
         if(data[currentByte] != 0x95) {
       //printk(KERN_INFO "data[%d] != %d, resetting.\n", currentByte, 0x95);
       reset_manchester_decoder();
         }
     }
    
     if(currentByte == 12 && currentBit == 7) {
       //add first two byte manually, as they're always the same
       data[0] = 0xAA;
       data[1] = 0x99;
       //printk(KERN_INFO "MSB: %x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x:%x\n", data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11], data[12]);
       //decode_debug_new(data);
      
       //printk(KERN_INFO "sending netlink message");
       //maverick_send_notification();
      
       kfifo_in(&rawdata_kfifo, data, sizeof(data));
       queue_work(cresta_workqueue, &netlink_work->ws);
       reset_manchester_decoder();
     }
     /*
     if(currentBit == 7) {
       printk(KERN_INFO "data[%d] = %x\n", currentByte, data[currentByte]);
       if(currentByte == 12) {
         printk(KERN_INFO "transmission finished, resetting\n");
         reset_manchester_decoder();
       }
     }*/
    
   }

    }

   
   
    high_level = !high_level;
err:
    return;
}


/**
 * top half of the cresta IRQ irq handler
 */
static irqreturn_t cresta_irq_th(int irq, void *dev_id, struct pt_regs *regs) {
  //NOTE: since 2.6.35 IRQs are disabled by default while in an ISR
  ktime_t now = ktime_get();
  kfifo_in(&irqtime_kfifo, &now, sizeof(now));
  queue_work(cresta_workqueue, &manchester_work->ws);

  return IRQ_HANDLED;
}


 
 
/*
 * Sets up the GPIO pin for interrupt handling
 * NOTE: use new gpiod API once Kernel 3.13 is available, see
 * https://www.kernel.org/doc/Documentation/gpio/consumer.txt
 */
int setup_interrupt(void) {
 
   if (gpio_request(CRESTA_GPIO, CRESTA_GPIO_DESC)) {
      printk(KERN_ERR "GPIO request faiure: %s\n", CRESTA_GPIO_DESC);
      return -1;
   }
   
   if (gpio_direction_input(CRESTA_GPIO)) {
     printk(KERN_ERR "Failed to set GPIO as input\n");
     return -1;
   }
 
   if ( (cresta_gpio_irq = gpio_to_irq(CRESTA_GPIO)) < 0 ) {
      printk(KERN_ERR "GPIO to IRQ mapping faiure %s\n", CRESTA_GPIO_DESC);
      return -1;
   }
 
   printk(KERN_INFO "Mapped int %d\n", cresta_gpio_irq);
 
   //NOTE: CRESTA_GPIO_DEVICE_DESC is usually a dev_t
   //strictly we don't need this parameter, however, setting it
   //to NULL (and also to NULL in free_irq) resulted in kernel
   //panic when doing the following:
   //insmod cresta.ko -> rmmod cresta -> insmod cresta.ko -> panic
   if (request_irq(cresta_gpio_irq,
                   (irq_handler_t ) cresta_irq_th,
                   IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
                   CRESTA_GPIO_DESC,
                   CRESTA_GPIO_DEVICE_DESC)) {
      printk(KERN_ERR "IRQ request failure\n");
      return -1;
   }
 
   return 0;
}
 
 
/*
 * Releases GPIO and Interrupt
 */
void release_interrupt(void) {
   disable_irq(cresta_gpio_irq); //wait for ISR to complete
   free_irq(cresta_gpio_irq, CRESTA_GPIO_DEVICE_DESC);
   gpio_free(CRESTA_GPIO);
   enable_irq(cresta_gpio_irq);
 
   return;
}
 
 
/*
 * Module initialization
 */
int __init maverick_interrupthandler_init(void) {
  printk(KERN_NOTICE "Loading Maverick Module.\n");
 
  //initialize netlink handling
  maverick_netlink_init();

  if(kfifo_alloc(&irqtime_kfifo, CRESTA_KFIFO_SIZE, GFP_KERNEL)) {
    printk(KERN_ERR "Error, couldn't allocate memory for FIFO buffer\n");
    goto err;
  }

  if(kfifo_alloc(&rawdata_kfifo, CRESTA_KFIFO_SIZE, GFP_KERNEL)) {
    printk(KERN_ERR "Error, couldn't allocate memory for FIFO buffer\n");
    goto err;
  }

 
  ts         = kzalloc(sizeof(ktime_t), GFP_KERNEL);
  lastChange = kzalloc(sizeof(ktime_t), GFP_KERNEL);
 

  if(NULL == ts || NULL == lastChange) {
    printk(KERN_NOTICE "Error, couldn't allocate memory for timepecs\n");
    goto err;
  }
 
 
  cresta_workqueue = alloc_workqueue(CRESTA_GPIO_DEVICE_DESC, WQ_NON_REENTRANT, 1);
  if (NULL == cresta_workqueue) {
    goto err;
  }
 
 
  netlink_work     = kmalloc(sizeof(struct cresta_work), GFP_KERNEL);
  manchester_work = kmalloc(sizeof(struct cresta_work), GFP_KERNEL);

  if(NULL == netlink_work || NULL == manchester_work) {
    goto err;
  }
 
  INIT_WORK(&netlink_work->ws, send_netlink_message);
  INIT_WORK(&manchester_work->ws, cresta_irq_bh);


 
  if(setup_interrupt()) {
    goto err;
  }


   return 0;
 
err:
   kfree(ts);
   kfree(lastChange);
   kfifo_free(&irqtime_kfifo);
   kfifo_free(&rawdata_kfifo);
   if(NULL != cresta_workqueue) {
     destroy_workqueue(cresta_workqueue);
   }
   kfree(netlink_work);
   kfree(manchester_work);
   return -1;
}

/*
 * Module cleanup
 */
void __exit maverick_interrupthandler_cleanup(void) {
   release_interrupt();
   kfree(ts);
   kfree(lastChange);
   
   //cleanup work queue
   flush_workqueue(cresta_workqueue);
   destroy_workqueue(cresta_workqueue);
   
   kfree(netlink_work);
   kfree(manchester_work);
   
   
   maverick_netlink_cleanup();   

   //free memory of fifos
   kfifo_free(&irqtime_kfifo);
   kfifo_free(&rawdata_kfifo);

   printk(KERN_NOTICE "Removed Maverick Module.\n");
   return;
}
 
 
module_init(maverick_interrupthandler_init);
module_exit(maverick_interrupthandler_cleanup);
MODULE_LICENSE("GPL");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);



Best Regards,
Sebastian

Nibbler
 
Posts: 4
Joined: Mon Nov 17, 2014 8:10 pm

Re: Help: Micro-controller + BBQ Thermometer

by BjoernS on Mon Dec 28, 2015 9:26 am

Did this a while ago:
https://github.com/BjoernSch/MaverickBBQ

I also found that the Transmitter from the ET-732 and the ET-733 are identical and interchangeable, at least for the Models sold in Germany.

BjoernS
 
Posts: 2
Joined: Sun Aug 16, 2015 9:53 am

Re: Help: Micro-controller + BBQ Thermometer

by martinr63 on Sun Dec 04, 2016 11:34 am

I was not able to receive my ET733 any longer. I saw the long high pulses was shorter, then the short low pulses. So I tried the code from BjoernS without luck. Then based on the idea from Nibbler, I changed the pythoncode from Bjoern from a fixed offset to calculating the pulselengh based on the header AA99
You can find the result on github

martinr63
 
Posts: 3
Joined: Fri Mar 20, 2015 10:48 am

Please be positive and constructive with your questions and comments.