robot vacuum xr dock ir codes

Following on from my earlier work emulating the Roomba-style virtual walls I thought I'd take a look at the IR codes used by my robot vacuum XR's dock. The dock is a charging station that the vacuum looks for when it's battery is low. Both the dock and the vacuum have a pair of metal contacts on the front; the vacuum just drives into the dock until it detects the charging voltage.
As I already knew that the virtual wall used the exact same IR signal as the Roomba, I thought that it might be likely that the IR codes for the dock had been copied as-is too. The Roomba dock uses three 38KHz modulated IR LEDs; a short range, uni-directional 'force field' on top and two forward projecting beams - called the red buoy and the green buoy. Each of these transmit different but synchronised 8 bit code, that has been carefully chosen so that where the beams overlap the bit streams are logically AND'd together. For example if the bit stream for the red buoy is 11110001 and the green buoy is 11110010, then where the beams overlap (i.e. in the middle) the bit stream would appear as 11110011. This technique cleverly increases the number of zones the robot can detect near the dock.
The IR bit format for the Roomba is well documented, but essentially a one is 3ms on/1ms off and a zero is 1ms on/3ms off - with the bit stream being terminated by a long off (4ms or more). This isn't that far from the simple 1ms on/1ms off arduino code I wrote to emulate the virtual wall, and so I modified the code to produce one of the Roomba dock codes. Testing with the XR showed nothing interesting, so I tried a couple of other Roomba codes but still nothing - at this point I decided I needed to see what the XR's dock was actually doing.
The XR is considered 'production' kit in my house and so any reverse engineering has to be without opening it up (I like to pretend it has some sort of anti-tamper device). An old DTV set top box donated a 38KHz IR sensor which I connected to an arduino loaded with Ken Shirriff's IRrecvDump code. Then I used my phone's camera to locate the IR LEDs.

The XR's dock has four IR LEDs, three forward facing and the uni-directional one on top; it's difficult to get a picture that includes all three forward facing LEDs as they've been deliberately arranged such that all three beams don't overlap. With the IR sensor held up to each LED in turn, this is the raw output from the arduino:
Left:
Raw (16): -31586 2850 -650 2800 -650 2850 -600 2900 -600 2800 -650 800 -2700 750 -2700 800
Raw (16): -31586 2850 -600 2850 -600 2850 -650 2800 -650 2850 -650 750 -2700 800 -2650 800
Raw (16): -31586 2850 -600 2800 -700 2800 -650 2850 -650 2850 -600 800 -2650 750 -2750 750
Raw (16): -31636 2800 -700 2800 -650 2850 -650 2850 -600 2850 -600 750 -2750 750 -2700 800
Center:
Raw (16): -31586 2850 -600 2850 -650 2800 -650 2850 -600 750 -2750 750 -2700 2850 -650 750
Raw (16): -31586 2850 -600 2900 -600 2800 -650 2850 -650 750 -2750 750 -2700 2850 -600 800
Raw (16): -31636 2900 -600 2800 -650 2850 -650 2850 -650 750 -2700 750 -2700 2800 -700 750
Raw (16): -31636 2850 -600 2850 -650 2850 -600 2850 -650 750 -2700 750 -2750 2850 -600 800
Right:
Raw (16): -31586 2900 -600 2850 -600 2850 -650 2800 -650 800 -2700 2850 -600 800 -2650 800
Raw (16): -31586 2850 -600 2850 -650 2850 -600 2850 -650 750 -2700 2850 -650 750 -2700 750
Raw (16): -31636 2850 -650 2850 -650 2800 -650 2850 -600 800 -2700 2800 -650 800 -2700 750
Raw (16): -31586 2850 -650 2850 -600 2850 -600 2850 -650 750 -2700 2850 -650 750 -2700 800
Force field:
Raw (16): -31850 2850 -600 2850 -650 2850 -600 2850 -650 750 -2700 750 -2750 750 -2700 2850
Raw (16): -31850 2850 -650 2850 -600 2850 -650 2850 -600 750 -2750 750 -2700 750 -2750 2800
Raw (16): -31900 2850 -600 2850 -600 2900 -600 2800 -700 750 -2700 750 -2750 750 -2700 2850
Raw (16): -31850 2850 -600 2850 -650 2850 -600 2850 -650 750 -2700 750 -2750 700 -2750 2800
These are pulse timings in microseconds; negative numbers are the off time, positive numbers show the on time. The first large off time (~30ms) marks the beginning/end of the bit stream, and then there's an on time of around 3ms - which is what I'd expect to see from a real Roomba dock; however the off time is much shorter than the 1ms of the Roomba. Making the assumption that the XR's IR protocol only differs in the short time being ~0.5ms rather than 1ms - so that one is 3ms on/0.5ms off(long-short) and a zero is 0.5ms on/3ms off(short-long) - the timings decode as:
Left:
Raw (16): -31586 | 2850 -600 | 2800 -700 | 2800 -650 | 2850 -650 | 2850 -600 | 750 -2700 | 750 -2750 | 750
| long-short| long-short| long-short| long-short| long-short| short-long| short-long| short-long
| 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0
Center:
Raw (16): -31586 | 2850 -600 | 2850 -650 | 2800 -650 | 2850 -600 | 750 -2750 | 750 -2700 | 2850 -650 | 750
| long-short| long-short| long-short| long-short| short-long| short-long| long-short| short-long
| 1 | 1 | 1 | 1 | 0 | 0 | 1 | 0
Right:
Raw (16): -31586 | 2900 -600 | 2850 -600 | 2850 -650 | 2800 -650 | 800 -2700 | 2850 -600 | 800 -2650 | 800
| long-short| long-short| long-short| long-short| short-long| long-short| short-long| short-long
| 1 | 1 | 1 | 1 | 0 | 1 | 0 | 0
Force field:
Raw (16): -31850 | 2850 -600 | 2850 -650 | 2850 -600 | 2850 -650 | 750 -2700 | 750 -2750 | 750 -2700 | 2850
| long-short| long-short| long-short| long-short| short-long| short-long| short-long| long-short
| 1 | 1 | 1 | 1 | 0 | 0 | 0 | 1
Looking at the bit patterns you can see how the only difference is in which bit is set in the last four bits:
Left: 1111 1000
Right: 1111 0100
Center: 1111 0010
Force field: 1111 0001
Putting some quick and dirty code onto the arduino to emulate these codes/timings had the expected effect on the XR - sort of. When the center LED was emulated, as soon as it came into the robot's view, the XR just drove straight forward. Emulating the left LED made the robot turn right; the right LED caused it to turn left. Thinking about how the robot must guide itself in, this seems to make sense but also means the physical layout of the LEDs is important. Expanding the bit patterns out to include the beam overlaps produces the final code table:
Left: 1111 1000
Left/center: 1111 1010
Right: 1111 0100
Right/center: 1111 0110
Center: 1111 0010
Center/FF: 1111 0011
Right/center/FF:1111 0111
Left/center/FF :1111 1011
Force field: 1111 0001
