Paul King chronic tinkererer

Mimicking RF Remote Signals with Arduino

Recently I read an interesting article about black-box analysis of unknown radio signals that inspired me to revisit my home light controller.

My original approach had been to capture the signals with a 433MHz receiver + logic-analyser then blindly replay them using an Arduino.

While this worked in practice, the resulting code was an rats nest of digitalWrite and delayMicroseconds calls. I only captured two of the four channels before it all became too tedious.

Proper analysis of the signals resulted in much simpler code and control of all four channels, including the 'all off' button.

The Target

I picked these up in ASDA a few years back; a remote and four wireless receivers for the princely sum of £5.

As you'd expect the security on these things is non-existant, but for switching lamps they'll do the job for the time being.

Capturing the signal

The sticker on the back of claimed transmission at 433.92MHz, so I fired up GQRX and started poking around 433.9MHz while spamming the buttons on the remote.

It didn't take long to find a big consistant spike at 433.823700 MHz, so I played with the Mode selector in GQRX until one (AM) sounded like digital data.

From there it was just a case of adjusting the squelch (SQL) setting to eliminate background noise, and capturing a WAV file of each button using the GQRX record function.

Note that the signal is repeated multiple times because I held the button on the remote down while recording.

Identifying the encoding scheme

From what I can tell, identification can be a bit of a crap-shoot - you try different schemes and hope something makes sense.

Using Audacity, I cropped and aligned the different samples, reduced each to mono, then used 'Waveform (dB)' in the track dropdown to give this:

Viewing the signals side-by-side helped a lot.

As you probably noticed, each sample is the same until around the 20th pulse.

Attempt 1

I guessed, incorrectly, that the signal was Manchester Code because it had been referenced in the original article I'd read.

Thankfully, this quickly turned out to be a dead end as illegal codes cropped up while trying to decode it. See this page for more info.

Attempt 2

Next up was Pulse Width Modulation.

There were two distinct pulses, fat ones (~75% duty-cycle) and thin (~25%).

This seemed simple enough to be legit so I proceeded to decode every sample.

Interpreting the data

It seemed logical to assume that the signal would have to contain an address and an on/off flag.

Assuming that fat pulses indicated binary 0 and thin a binary 1, this gave:

button      x x x x x x x x x x x x x x x x x x x x  a b b b  y
===============================================================
1 off       0 1 0 1 1 0 0 0 1 1 0 0 0 0 1 1 1 0 0 0  1 0 0 0  1
1 on        0 1 0 1 1 0 0 0 1 1 0 0 0 0 1 1 1 0 0 0  0 0 0 0  1
2 off       0 1 0 1 1 0 0 0 1 1 0 0 0 0 1 1 1 0 0 0  1 1 0 0  1
2 on        0 1 0 1 1 0 0 0 1 1 0 0 0 0 1 1 1 0 0 0  0 1 0 0  1
3 off       0 1 0 1 1 0 0 0 1 1 0 0 0 0 1 1 1 0 0 0  1 0 1 0  1
3 on        0 1 0 1 1 0 0 0 1 1 0 0 0 0 1 1 1 0 0 0  0 0 1 0  1
4 off       0 1 0 1 1 0 0 0 1 1 0 0 0 0 1 1 1 0 0 0  1 0 0 1  1
4 on        0 1 0 1 1 0 0 0 1 1 0 0 0 0 1 1 1 0 0 0  0 0 0 1  1
all off     0 1 0 1 1 0 0 0 1 1 0 0 0 0 1 1 1 0 0 0  1 1 1 1  1

x = 20-bit prefix, always the same
a = on = 0/off = 1 (a tad weird)
b = 3 bit address, least-significant-bit first
y = 1-bit suffix, always the same

Clarification

The button labelled "2 off" on the remote sends the 20-bit prefix, the on/off bit (off=1), the address 1,0,0 (b001), and finally the 1-bit suffix:

01011000110000111000 1 100 1

Unsurprisingly "2 on" sends the same code but with the on/off bit = 0 (ON):

01011000110000111000 0 100 1

That just left establishing the timings for a fat and thin pulse.

Calculating the timings

Using sox, I converted each WAV file into a raw dat file:

# sox 1-on.wav 1-on.dat

# head -n 10 1-on.dat
; Sample Rate 48000
; Channels 1
               0      -0.20791626
   2.0833333e-05      -0.21231079
   4.1666667e-05      -0.22146606
        6.25e-05      -0.21704102
   8.3333333e-05      -0.20309448
   0.00010416667      -0.19512939
        0.000125      -0.20596313
   0.00014583333      -0.21807861

That gave a list of every sample in the WAV file:

Any line with positive amplitude I treated as a high part of the signal.

The average of the time differences between consecutive groups of high amplitudes gave a rough idea of period, and from that the timings could be calculated:

Mean period T = 0.001169167s = 1169.167us
Frequency = 1/T = ~855Hz

~75% duty-cycle = 1169.167 * 0.75 = 876.87525us
~25% duty-cycle = 1169.167 * 0.25 = 292.29175us

Fat  = 876us ON, 292us OFF
Thin = 292us ON, 876us OFF

Gap between repeats (see below) = ~0.009s = 9000us

Transmitting the signal

Hardware

I just used an Arduino (any old microcontroller will do) coupled with a 433MHz transmitter.

Wiring it up is straight-forward:

Software

I deliberately kept this simple in the hope that timing delays due to overhead of calls to digitalWrite wouldn't impact the timings enough to make a difference... it worked fine for a change!

The script just loops over an array of 0s and 1s, sending each bit based on the timings calculated above:

#define LONG_DELAY_US 876
#define SHORT_DELAY_US 292

...

void sendBit(byte b) {
  if (b == 0) {
    // 75% duty cycle
    digitalWrite(RF433MHZ_PIN, HIGH);
    delayMicroseconds(LONG_DELAY_US);
    digitalWrite(RF433MHZ_PIN, LOW);
    delayMicroseconds(SHORT_DELAY_US);
  }
  else {
    // 25% duty cycle
    digitalWrite(RF433MHZ_PIN, HIGH);
    delayMicroseconds(SHORT_DELAY_US);
    digitalWrite(RF433MHZ_PIN, LOW);
    delayMicroseconds(LONG_DELAY_US);
  }
}

The full code is on github, if you're interested.

You'll notice that the signal is transmitted multiple times in the full code. This seemed to be required, and was what I was also seeing from the remote.

The delay between each tranmission was calculated above to be ~9000µs.

Next Steps

The current controller is pretty monolithic. It never made it to its own PCB:

It also features:

For v3 I'd like to split this into a bunch of smaller modules, each with only one job. Each module will communicate on a wireless/mesh network (using an NRF24L01 module) to a master node that can talk HTTP/OpenHAB.

Watch this space, I guess :)