Two people have reverse engineered most of the protocol. These things are cheap, and do something I wanted to build. Time to finish the job.
Remember that device that was off-frequency? The one that I was going to just let be off-freq and deal with in software on the receiving end? The one that's functioning incorrectly but I'm going to ignore that minor detail and live with it?
Yeah, I couldn't live with it. (I shoulda seen that coming.)
The conversation in my head went something like this.
"Logically, the problem must be in the transmitter," says me. "The transmitter's discrete. There doesn't seem to be any initialization sequence configuring it. So it's got to be a hardware problem, and if it's a hardware problem, it might be correctable."
So, slightly inebriated, I decide it's time to poke at the hardware. "Maybe I'll desolder the transmitter and harness it." Pause. "I'm not sure what that buys me. It'll just be off-frequency outside of the sensor." Sip. "I could make it transmit more frequently, at least. Or maybe pull out the scope! It doesn't go up that high, but maybe I'll find that they put in the wrong crystal or something."
A short break later while I refilled my beverage of choice, I started thinking about what crystal it is. "I could open another one and look at it. That's probably the easiest way to make sure it's got the right crystal. And then when I desolder it, I can take care of this nasty ugly blob of solder on the crystal leads on the transmitter." Sip.
My brain signals something that I'm not quite paying attention to.
A blob of solder. "Huh, they soldered the humidity sensor in really poorly. Glad I could fix that and it worked fine." Sip.
A blob of solder. "And here it is, but not nearly as badly, on the transmitter. Good thing too, they might have shorted out something important that made it function improperly."
Perhaps you've figured it out by now? Cause I was clueless until about that point - yes, indeed, the crystal was poorly soldered on to the transmitter.
30 seconds of running it downstairs and resoldering the crystal later, and in a fashion befitting overly attitudinal chefs, *BAM* - we're stable, on-frequency, no drift at all.
Aah. I gotta remember how useful a little downtime is more often.
While I was waiting for an Ubuntu install (to do that SDR work), I figured it would be worth some time to author a quick plug-in for Saleae Logic to decode Acurite temperature/humidity sensor data directly.
The decoder I wrote isn't good at returning a byte-at-a-time, which lead me to do an all-or-nothing decoding here. I'm not sure if I'll leave it that way or not, but here it is, decoding two over-the-air received streams and one from-the-transmitter stream between them:
The code is somewhat raw, but it's up on GitHub.
Digging out the RTL SDR (and waiting for Ubuntu to install in a VM because I've temporarily broken my gnuradio install under MacOS again), it's as I suspected: the new "broken" temperature sensor is broadcasting off-frequency. The three dots under the center line in this waterfall display are the three sensors that behave themselves, broadcasting at 433.92 MHz. The one off to the right at about 434.4MHz is the rogue.
Given that 434.4 is still in the ISM unlicensed band, I'm planning on keeping the sensor and working around the problem in software.
Looking at the internals of the two sensors I had, I noticed that the humidity sensor and a few other (probably supporting) components were missing. Of course I can't go without knowing what belongs there, so I ordered two "temperature and humidity" sensors directly from the acurite website. Same part number as the thing I had already.
First, the old units:
Some minor manufacturing differences (and defects) aside, you can see that on the left there's a spot for the humidity sensor. Here's a crop:
(And no, I don't know why there would be a C/F jumper on the transmitter. Poking at it, I couldn't make the device do anything differently when jumping it in various odd ways.)
So here's the new sensor:
Obviously, a new rev of the circuit. On the left, there's a humidity sensor (which is very, very badly soldered; the lower lead has a glop of solder on it that isn't even soldered to the pad).
And it's DOA.
Okay, not *totally* DOA. It blinks periodically. Sticking a logic probe on the data pin of the transmitter, I see it trying to transmit (three packets, just like the other sensors). But I'm not receiving any data from it at all.
A second that I bought at the same time works just fine. So it's not a general problem receiving from the new units. Naturally, I suspected the lousy soldering job; resoldering the humidity sensor doesn't make a difference.
I suspect that either the transmitter is misaligned and transmitting on a different frequency, or the transmitter is just plain dead. I'll have to get my SDR radio working to see if any RF is coming out of this thing.
All of the exposed pads around the IC also intrigue me. I wonder what kind of processor that is, and if something fun can be done to it...
The RFM12B is on its way out in favor of the RFM69. So, seems like a good idea to make an RFM69 receive this data too.
First: the RFM69 has to be wired up with DIO2, via a 100 ohm resistor, to A0. Fortunately that's all the hardware mod that you have to do to the RFM69 (unlike the RFM12B, which needed that cap replaced).
Second: time to code up the differences between the 12B and the 69.
rfm69_write(RegOpMode, RegOpModeStandby); rfm69_write(RegDataModul, RegDataModulContinuous | RegDataModulOOK); // Set continuous OOK mode RegBitrateSet(8000); // 8.0kb/s RegFrfSet(433920000); // fundamental frequency = 433.92MHz (really 433.920044 MHz) rfm69_write(RegRxBw, RegRxBwDccFreq4 | RegRxBwOOK50k); // 4% DC cancellation; 50k bandwidth in OOK mode rfm69_write(RegLna, RegLnaZ200 | RegLnaGainSelect12db); // 200 ohm, -12db rfm69_write(RegOokPeak, RegOokThreshPeak | RegOokThreshPeakStep0d5 | RegOokThreshPeakDec1c ); rfm69_write(RegOpMode, RegOpModeRX); rfm69_write(RegAfcFei, RegAfcFeiAfcClear);
Ignoring the specific differences between the RFM12B and the RFM69, there are still a few differences between the two initializations. The RFM12B code I used sets the datarate to 2.4Mb/s; this, to just 8Kb/s. The RFM12 code is also setting the frequency farther away from 433.92 MHz, which this is spot-on -- something that's also more obvious by the smaller bandwidth in this code (50khz instead of 270khz).
So perhaps it's time to go back and tune the RFM12's initialization, given what I now know. Or maybe not, since I'm not likely to use them again...
Regardless, the code's pushed to github in a branch named 'rfm69'.
Contents: one Arduino Pro Mini 5v; one RFM12B, slightly modified; one compiled program; two AcuRite temperature sensors.
which happily spits garbage like this out the serial port:
addData: A2 66 44 90 88 93 F7
DecodePacket: A2 66 44 90 88 93 F7
Source ID: 4454
Temperature: 4.40 C (39.92 F)
addData: C2 7E 44 FF 9 81 D
DecodePacket: C2 7E 44 FF 9 81 D
Source ID: 382
Temperature: 15.40 C (59.72 F)
Next: I hate hacking up the original receivers for these kinds of projects. I could rip the (211AYQS) receiver out of the "inside" unit, harness that on a microcontroller, and be done. But no; just like I never ripped the WWVB receiver out of the "atomic" clock - which I bought for the express purpose of ripping the WWVB receiver out of it, sigh - I know I'll never be satisfied ripping the receiver out of this AccuRite thing.
So my plan is to use an RFM12B, instead.
I've now got it receiving a reasonable facsimile of the transmitted signal. (Comparing the received signal from the 211AYQS receiver and the bitstream coming out of the RFM12B using a Saleae Logic shows they're pretty close.) It took some work to get here.
First: the RFM12B had to be hacked to work in OOK mode. (By default, one of the caps on the board defeats the fast voltage swings needed for OOK.) On the third try, I settled on a 150pF capacitor, replacing the 4.7nF capacitor next to the IC (a 100pF cap seemed too small; 220pF didn't seem to help any either; and in retrospect, they were all probably fine). This is all as-documented here: [OOK ASK with a modified RFM12B]
Second: the Jeelib code for OOK mode needs to be tweaked. Specifically, the initialization sequence (starting with the RFM12B_OOK example project) needs to be changed to better support the temperature sensor protocol, specifically on the four lines that I comment here:
rf12_control(0x8017); // 433 MHz; not 868 MHz rf12_control(0x82c0); rf12_control(0xa68a); // technically, 0xa620 would be the center frequency, but this works as-was rf12_control(0xc655); // Set baud rate to 4k, not 4.8k rf12_control(0x948a); // Raise DRSSI limit to -91dbm from -97dbm rf12_control(0xc220); rf12_control(0xca00); rf12_control(0xc473); rf12_control(0xcc67); rf12_control(0xb800); rf12_control(0xc800); rf12_control(0xc040);
Without these changes, the data doesn't come out of the RFM12B cleanly, as seen in this image (top is the data as received by the 211AYQS; bottom is as received from the RFM12B):
Once it's properly tuned, the data is much more reasonable (top and bottom traces are both the RFM12B, being tested from two different points on the board; the center trace is the 211AYQS, which seems to receive the data just a smidge faster):
And that's as far as I've gotten. Next up, I have to write a new demodulator...
Although two different people have reverse engineered the wireless protocol used by these wireless temperature sensors, they've both ignored the checksum. While that's fine from the perspective of "at least I got the data out," it's not good enough for me. RF is messy; checksums are useful. I'd like to understand what the checksum is, so that I'm relying on more than just parity bits.
First stop: assuming it's a CRC.
Plain CRCs obey a mathematical constraint -- if you XOR the plaintext by a given value, that causes a specific XOR change to the output. So if data (D) is modified by (A) to generate CRC (C) with modifier (B):
D1 ^ A1 = C1 ^ B1
D1 ^ A2 = C1 ^ B2
Then it should also be true that
D1 ^ A1 ^ A2 == C1 ^ B1 ^ B2
If that relationship doesn't hold, then it's not a standard CRC implementation.
Well, I picked three messages, determined their xor'd differences, and found that this relationship was true for those messages. So I spent a couple of hours trying to divine what the polynomial might be, on which that CRC is based.
As a quick aside: I stumbled across RevEng http://reveng.sourceforge.net/ - which I'd not previously seen. Looks interesting.
Sadly, that was all a red herring. I finally decided I should take a look at other checksums, and starting with what I thought would be the simplest one - a modular checksum - I found that the checksum on these packets is no more than adding up all of the bytes in the packet.
Wow, what a waste of time. :)
I'm baffled by the combination of parity bits (as the high bit of each byte) and a simple modular sum byte at the end. A typical modular checksum uses the two's complement of the added bytes, so that the implementation can just add up all of the bytes of the packet, and expect to get a final result of 0. This implementation seems to just make more work for the receiver. It feels a lot like it was implemented by someone that had little experience with RF protocols.