This project’s scope is open ended. The initial scope is to have a temperature sensor in each room, capture that data and push it to a server where I’ll have a webpage displaying a blueprint of my house and the temperature and humidity in each room, giving a visual representation of the balance of temperature throughout the house. Extra icing will be some controls to set the desired level of each room and use the color of the background for that room to show the relative difference. Maybe some raindrops or similar to show the same for the actual humidity vs the desired humidity. Long term, I may install servo controlled dampers in the duct work to balance the temperature, or even replace my Nest with my own controller.
I originally conceived this as building the remote sensors and sending the data from them via WiFI but quickly determined that between the cost of the processor board and wireless card, it would be cost prohibitive. I searched the web for others who had attempted the same and came across those who simply decoded existing off the shelf sensors used with home weather stations. I settled on the Ambient sensor because I found an Ambient weather station on Amazon for $120 that had 8 sensors and also, there was a ready made solution of code for the Arduino of which I had an emergency backup sitting around (i.e. a solution in search of a problem). I purchased the Arduino when I saw it on sale for $10.I later found that Ambient sells the sensors alone for $10 each.
I began by starting 2 sensors and downloading the Arduino sketch here. These good folks have worked out decoding the RF signal and pushing it through an Arduino WiFi shield to Xivey. I plan to use a cheaper Ethernet shield since I can set this receiver up near a Ethernet drop, so I stripped their code down to eliminate the server and WiFi code and for now, just print it to the serial port.
It appears, that no one has cracked what kind of checksum or CRC Ambient uses. The current sketch attempts to filter bad readings out by discarding large changes over short periods of time. I wasn’t happy with that and so set out to reverse engineer the checksum or CRC.
Reverse Engineering the Ambient F007TH Sensor Checksum
I captured a handful of samples. As the forum and code/sketch show, the message is 5 bytes and a error checking byte. I quickly confirmed it was not a simple checksum. Nor was it a conventional CRC. I tried several CRC8 implementations and even tried brute force checking every starting combination of polynomial and CRC value and none worked on more than 1 message. I found an excellent site that allowed you to vary the parameters of a CRC8 calulation but no amount of twiddling would produce the results I was seeing (although I didn’t brute force every combination of polynomial). While I studied it, the Arduino captured data. So I decided to look at the effect of 1 bit changes on the checksum(I’ll call it checksum from here out since it is not a CRC).
I took 13,000 samples of mesages that I had and dumped them into Excel. There I sorted them to get identical messages together. Then I wrote a quick VBA macro to eliminate duplicates and add a count value to show how many of each duplicate existed. I then filtered out those with less than 3 duplicates to ensure I was not working with any messages with bit errors.
I first looked at the last bit of the messages. Excel filtering made it easy to filter message that were the same for the first 4 bytes and then select sets that differed only by the lsb of the last data byte (e.g. 0->1, 2->3, …). I then looked at the checksum for each message and compared them for each set. It turned out that they varied only by bit 4 (0x10). I repeated this for the next bit of the last data byte. It differed only by 1 bit as well – bit 5 (0x20). I got excited as there seemed to be the beginnings of a simple pattern. I continued for bits 2 and 3. Unfortunately I had no data for bits 4-7 of this byte because this was humidity and my range of humidity data was limited. So I went on to the next to last byte starting with the lsb again. This time I had data for all 8 bits (this was temperature in 1/10 degrees F).
Immediately I could see that the pattern had changed. it was no longer 1 bit, although it did seem to still shift. The tables below show the data I collected as well as the values I extrapolated after deducing the algorithm. Looking at the table from the temperature byte, it looked like a rotate by 1 bit – almost. Further examination revealed that whenever a 1 rotated around from lsb to msb (or msb to lsb depending on which direction you are progressing up or down the table) the middle 2 bits (bits 3 and 4) flipped. This was the key to deciphering the checksum. You can see extrapolated values for bits 4-7 of the humidity and bit 7 of the temperature. Also you can see the projected pattern for bit -1 of the temperature, which would be the msb of the humidity and that they line up.
I created an algorithm that generated this sequence of masks in reverse order (from the lsb of the last byte to the msb of the first byte). I then took and existing message and its checksum and progressed from the lsb of the message to the msb of the message. For each 1 in the message, I XORed the mask against the current checksum (starting with the received checksum). This gave me the initial value to use for generating checksums for these 5 byte examples. And the sequence of mask values yielded the starting value for a 5 byte sequence. Checking a 2nd sample yielded the same initial checksum value. Even better, now reversing the algorithms to generate the sequence of masks from the msb of the first byte and reversing the algorithm to start with the msb of the first data byte generated a checksum that matched any sample in my list! The problem is solved!
Next I’ll move on to adding the Ethernet shield and related code and a way to push the data to a server.
Table of Changing Bits in Checksum
| HHHHHHHH | CCCCCCCC | |
| 0 | 10 | 0001 0000 |
| 1 | 20 | 0010 0000 |
| 2 | 40 | 0100 0000 |
| 3 | 80 | 1000 0000 |
| 4 | 31 | 0011 0001 |
| 5 | 62 | 0110 0010 |
| 6 | C4 | 1100 0100 |
| 7 | B9 | 1011 1001 |
Table of Changing Bits in Checksum
| TTTTTTTT
BIT |
HHHHHHHH | CCCCCCCC
Checksum |
||
| B9 | 1011 1001 | |||
| 0 | X | 43 | 0100 0011 | |
| 1 | X | 86 | 1000 0110 | |
| 2 | X | 3D | 0011 1101 | |
| 3 | X | 7A | 0111 1010 | |
| 4 | X | F4 | 1111 0100 | |
| 5 | X | D9 | 1101 1001 | |
| 6 | X | 83 | 1000 0011 | |
| 7 | X | 37 | 0011 0111 | |
| 6E | 0110 1110 |
Sample serial port output
(the first byte FD is a flag and ignored in the checksum)
FD 45 E8 14 65 22 CA Channel=2 F=72.5 H=34%
FD 45 E8 14 65 22 CA Channel=2 F=72.5 H=34%
FD 45 E8 14 65 22 CA Channel=2 F=72.5 H=34%
FD 45 E8 14 65 22 CA Channel=2 F=72.5 H=34%
FD 45 E8 14 65 22 CA Channel=2 F=72.5 H=34%
Bad msg: 8A 22 08 8C 4C 5E
Computed Checksum=F2 Actual Checksum=5E
FD 45 E8 14 65 22 CA Channel=2 F=72.5 H=34%
FD 45 E8 14 65 22 CA Channel=2 F=72.5 H=34%
FD 45 E8 14 64 22 89 Channel=2 F=72.4 H=34%
FD 45 E8 14 64 22 89 Channel=2 F=72.4 H=34%
FD 45 E8 14 64 22 89 Channel=2 F=72.4 H=34
Checksum code
uint8_t Checksum(int length, uint8_t *buff)
{
uint8_t mask = 0x7C;
uint8_t checksum = 0x64;
uint8_t data;
int byteCnt;
for ( byteCnt=0; byteCnt < length; byteCnt++)
{
int bitCnt;
data = buff[byteCnt];
for ( bitCnt= 7; bitCnt >= 0 ; bitCnt-- )
{
uint8_t bit;
// Rotate mask right
bit = mask & 1;
mask = (mask >> 1 ) | (mask << 7);
if ( bit )
{
mask ^= 0x18;
}
// XOR mask into checksum if data bit is 1
if ( data & 0x80 )
{
checksum ^= mask;
}
data <<= 1;
}
}
return checksum;
}
Jonathan Herr
January 31, 2015
There’s an error in the “Table of Changing Bits in Checksum” table. I don’t believe that one of the bits should be a ‘2’…
eclecticmusingsofachaoticmind
February 1, 2015
Good catch. I fat fingered an entry. The position is correct, but of course all bits should be 0 or 1, not a 2. Thanks for pointing it out. I’ve corrected that.
rewolff
February 1, 2015
Your algorithm/code seems very, very much to be a CRC. Check out “linear feedback shift registers” (LFSR).
eclecticmusingsofachaoticmind
February 1, 2015
It definitely seems to be similar to a CRC. The main difference I see is that a shift register implementation of a CRC has only 1 shift register. My implementation of the solution has a shift register and “accumulator”. The shift register is much like a standard CRC. But the CRC register is not what is transmitted. What is transmitted is the accumulated results of the “CRC” register with an accumulator which is built up by XORing in the mask whenever the incoming data bit is one. This may can be simplified down into a standard CRC shift register implementation, but if so, I am missing how to do so.
Wes Rishel
February 1, 2015
Great detective work and very useful info. Thanks! I particularly appreciate you including your approach as well as the conclusion.
One very small possible improvement. The C code has embedded HTML named-character references for the less than sign and ampersand. So, for example “mask << 1" appears as "mask >> 1".
It appears that this is because WordPress is using Alex Gorbatchev's "Brush:cpp" class definition and the special characters such as '’ should not be protected within its range,
OTOH, anyone that knows enough to read the sketch can probably crack the code, so it’s not a big deal.
eclecticmusingsofachaoticmind
February 1, 2015
Thanks for the kind words.
Someone else pointed out the & issue on another channel and I thought I fixed it then. I just noticed (probably about the time you posted) that they were “back”. Hopefully I fixed them right this time!
Gus Mueller
March 16, 2015
Thanks so much for your work on this project. It was great to be able to upload your code to an Atmega328 and get valid readings from all my AmbientWeather probes accurately and immediately. It’s looking like building my own open source weather station is going to be a lot easier than initially feared. I’ll just have to see if my plan to measure windspeed and direction using an array of barometers is really going to work.
eclecticmusingsofachaoticmind
March 17, 2015
Glad it helped. Watch this space for the next stage of a web app to show the whole house (but it will be a while) and then hopefully the final stage of fine tuning room temperatures through servo controlled dampers on the ducts.
A Weather Guy
May 7, 2015
Thanks so much for posting this and great work BTW!
The “mask” variable implements a LFSR which always runs out the same sequence, so you can speed the algorithm if you pre-compute the first “n” values of the sequence, then just get the “nth” value from the table according to which bits are turned on in the message. Since there are only 40 bits in the message it would cost you 40 bytes of storage.
It’s been a while since I dabbled in this (Galois fields, etc) but I think that the operation where you examine the data bit and then add in the current sequence value is equivalent to a multiplication operation. If that’s correct then this is a non-linear algorithm…and makes this a little more like a cryptographic hash than a CRC or checksum.
P.S. If you’re looking for a job, I think the NSA is hiring… 😉
A Weather Guy
May 7, 2015
I did a bit of searching and I think this “checksum” is an “LSFR-based Toeplitz hash”. See this paper for details. Not sure I’ve got this right but it seems to fit. Ambient Weather must have some interesting folks on the engineering staff!
eclecticmusingsofachaoticmind
November 10, 2015
Thank you for the kind comments. I apologize for the slow response. Frankly I was afraid of what rabbit holes reading up on LSFR-based Toeplitz hash might take me! I’m afraid I wasn’t able to find anything on the Toeplitz hash itself, at least at a level I understood (wikipedia level :-)). I did recognize that like a CRC it appeared to be a LSFR from my days of implementing modem prototocols (MNP anyone?) in software, including the CRC check. I’m afraid that despite numerous examples of relating LSFR to polynominal division, I could never make the intuitive leap of following that. But I can read a LSFR diagram and implement that code. Or in the case of this project, look for the patterns and deduce the rule. I was fortunate in having a wealth of data to find data that differed by only 1 bit to make the analysis easier. It is not Enigma level reverse engineering but it was fun. The harder work to me would have been the folks who reverse engineered the modulation. Both the ones I built my code on as well as the poor guy in Germany in the above post who re-invented that wheel. My hats off to all of them.
I forgot to reply to your comments on speeding it up, so I’m editing this reply to add this. Yes, you are correct. As a matter of fact that played heavily into my earlier CRC modem algorithms. However in this case, I found time was not an issue and I wanted to be as clear as possible and felt such changes would occlude the point of the code. But I encourage others who find themselves short of compute time to take advantage of the insight.
Baku
November 10, 2015
Hi Ron!
You have no idea how happy i was to find your solution for this checksum nightmare! And yes, i have some experience with this and did a brute-force attack with the common CRC, permuting thru ALL possible Rocksoft-parameters with brute force and no success…
This was the final riddle to solve on this temperature sensors. The rest of the protocol i already reverse engeneered before, by some cheap tricks like observing and demodulating the radio transmissions, connecting my Atmel to the receiver of the base station and -best trick- to replace the sensors of one transmitter by variable resistors to get predictable readings 🙂
Luckily i found your site _after_ finishng all of this enjoyable and diverting hours in my (sorry, i don’t know the english word) ‘Bastelbude’ (The location where elder men go for the sole purpose of indulging in their purposeless hobbies).
The main reason for this ignorance is, that the F007TH temperature sensors are sold here in germany under the brand name ‘Froggit’. And adding this to google searches will discard all hits to the F007TH from ‘Ambient Weather WR-334-U’…
But to come back to my up to now unasked question:
May i use your code and publish it -of course with credits to the creator- in my documentation of the F007TH protocol for the german community?
I will only tell good things about it, and despite you call it ‘sketch’, i would call it a function or, more modern, a ‘code snippet’. It worked under VC2015 (OK, i had to define uint8_t as BYTE) as well as gcc without any changes. And it is readable.
Please let me know about your terms and conditions to reuse your phantastic code!
eclecticmusingsofachaoticmind
November 10, 2015
Hi Baku,
I’m glad you were finally able to find this. Hopefully your comments with the “Froggit” terms may lead others here as well.
I used the term “sketch” because that is what programs are called in the Arduino world where I implemented this. I’ve no idea why other than maybe it is less intimidating and hopefully draws in newbies.
In any case to answer the heart of your question, I’d be honored to share the code with you with no restrictions. I built my code based on the work of others and just want to give back. So I give you (and anyone else) permission to freely use and share this as you wish (open source / freeware). Attribution would be nice. Best of luck to you in your work. Someday soon I hope to return to the larger portion of this project.
Ron
Baku
December 28, 2015
Hi Ron!
I even can contribute something:
The bit just above the 3 bits for the sensor identification (0..7) is the ‘Low Battery’ bit, which turns on if the battery voltage of the sensor falls below ~2.5V.
Meanwhile i did some efforts in checking out some chinese receiver modules and improving my decoding software. It is far beyond the ‘original’ software of the base station and decodes more than double of the received telegrams correctly. And -up to now- i did not find one single telegram that passed your ‘checksum-test’ and gave invalid or unlogical readings.
Anyway – i like yor work. But i still don’t understand your opinion to the ‘Arduino-community’.
eclecticmusingsofachaoticmind
December 28, 2015
Hi Baku,
I’m happy to hear about your success and also deducing the battery low bit. That will be a very useful addition. Thank you for sharing that.
You said you don’t understand my opinion to the Arduino-community. If you are referring to our discussion of sketch vs snippet, it is just a minor point in communication I was trying to clear up and clearly failed. It is not worth further confusing the matter further. I’ll just drop that point and stick to the technical.
I’m hoping to finish a couple of other minor projects this week while off work and get back to the sensors. At this point the most utilitarian portion (the “checksum”) is done. What remains is my personal goal of displaying all the temperatures from sensors around the house on a map/blueprint of the house on a dynamic web page. Then much later, adding servo-dampers to my HVAC to help achieve desired temperatures in each room (or as close as I can, given the compromise of working with a desired temperature and actual temperature).
Regards
Ron