Showing posts with label ルンバ. Show all posts
Showing posts with label ルンバ. Show all posts

Sunday, July 1, 2018

Commanding a Roomba with Alexa and Raspberry Pi [Final]: Node-RED → FlashAir → Arduino → Roomba

Last time we got Arduino sending IR codes to the Roomba.
This time we wire up Node-RED to call FlashAir, which in turn kicks Arduino to fire the IR transmission.

  1. Gather parts.
  2. Set up Node-RED on the main Raspberry Pi and register it with Amazon Echo Dot
  3. Headless WiFi setup for Raspberry Pi Zero W
  4. Copy IR remote codes with Raspberry Pi
  5. Call IR functions from Node-RED to control the TV
  6. Control Arduino via HTTP API using FlashAir GPIO mode
  7. (Bonus) Control Roomba from Arduino via ROI serial interface
  8. Control Roomba from Arduino via IR
  9. Control Roomba from Node-RED via FlashAir/Arduino [This article]

FlashAir GPIO

FlashAir can use each SD card pin as a GPIO pin.
See FlashAir Developers for details.

As preparation, add "IFMODE=1" to the "CONFIG" file in FlashAir's SD_WLAN folder.
CONFIG is in a hidden folder, but on Mac or Linux you can access it from Terminal without any special tools.
On Windows, use any tool that can show hidden folders.

For this build I used the same SD card slot DIP adapter from Akizuki Denshi that FlashAir Developers recommends.
One thing that catches people off guard: this DIP adapter does not expose all of FlashAir's pins.
That means not all FlashAir GPIO pins are accessible.

Specifically, as shown in the DIP adapter schematic, DAT1 and DAT2 are only connected to 3.3V via pull-up resistors — their pads are not broken out.
So FlashAir bits 0x04 and 0x08 are unavailable without soldering jumpers.

The pin mappings available without soldering are shown below.
The "Arduino" column shows which Arduino pin was used, and "Purpose" shows the function assigned in this build.
  1. Bit
    Mask
    FlashAirDIP
    Module
    ArduinoPurpose
    0x01
    CMD
    SDI
    D5
    Roomba ON
    0x02
    DAT0
    SDO
    D4
    Sleep enable/disable
    0x10
    DAT3
    CS
    D2
    Software reset
    -
    3.3V
    VCC
    3.3V
    Power to FlashAir
    -
    GND
    GND
    GND
    GND

Arduino and FlashAir Wiring

Using the wiring above, the Arduino is programmed to behave as follows:
  1. When pin D2 changes state, a software reset wakes Arduino from sleep.
  2. If pin D5 is HIGH, send IR codes to the Roomba.
  3. If pin D4 is HIGH, enter sleep mode.
If you only need to start the Roomba, D5 can be omitted.
That said, with DAT2 and DAT3 broken out, you can represent 3 bits of commands over WiFi,
giving you 8 possible IR codes (2³).
The CMD pin was assigned to D5 with that future expansion in mind.

Node-RED to FlashAir

