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.

No comments:

Post a Comment