Home Automation / Temperature Sensors

Posted on January 21, 2015

20


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;
}
Advertisements