<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="sodnpoo.xsl" type="text/xsl"?>
<xml page="/?t=robot+vacuum"><a href="http://sodnpoo.com/?x=html">HTML version</a><post>
  <tag value="robot vacuum"/>
  <tag value="reverse engineering"/>
  <title>robot vacuum xr dock ir codes</title>
  <date>28 Feb 2014</date>
  <p>
  </p>
  <image src="/posts.assets/xr_dock1.jpg"/>
  <p>
  Following on from my earlier work emulating the Roomba-style <a href="http://www.sodnpoo.com/posts.xml/arduino_powered_robot_vacuum_virtual_wall.xml">virtual walls</a> I thought I'd take a look at the IR codes used by my <a href="http://www.paramountzone.com/robot-vacuum-cleaner-new.htm">robot vacuum XR's</a> 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.
  </p>
  <p>
  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.
  </p>
  <p>
  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.
  </p>
  <p>
  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 <a href="http://www.righto.com/2009/08/multi-protocol-infrared-remote-library.html">Ken Shirriff's IRrecvDump</a> code. Then I used my phone's camera to locate the IR LEDs.
  </p>
  <image src="/posts.assets/xr_dock2.jpg"/>
  <p>
  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:
  </p>
  <pre>
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 
  </pre>
  <p>
  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:
  </p>
  <pre>
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

  </pre>
  <p>
  Looking at the bit patterns you can see how the only difference is in which bit is set in the last four bits:
  </p>
  <pre>
Left:        1111 1000
Right:       1111 0100
Center:      1111 0010
Force field: 1111 0001  
  </pre>
  <p>
  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:
  </p>
  <pre>
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  
  </pre>
</post><post>
  <tag value="arduino"/>
  <tag value="robot vacuum"/>
  <tag value="reverse engineering"/>
  <title>arduino powered robot vacuum virtual wall</title>
  <date>
  8 Feb 2014
  </date>
  <p>
  </p>
  <image src="/posts.assets/virtual_wall1.jpg"/>
  <p>
  We've had a robot vacuum at home for a couple of months and now we've got one for the office at work. <a href="http://www.paramountzone.com/robot-vacuum-cleaner-new.htm">The XR at home</a> came with a <a href="http://uksupport.irobot.com/app/answers/detail/a_id/965/">virtual wall</a> that produces an infrared beam that the vacuum just wont go anywhere near. <a href="http://www.vileda.com/uk/products/electrical-products/vileda-cleaning-robot.html">The Vileda at work</a> didn't come with one (nor a remote or a dock) which would of been handy; there's a couple of areas we'd like to keep it away from.
  <p>
  On the front of the Vileda there's a 360 degree IR sensor that both my XR and the Roomba's have - although the Vileda doesn't come with any accessories that it would need it for (dock, virtual wall) - nor at present does Vileda appear to sell any. Making the assumption that both of these had probably been - for at least some part - reverse engineered from a Roomba, I took the virtual wall from home into work and tested it against the Vileda; it too wouldn't go any where near it :)
  </p>
  <p>
  I'm not really allowed to open up the the XR (or it's accessories) at home as it's technically "in production", but initial thoughts were that I just needed to be able to detect the beam coming from the virtual wall myself and then re-create it. Before doing that though I thought I'd see if the Roomba virtual wall had been reverse engineered already and see if I could implement that, and if it caused the XR to flee.
  </p>
  After a little searching I found <a href="https://sites.google.com/site/irobotcreate2/createanirbeacon">this post</a> that details some of the IR protocol used on the Roomba's - and right at the bottom of the page there is this:
  </p>
  <pre>
  "The Virtual Walls generate a 1ms ON, 1ms OFF signal continuously.  The 1ms ON is a 38Khz PWM pulse."
  </pre>
  <p>
  Excellent. A little more searching and I found <a href="http://forum.arduino.cc/index.php?topic=38452.msg284490#msg284490">this post</a> on the arduino forums; right at the bottom of the code block there's this function:
  </p>
  <pre>
// this will write an oscillation at 38KHz for a certain time in useconds
void oscillationWrite(int pin, int time) {
 for(int i = 0; i &lt;= time/26; i++) {
   digitalWrite(pin, HIGH);
   delayMicroseconds(13);
   digitalWrite(pin, LOW);
   delayMicroseconds(13);
 }
}
  </pre>
  <p>
  So I swiped an IR LED from an old TV remote and after a bit of messing with my phones camera (it doesn't have an IR filter so I could use it to see the infrared) and the blink example program, I wrapped oscillationWrite() up in loop() so that it did a 38KHz pulse every millisecond:
  </p>
  <pre>
void loop() {
  oscillationWrite(irled, 1000);
  delayMicroseconds(1000);
}
  </pre>
  <p>
  I was pretty hopeful that this would work and went to test it with the XR, but it completely ignored it. Assuming one of the timings was off I hooked the scope to the LED pins. The millisecond pulse was a little off but not by a lot, but the 13 microsecond pulses that make up the 38KHz carrier were coming in at 18 microseconds, probably due to the amount of code that's executed by digitalWrite(). Rather than mess around using port manipulation to speed up toggling the pin, I just knocked the delay time down to 8us to compensate.
  </p>
  <p>
  Testing again with the XR proved successful, with it backing away whenever it got to close to my improvised beam :) for a complete test I trapped the XR between my beam and the virtual wall it came with. My 'wall' is across the bottom of the door next to the olde worlde CD player and the real one is the black object on the floor next to the sofa. Everytime it sees a beam, it backs away. (The bumper is actually at the front - it's just that in the video, the XR spends most of it's time going backwards!)
  </p>
  <p>
  <center><iframe width="560" height="315" src="//www.youtube.com/embed/CmBN9TH2uiY" frameborder="0"/></center>
  </p>
  <p>
  Although I now had a working solution I couldn't help but think that the hardware PWM could generate the carrier more accurately than rapidly toggling a pin. Yet more searching brought up this <a href="http://forum.arduino.cc/index.php?topic=102430.msg769416#msg769416">this post</a> that suggested a wonderful simple solution:
  </p>
  <pre>
  "Why not just use the Tone() function?"
  </pre>
  <p>
  Swapping out oscillationWrite() for tone() works perfectly and results in this very concise sketch:
  </p>
  <pre>
int irled = 40;

void setup() {                
  pinMode(irled, OUTPUT);     
}

void loop() {
  tone(irled, 38000, 1);
  delayMicroseconds(1000);
}
  </pre>
  <p>
  Hardware-wise, it's nothing special; just an IR LED with it's positive leg connected via a current-limiting resistor to the +5v, and it's negative leg to any pin you like. (Pin 40 for me, using a Mega.)
  </p>
  <p>
  </p>
</post></xml>
