Back | Home

Roomba More Smarter

This is a stub; will be updated later.

Motivation

My apartment tends to be a dirt & dust magnet. There is an exhaust room of some kind which gets fairly dusty (and since it internally builds up dust, it can't be cleaned until it is exhausted...), and outside dirt and stuff tends to get tracked. The only solution to this problem is to clean the dust, however, even though I clean the dust frequently, it basically never goes away. Also, I would prefer if I could be lazy about it. So, naturally, I looked at robot vaccums.

Robot Vaccums

Robot vaccums are basically the lazy man's way of cleaning. You tell them to clean, and they clean until they have no more battery. Simple enough, right?

Turns out, while robot vaccums can certainly get the job done, there are a few drawbacks.

The first one: robot vaccums are expensive. Most of them go for around $400+, with the highest I've seen reaching around $800! Not worth it when manual cleaning with a vaccum is significantly cheaper...

The second one: not all of the robots have the same cleaning algorithms. Some of them are fairly bad at cleaning. For example, I got my hands on a bobsweep Pet Hair Plus for $30 at Goodwill. Besides the fact that it has an extremely long name, it is extremely dumb. It can only consistently clean a 3x3 square in our apartment, and that's it. Otherwise, it keeps trying to clean random spots of the apartment, before dying while trying to reach the charger. I attempted to try and modify the device (and dump the firmware), but the motherboard suffered a short circuit while I was researching it. So, I eventually gave up on it, until I came across a new discovery.

iRobot Roomba 655

The All-Campus Makerspace at our school has a "Free Store", where makers are welcome to come in and get resources for their projects. Its a relatively new concept, and has been relatively successful so far.

One day, I decided to go to look for networking equipment for a project, and came across a defunct Roomba 655 in the "to take apart" section. It was stripped of its battery & cover, but looked fine otherwise. I decided to take the Roomba, since it'd be interesting to get it working.

A few days later, I sat down to inspect it; this required me to take it apart. Almost immediately after removing the dustbin and top cover, I was greeted with a funny looking interface of some kind.

Alt text

This was a Mini-DIN connector, a smaller version of the connector which is commonly used for MIDI peripherals. I took note of this, and decided to investigate it further when I got the Roomba to power up.

Afterwards, I was able to find the proper power orientation for the Roomba battery, and manually powered it with a benchtop power supply at approximately 12V. The device turned on, and made a sound indicating that the battery was missing. After fixing this by shorting the two thermistor(?) pins in the battery connector region, I was able to get the Roomba to operate normally.

After doing some digging, I was able to discover that this was a serial communication interface (or, as they call it, Roomba SCI).

Because of this discovery, when I was putting it back together, I decided to drill a hole in the chassis cover over the interface. By default, this is covered, so I would have to remove the entire top cover if I want to access it. This makes it so it is significantly easier to work with.

Alt text

Serial Communication Interface (SCI)

When looking into the SCI, I was able to find the Roomba SCI Specification, which tells me which command corresponds to what action. It turns out that the Roomba SCI provides full access and control to the Roomba's motors, sensors, and actuators.

Physical Connection Interface

The pinout for it is as follows:
Alt text

It is very important to understand how this interface works before we start trying to use it.

Talking to the Roomba - Wiring

The SCI uses the Universal Asynchronous Receiver-Transmitter (UART) protocol, which is a simple two-wire communication protocol.

The protocol works fairly simply: if you have two devices A and B, the transmit (TX) line of A is connected to the receive (RX) line of B, and the transmit line of B is connected to the receive line of A.

From there, if A wants to send a byte to B, A will (assuming a standard 8N1 UART configuration):

The same process goes for if B wants to send a message to A.

The receiving device will then parse the bits at a given rate that it is configured to read at. This rate is known as the baud rate, which is measured in bits per second. It is important that both devices that are communicating have the same baud rate, otherwise there will be miscommunication.

In our case, A would be our serial communication tool. I will use an Espressif ESP32 Dev Module, since it is the only thing I have on hand. However, since the ESP32 has a logic level of 3.3V, we must use a bidirectional level shifter to step the ESP32's communication voltages up to 5V. Otherwise, when the Roomba sends 5V to the ESP32's 3.3V GPIO pins, the ESP32 will get damaged as it is not rated for that voltage.

Of course, B will be our Roomba, since it should be receiving the commands. The bytes we are sending from A to B will correspond to commands for the Roomba, each of which have varying parameters.

Based on the above information, we can wire up our ESP32 Dev Module. The wiring is as follows:
Alt text

Note: I used an ESP32 Dev Module, so I was able to program it over USB with a USB-Serial converter (CH340G).

Talking to the Roomba - Programming

Now that we've wired up the Roomba, we need to send commands to it to control it. Here is a short list, from the quick reference section of the documentation.

Alt text

In the control flow presented by the documentation, the first command we need to send is CMD_START (0x80/128). This command takes no parameters, so we just need to send this one byte.

Afterwards, we need to tell the Roomba exactly how much control this serial interface has on its operations.

To do this:

After that, we can do as much as we want, and send any command we want!

The code I wrote to achieve this initial with the ESP32 is as follows:

#define WAKEUP 4
#define RX2 16
#define TX2 17

HardwareSerial g_RoombaSerial(1);

enum roomba_cmd {
  CMD_START = 128,
  CMD_CONTROL = 130,
  CMD_FULL = 132
};

void setup() {
  Serial.begin(9600);
  pinMode(WAKEUP, OUTPUT);
  // Software serial to instrument Roomba.
  g_RoombaSerial.begin(115200, SERIAL_8N1, RX2, TX2, false);

  // Put Roomba in full control mode.
  delay(5000);
  digitalWrite(WAKEUP, LOW);
  delay(10);
  digitalWrite(WAKEUP, HIGH);
  delay(10);
  g_RoombaSerial.write((uint8_t) CMD_START);
  g_RoombaSerial.write((uint8_t) CMD_CONTROL);
  g_RoombaSerial.write((uint8_t) CMD_FULL);
}

void loop() {
}

Essentially, we wait 5 seconds for the ESP32 to fully turn on, then toggle the WAKEUP pin to force the Roomba to wake up the SCI interface. Afterwards, we send the commands requesting full control.

Gran-Turismo Roomba

As a test, let us try to manually drive the Roomba around through SCI. But, I want to use a controller, since it would give us more precision. As such, I stumbled upon Bluepad32, which allows me to use my game controllers on the ESP32.

I mapped each button on the controller to a corresponding action on the Roomba.

For example, the joystick controls the rotation of the Roomba, the right trigger controls the forward velocity, the left trigger controls the backwards velocity, and the cross & circle buttons control the state of the vaccums (on/off).

From there, whenever the corresponding action is pressed, I send the corresponding Roomba command (which I dynamically create based on the structure in the SCI specification) to the serial interface we created.

The code I used to do this is below.

#include 

#define RX2 16
#define TX2 17
#define DEADZONE 16

ControllerPtr connected_controller;
HardwareSerial g_RoombaSerial(1);

int16_t g_PreviousSpeed = 0, g_PreviousRotationSpeed = 0;

void onConnect(ControllerPtr ctl);
void onDisconnect(ControllerPtr ctl);

enum roomba_cmd {
  CMD_START = 128,
  CMD_CONTROL = 130,
  CMD_FULL = 132,
  CMD_SENSOR = 148,
};

void setup() {
  Serial.begin(9600);
  pinMode(4, OUTPUT);
  BP32.setup(&onConnect, &onDisconnect);
  BP32.forgetBluetoothKeys();
  BP32.enableNewBluetoothConnections(true);
  
  // Software serial to instrument Roomba.
  g_RoombaSerial.begin(115200, SERIAL_8N1, RX2, TX2, false);
  RoombaInit();
}

void onConnect(ControllerPtr ctl) {
  if (connected_controller == nullptr) {
    connected_controller = ctl;
    ControllerProperties properties = ctl->getProperties();
    char buf[80];
    sprintf(buf, "BTAddr: %02x:%02x:%02x:%02x:%02x:%02x, VID/PID: %04x:%04x, flags: 0x%02x", properties.btaddr[0], properties.btaddr[1], properties.btaddr[2], properties.btaddr[3], properties.btaddr[4], properties.btaddr[5], properties.vendor_id, properties.product_id, properties.flags);
    Serial.println(buf);
  } else {
    Serial.println("Controller already connected; discarding.");
  }
}

void onDisconnect(ControllerPtr ctl) {
  connected_controller = nullptr; 
}

void HandleGamepad(ControllerPtr gamepad) {
  int16_t c_NewSpeed = 0, c_NewRotationSpeed = 0;
  
  if (gamepad->throttle() > 20) {
    c_NewSpeed = floor(gamepad->throttle() * 500.0 / 1024.0);
  } else if (gamepad->brake() > 20) {
    c_NewSpeed = -floor(gamepad->brake() * 500.0 / 1024.0);
  }

  if (gamepad->axisX() < -DEADZONE || gamepad->axisX() > DEADZONE) {
    c_NewRotationSpeed = floor(gamepad->axisX() * 200.0 / 512.0);
    if (!c_NewSpeed && c_NewRotationSpeed < 0) {
      c_NewRotationSpeed = -1;
      c_NewSpeed = abs(floor(gamepad->axisX() * 500.0 / 512.0));
    } else if (!c_NewSpeed && c_NewRotationSpeed > 0) {
      c_NewRotationSpeed = 1;
      c_NewSpeed = abs(floor(gamepad->axisX() * 500.0 / 512.0));
    }
  } else {
    c_NewRotationSpeed = 0x8000;
  }

  if (g_PreviousSpeed != c_NewSpeed || g_PreviousRotationSpeed != c_NewRotationSpeed) {
    // Send command.
    Serial.print("Speed: ");
    Serial.println(c_NewSpeed);
    Serial.print("Rotation: ");
    Serial.println(gamepad->axisX());
    g_RoombaSerial.write((uint8_t) 137);
    g_RoombaSerial.write((uint8_t) (c_NewSpeed >> 0x8) & 0xFF);
    g_RoombaSerial.write((uint8_t) (c_NewSpeed) & 0xFF);
    g_RoombaSerial.write((uint8_t) (c_NewRotationSpeed >> 0x8) & 0xFF);
    g_RoombaSerial.write((uint8_t) c_NewRotationSpeed & 0xFF);
    delay(10);
  }

  g_PreviousSpeed = c_NewSpeed;
  g_PreviousRotationSpeed = c_NewRotationSpeed;

  if (gamepad->buttons() & 1) {
    g_RoombaSerial.write((uint8_t) 138);
    g_RoombaSerial.write((uint8_t) 7);
  } else if (gamepad->buttons() & 2) {
    g_RoombaSerial.write((uint8_t) 138);
    g_RoombaSerial.write((uint8_t) 0);
  }
}

void loop() {
  BP32.update();
  if (connected_controller) {
    HandleGamepad(connected_controller);
  }
}

void RoombaInit() {
  // Put Roomba in full control mode.
  delay(5000);
  digitalWrite(4, LOW);
  delay(10);
  digitalWrite(4, HIGH);
  delay(10);
  g_RoombaSerial.write((uint8_t) CMD_START);
  g_RoombaSerial.write((uint8_t) CMD_CONTROL);
  g_RoombaSerial.write((uint8_t) CMD_FULL);
}

As a result, we were able to get the Roomba to move to where I want it to (including spinning it on its own axis, angled turns, etc), and clean stuff up with the vaccum.

<Video Here?>

The Ultimate Plan

So, now we're ready to make the Roomba smarter! One of my roommates and I came up with an idea to try and make the machine smarter: hacking on it to implement a better mapping algorithm for our apartment.

Since the Roomba can now be interfaced with a microcontroller like our ESP32, we can very easily instrument the Roomba, and even implement features which iRobot never did, such as WiFi and Bluetooth support (for our own purposes). This gives us increased control, and allows us to use the Roomba as a more versatile tool.

So, ideally, what we can do is use the Roomba to collect data, and build a map of our apartment. From there, we can use a path planning algorithm to find a more optimal cleaning path, compared to the "traveling salesman" approach we took previously.

I will update this eventually with the progress.