Decoding RCX IR command protocol

This post is part 2 of 2 of  LEGO IR sniffing

After showing LIRC controlling different LEGO IR protocol devices, Alexandre Campos suggested that a real LEGO Universal IR Controller should also control the MINDSTORMS RCX and Scout pBricks.

Very well, let’s do it.

I have a 9738 RCX Remote Control Unit:

This remote can control the 3 motors of the RCX (or the two motors and the LED of the Scout) and also start/stop programs stored in the pBrick memory. Unlike the other LEGO IR remotes, it’s not possible to choose a channel so the remote will control all pBricks in range.

I captured all motor control signals with Arduino rawRecv and replicated them with rawSend and it worked very well so I thought it will be easy to convert the signal to LIRC format like I did with Spybotics.

Wrong.

Turns out the RCX signals are huge. For instance the “A Forward” key generate a 100 byte signal:

#define RAW_DATA_LEN 100
uint16_t rawData[RAW_DATA_LEN]={
 390, 442, 386, 442, 386, 442, 386, 442, 
 386, 858, 382, 4170, 3710, 850, 802, 438, 
 806, 438, 346, 1722, 386, 446, 382, 858, 
 386, 442, 806, 850, 3706, 850, 346, 4206, 
 1626, 450, 2042, 442, 346, 1310, 346, 1722, 
 350, 482, 806, 434, 382, 862, 346, 894, 
 350, 478, 350, 478, 386, 446, 766, 474, 
 1190, 466, 382, 446, 382, 446, 382, 446, 
 350, 530, 298, 894, 346, 4210, 3670, 882, 
 774, 474, 766, 474, 346, 1726, 346, 530, 
 298, 894, 346, 518, 734, 886, 3762, 794, 
 318, 4238, 1642, 430, 2062, 422, 318, 1338, 
 294, 1774, 350, 1000};

Assuming a 417 µs bit lenght (2400 bps) I just copy these 100 values into a spreadsheet like LibreOffice Calc and  use ROUND(x/417) to get the toggling sequence:

1,1,1,1,1,1,1,1,1,2,1,10,9,2,2,1,2,1,1,4,1,1,1,2,1,1,2,2,9,2,1,10,4,1,5,1,1,3,1,4,1,1,2,1,1,2,1,2,1,1,1,1,1,1,2,1,3,1,1,1,1,1,1,1,1,1,1,2,1,10,9,2,2,1,2,1,1,4,1,1,1,2,1,1,2,2,9,2,1,10,4,1,5,1,1,3,1,4,1,2

The whole sequence represents a 221 bit long signal, that’s really long!

Since transmission starts with a start bit ‘0’ those first 14 numbers represent this sequence:

010101010110111111111100000000011

Does it makes any sense?

The RCX messages always start with a “55 FF 00” header, followed by a payload where each data byte is followed by it’s complement. At the end, a checksum is sent, also followed by it’s complement.

For a “A Fwd” command (“D2 00 08”) the payload is:

D2 2D 00 FF 08 F7

(where 2D, FF and F7 are the complements of D2 00 08, i.e. FF-D2 = 2D, FF-00=FF and FF-08=F7)

And the checksum is “DA 25”

D2 + 00 + 08 = DA
FF - DA = 25

So the complete message is 11 byte long:

55 FF 00 D2 2D 00 FF 08 F7 DA 25

Each one of these bytes is sent in reverse order, with a start bit, an odd parity bit and a stop bit so with 3 extra bits for each we get a 121 bit long signal, much less than the 221 bits I captured.

So what should I had captured?

Let’s see just the header in binary notation:

55 FF 00 = 01010101 11111111 00000000

reverting each byte:

10101010 11111111 00000000

adding odd parity bit:

101010101 111111111 000000001

adding a start and a stop bit:

01010101011 01111111111 00000000011

Now if we re-group everything:

0 1 0 1 0 1 0 1 0 11 0 1111111111 000000000 11

Now we count how many times each bit occurs before “toggling”:

1 1 1 1 1 1 1 1 2 1 10 9 2

Thats quite familiar… let’s see those first 14 values of my captured sequence again:

1,1,1,1,1,1,1,1,1,2,1,10,9,2

Well, just one extra ‘1’ at the beginning, can’t be just a coincidence.

Analyzing the whole toggling sequence, I see two occurrences of the header. So I probably captured two repeated “A Fwd” signals, but the Arduino rawRecv sketch stopped capturing at the 100th sample.

So let’s ignore everything from the second header to the end:

#define RAW_DATA_LEN 58
uint16_t rawData[RAW_DATA_LEN]={
 390, 442, 386, 442, 386, 442, 386, 442, 
 386, 858, 382, 4170, 3710, 850, 802, 438, 
 806, 438, 346, 1722, 386, 446, 382, 858, 
 386, 442, 806, 850, 3706, 850, 346, 4206, 
 1626, 450, 2042, 442, 346, 1310, 346, 1722, 
 350, 482, 806, 434, 382, 862, 346, 894, 
 350, 478, 350, 478, 386, 446, 766, 474, 
 1190, 466};

Using this signal with rawSend works so my guess was correct.

I made a python script that calculates the complete signal and also it’s LIRC value from the toggling sequence and it gives a 121 bit long string as expected:

0101010101101111111111000000000110010010111101011010011000000000110111111111100001000001011101111010010110110101010010001
0xaadff80325eb4c01bff082ef4b6a91

Unfortunately using “0xaadff80325eb4c01bff082ef4b6a91” on a LIRC configuration file doesn’t work and my syslog shows:

lircd-0.9.4d[8847]: Info: Using remote: LEGO_RCX.
lircd-0.9.4d[8847]: Error: error in configfile line 10:
lircd-0.9.4d[8847]: Error: "0xaadff80325eb4c01bff082ef4b6a91": must be a valid (__u64) number
lircd-0.9.4d[8847]: Error: reading of file '/usr/local/etc/lirc/lircd.conf.d/LEGO_RCX.conf' failed

Must be an unsigned 64 bit long value?! Dang!

Searched a lot trying to find a way to use longer values in the LIRC file but all I could find was a workaround, mostly used by air conditioner owners: instead of using hexadecimal codes LIRC also accepts “raw_codes”

The first number indicates the duration of the first pulse in microseconds. The second number indicates the duration of the space which follows it. Pulse and space durations alternate for as long as is necessary. The last duration should represent a pulse.

Turns out this is more or less the same as the rawSend output I got before, except for the last value that it is a gap. So I removed the last value (‘466’) and defined a one-size-fits-all gap (‘2138’, the larger last value I got from all rawRecv captures).

begin remote
   name LEGO_RCX
   flags RAW_CODES
   frequency 38000
   gap 2138

   begin raw_codes

      name A_FWD

          390 442 386 442 386 442 386 442 
          386 858 382 4170 3710 850 802 438 
          806 438 346 1722 386 446 382 858 
          386 442 806 850 3706 850 346 4206 
          1626 450 2042 442 346 1310 346 1722 
          350 482 806 434 382 862 346 894 
          350 478 350 478 386 446 766 474 
          1190

And it works!

So now I have three LIRC configuration files:

With these 3 files and ANY supported LIRC transmitter I can now control every single IR device ever made by LEGO – even MINDSTORMS EV3 with IR distance sensor since it understands Power Functions IR signals.

Series Navigation<< Decoding old LEGO infrared protocol