From Node-RED, send HTTP requests to FlashAir in this sequence to trigger Arduino:
  1. Set all FlashAir pins LOW — send 0x00 to FlashAir. (http://~/command.cgi?op=190&CTRL=0x1f&DATA=0x00)
  2. Wait one second (to ensure FlashAir finishes processing step 1).
  3. Set FlashAir bits 0x10, 0x01, 0x02 HIGH — send 0x13 to FlashAir. (http://~/command.cgi?op=190&CTRL=0x1f&DATA=0x13)
This request transitions DAT3 (0x10) from LOW to HIGH.
That LOW-to-HIGH change on Arduino's reset pin wakes it from sleep.

On wakeup, Arduino checks CMD (0x01) and, if HIGH, sends the IR code to start the Roomba.

After that, Arduino checks DAT0 (0x02) and goes back to sleep when it's HIGH, saving power.
Logically, LOW-to-sleep would be more power-efficient — but FlashAir sets all GPIO pins HIGH at power-on.
If sleep was triggered by DAT0=LOW, the Arduino would never sleep at startup.
Assigning DAT0=HIGH as the sleep trigger means Arduino sleeps immediately on power-on, which is what we want.

Arduino Code

The full Arduino sketch implementing the above logic. IR recording and transmission are the same as last time and omitted here.
#include <EEPROM.h>
#include <avr/sleep.h>
#include <avr/interrupt.h>

// Arduino - board - SD
// 2 pin   - CS    - DAT3 - 1pin - 0x10 (RESET)
// 4 pin   - SDO   - DAT0 - 7pin - 0x02 (SLEEP)
// 5 pin   - SDI   - CMD  - 2pin - 0x01 (CLEAN)

#define PIN_IR 10
#define PIN_IR_SENSOR 11

// FlashAir SDD pin (0x02)
#define PIN_SD_SLEEP 4

// FlashAir SDI pin (0x01)
#define PIN_SD_CLEAN 5

#define SCAN_INITIAL_TIMEOUT 10000000
#define SCAN_TIMEOUT 10000000
#define IR_BUF_LEN 64

volatile int IR_BUF[IR_BUF_LEN];
volatile bool SETUP_FLAG = false;

void setup() {
  Serial.begin(9600);
  SETUP_FLAG = true;
  pinMode(PIN_IR, OUTPUT);
  pinMode(PIN_IR_SENSOR, INPUT);
  pinMode(PIN_SD_CLEAN, INPUT);
  pinMode(PIN_SD_SLEEP, INPUT);
  pinMode(2, INPUT_PULLUP);

  Serial.println("Start!");
}

// Nothing to do on reset wakeup — just let loop() run
void wakeup(){
  Serial.println("Wakeup");
}

// Ignore initial HIGH state at power-on via SETUP_FLAG.
// Cleaning starts only after a LOW-to-HIGH transition on PIN_SD_CLEAN.
void loop() {
  Serial.println("Run");
  if ( ! SETUP_FLAG ) {
    if ( digitalRead(PIN_SD_CLEAN) == HIGH ){
      Serial.println("Run Roomba in clean mode");
      loadBuffer(0);
      executeIR();
      executeIR();
      executeIR();
    }
    // Add more commands here if additional FlashAir pins are broken out
  }
  SETUP_FLAG = false;
  delay(500);
  check_sleep(digitalRead(PIN_SD_SLEEP));
  delay(500);
}

// Sleep when sleep pin is HIGH
void check_sleep(bool flag) {
  if ( ! flag ) { return; }
  Serial.println("Sleep");
  delay(1000);
  // Wake up when reset pin changes state
  attachInterrupt(0, wakeup, CHANGE);
  set_sleep_mode(SLEEP_MODE_STANDBY);
  sleep_enable();
  sleep_mode();
  sleep_disable();
}

Done

That's the full system complete.
TV, projector, and Roomba can all be voice-controlled.
Not covered here, but with a bit of Node-RED flow design you can trigger multiple devices simultaneously from a single voice command.
In the video below, launching the projector automatically switches the amplifier's audio output from TV to projector.

Sunday, June 17, 2018

Commanding a Roomba with Alexa and Raspberry Pi (6): Controlling Roomba from Arduino via IR

Last time we covered controlling the Roomba via ROI over serial.
ROI allows fine-grained control, but since it's a wired serial connection, the Arduino has to ride on the Roomba itself.
Powering the Arduino from the Roomba and embedding it is possible, but it's a lot of work.
Since all we need is simple power ON/OFF, connecting via IR without any wired connection to the Roomba is the cleaner approach.

When I tried capturing the Roomba's remote codes with lirc, it failed with errors.
The carrier seems to be 38 kHz like a TV remote, but the signal may simply be too long.

So the plan is: use Arduino to capture the IR remote codes, then replay them to the Roomba with an IR LED.
It's the same idea as Raspberry Pi + lirc, but in Arduino style — which means a bit more hands-on.
  1. Gather parts.
  2. Set up Node-RED on the main Raspberry Pi and register it with Amazon Echo Dot
  3. Headless WiFi setup for Raspberry Pi Zero W
  4. Copy IR remote codes with Raspberry Pi
  5. Call IR functions from Node-RED to control the TV
  6. Control Arduino via HTTP API using FlashAir GPIO mode
  7. (Bonus) Control Roomba from Arduino via ROI serial interface
  8. Control Roomba from Arduino via IR [This article]
  9. Control Roomba from Node-RED via FlashAir/Arduino

Building the Circuit

Connect an IR receiver module and an IR transmitter module to the Arduino.
The receiver is only needed while recording the remote codes — it can be removed afterward.


Writing Remote Codes to Arduino EEPROM

The idea is to measure the timing of HIGH/LOW transitions on the IR receiver's signal pin and store that array in EEPROM.
This sketch can record two different remote codes.

#include <EEPROM.h>

#define PIN_IR_SENSOR 11

#define SCAN_INITIAL_TIMEOUT 10000000
#define SCAN_TIMEOUT 10000000
#define IR_BUF_LEN 64

volatile int IR_BUF[IR_BUF_LEN];
volatile bool SETUP_FLAG = false;

void setup() {
  Serial.begin(9600);
  SETUP_FLAG = true;
  pinMode(PIN_IR_SENSOR, INPUT);
  updateIR(0);
  updateIR(1);
}

int updateIR(int pos) {
  Serial.println("SCAN START");
  memset(IR_BUF,0,IR_BUF_LEN);
  for( int i=0; i<3; i++ ){
    Serial.print("PUSH BUTTON ");
    Serial.println(pos);
    int result = scanIR(i);
    for(int j=0; j<result; j++){
      Serial.print(IR_BUF[j]);
      Serial.print(" ");
    }
    Serial.print("\n");
    Serial.print(result);
    Serial.println(" bytes were read");
    delay(3000);
  }
  saveBuffer(pos * IR_BUF_LEN*2);
  Serial.println("SCAN END");
  return 1;
}


int scanIR(int repeat) {
  int idx=0;
  int ir_status = HIGH;
  unsigned long lastStatusChanged = 0;
  unsigned long timeout = SCAN_INITIAL_TIMEOUT;
  while(1){
    if ( ir_status == LOW ){
      while( digitalRead(PIN_IR_SENSOR) == LOW){
        // wait
        ;
      }
    } else {
      if( wait_high_signal( micros() + timeout ) < 0 ){
        break;
      }
    }

    unsigned long now = micros();
    if( lastStatusChanged > 0 ){
      int val = (int)((now - lastStatusChanged) / 10);
      if( repeat > 0 ){
        if( IR_BUF[idx] > 1 ){ break; }
        if( abs((int)(IR_BUF[idx] - (int)val)) >  IR_BUF[idx]*0.3 ){ idx = 0; break; }
        IR_BUF[idx] = (int)( IR_BUF[idx] * repeat + val ) / (repeat + 1);
      } else {
        IR_BUF[idx] = val;
      }
      idx++;
      if( idx == IR_BUF_LEN -1 ){ break; }
    }
    lastStatusChanged = now;
    if (ir_status == HIGH) {
      ir_status = LOW;
    } else {
      ir_status = HIGH;
    }
    timeout = SCAN_TIMEOUT;
  } // while 1

  if( idx > 0 ){ IR_BUF[idx] = -1; }
  return idx;
}

int wait_high_signal(unsigned long timeout) {
  while( digitalRead(PIN_IR_SENSOR) == HIGH ){
    if( micros() > timeout ) { return -1; }
  }
  return 1;
}

void saveBuffer(int pos) {
  Serial.println("SAVE START");
  byte buf;
  for( int i=0; i < IR_BUF_LEN; i++ ){
    buf = lowByte(IR_BUF[i]);
    EEPROM.write( pos+i*2, buf );
    delay(5);
    buf = highByte(IR_BUF[i]);
    EEPROM.write( pos+i*2+1, buf );
    delay(5);
    if( buf < 0 ){ break; }
  }
  Serial.println("SAVE END");
}

Replaying the Stored Code via IR Transmission

This sketch reads the timing array from EEPROM and pulses the IR LED at those intervals.
My Roomba often doesn't respond to a single transmission (might be specific to my unit),
so the code sends the signal three times in a row.
Since the loop function would keep sending forever, the Arduino enters sleep mode after finishing.

#include <EEPROM.h>

#define PIN_IR 10

#define IR_BUF_LEN 64

volatile int IR_BUF[IR_BUF_LEN];

void setup() {
  Serial.begin(9600);
  pinMode(PIN_IR, OUTPUT);
  Serial.println("Start!");
}

void loop() {
  Serial.println("Run");
  loadBuffer(0);
  executeIR();
  executeIR();
  executeIR();
  check_sleep(true);
}

void wakeup(){
  Serial.println("Wakeup");
}
void check_sleep(bool flag) {
  if ( ! flag ) { return; }
  Serial.println("Sleep");
  delay(1000);
  attachInterrupt(0, wakeup, CHANGE);
  set_sleep_mode(SLEEP_MODE_STANDBY);
  sleep_enable();
  sleep_mode();
  sleep_disable();
}

void loadBuffer(int pos) {
  Serial.println("LOAD START");
  int h, l;
  for( int i=0; i<IR_BUF_LEN; i++ ){
    l = EEPROM.read(pos+i*2);
    h = EEPROM.read(pos+i*2+1);
    IR_BUF[i] = (h << 8) + l;

    Serial.print(IR_BUF[i]);
    Serial.print(" ");
    if( IR_BUF[i] < 0 ){ break; }
  }
  Serial.println("\nLOAD END");
}

void executeIR() {
  for(int i=0; IR_BUF[i] > 0; i++){
    unsigned long len = (unsigned long)IR_BUF[i] * 10;
    unsigned long now = micros();

    do {
      digitalWrite(PIN_IR, 1-i&1);
      delayMicroseconds(8);
      digitalWrite(PIN_IR, 0);
      delayMicroseconds(7);
    } while( now + len > micros() );
  }
}


At this point, Arduino can send the "start cleaning" command to the Roomba via IR.
The issue is that in the current form, the signal only fires once — right when the Arduino powers on.
Next time: modify the setup so Arduino wakes from sleep when it receives a request from Node-RED via FlashAir.

Sunday, April 29, 2018

Commanding a Roomba with Alexa and Raspberry Pi (Bonus): Controlling Roomba from Arduino via ROI Serial Interface

In the previous article we got Node-RED calling Arduino via Alexa.
Now, there are several ways to control the Roomba from Arduino.

One is the ROI (Roomba Open Interface) covered in this article.
The advantage is that the Arduino rides on the Roomba itself, so the connection stays live as the Roomba moves around.
The downside is that something sticks out of the Roomba — which risks snagging on furniture during a cleaning run.

The other approach is to leave Arduino in a fixed location and control the Roomba via IR.
Nothing needs to be attached to the Roomba.
The trade-off: you can only send commands while the Roomba is at its dock.

In practice, the Roomba returns to the dock automatically after cleaning, so you rarely need to send commands mid-run.
For simply starting a clean cycle, the IR approach is the better primary option.

That's why ROI control is presented here as a bonus.
  1. Gather parts.
  2. Set up Node-RED on the main Raspberry Pi and register it with Amazon Echo Dot
  3. Headless WiFi setup for Raspberry Pi Zero W
  4. Copy IR remote codes with Raspberry Pi
  5. Call IR functions from Node-RED to control the TV
  6. Control Arduino via HTTP API using FlashAir GPIO mode
  7. (Bonus) Control Roomba from Arduino via ROI serial interface [This article]
  8. Control Roomba from Arduino via IR
  9. Control Roomba from Node-RED via FlashAir/Arduino

What is ROI?

ROI is a serial interface for controlling the Roomba from external devices.
The full ROI specification is available on iRobot's site.

The connector location varies by model — on my Roomba 760, it's on the underside of the top handle.

On some other models, the connector is hidden under the top panel.

Connecting Arduino to the Roomba

The ROI uses a 7-pin mini-DIN connector.
You can find plugs at electronics retailers like Akihabara, but jumper wires inserted directly into the socket also work fine.

Pin assignments are in the ROI specification.


For this build, only pins 3 (RXD), 4 (TXD), and 6 (GND) need to be connected.
Arduino's hardware serial pins are 0 and 1, but using them blocks PC serial communication and makes debugging difficult.
To avoid that, I connected to other pins and used SoftwareSerial.

If you want to power the Arduino from the Roomba, tap pin 1 of the ROI connector, run it through a regulator, and feed it into Arduino's Vin pin.

Sending Commands from Arduino

I added Roomba serial communication to the Arduino code from last time (which handled the FlashAir connection).
In the example below, Arduino pins 12 and 13 are connected to the Roomba's TXD and RXD respectively.
The Roomba's default baud rate is 115200.
#include <SoftwareSerial.h>

#define PIN_SD_CLEAN 11

SoftwareSerial device(12, 13);

void setup() {
  Serial.begin(9600);
  pinMode(PIN_SD_CLEAN, INPUT);
}

// Ignore the initial HIGH state right after Arduino boots.
// Cleaning starts only after a LOW-to-HIGH transition.
volatile bool clean_flag = HIGH;
void loop() {
  if ( clean_flag == LOW && digitalRead(PIN_SD_CLEAN) == HIGH ){
    Serial.println("Run Roomba with clean mode");
    device.begin(115200);
    byte buffer[] = {
      byte(128), // Start
      byte(135)  // Clean
    };
    device.write(buffer, 2);
  }
  clean_flag = digitalRead(PIN_SD_CLEAN);
  delay(1000);
}
That's all there is to it.
Each ROI command is one byte, and you send bytes in sequence — the Roomba executes them in order.
The Roomba has several operating modes; sending low-level commands requires switching modes first.
Byte 128 is "Start" — after startup, the default mode is Passive.
In Passive mode, you can read sensors and start a clean cycle.
Since all we want is to start cleaning, we jump straight to the Clean command (135).

Modes and commands are all described in the ROI specification.
With it, you can turn the Roomba into a remote-controlled robot, play sounds, set indicator lights, and more.

Result

Here's the finished system in action:

Next time: triggering the Roomba via IR instead of ROI.

Wednesday, October 14, 2015

Repairing a Roomba's Side Brush Module

Check on Amazon A Roomba unexpectedly came into my life.
It was pretty well-used when I got it, and after running it for a while the side brush (spinning brush) stopped working — so time to repair it.

Replacement parts


Check on Amazon
Many robot vacuum brands are on the market these days, but one thing Roomba does better than the rest may be this: parts are available on Amazon.
Main brushes, tires, and other components are all sold individually.

Roomba seems to treat its drivetrain parts as consumables.
The internals are completely modular, and any module can be swapped out with a single screwdriver.

Of course, arguably the even better thing is that the internal API spec is publicly documented, and you can access the serial port.
But that's a story for another time.