2018年7月1日日曜日

Alexa と Raspberry Piでルンバに掃除してもらう:【最終回】Node-REDからFlashAir/Arduino経由でルンバを動かす

前回、Arduinoからルンバに赤外線コードを送れるようにしました。
今回は、Node-REDからFlashAirを呼び出して、Arduinoの赤外線送信をキックします。

  1. 材料を揃える。
  2. 母艦のRaspberry PiにNode-REDをセットアップしてAmazon Echo Dotに認識させる
  3. Raspberry Pi Zero WをヘッドレスでWiFi対応セットアップする
  4. Raspberry Piで赤外線リモコンのコードをコピーする
  5. Node-REDから赤外線機能を呼び出してテレビを操作する
  6. FlashAirのGPIO機能でArduinoをHTTP API経由で制御する
  7. (おまけ)ArduinoからROI経由でルンバを動かす
  8. Arduinoから赤外線経由でルンバを動かす
  9. Node-REDからFlashAir/Arduino経由でルンバを動かす【今回】

FlashAirのGPIO

FlashAirではSDカードの各ピンをGPIOとして使用できます。
詳しくはFlashAir Developersで説明されています。

準備として、FlashAirのSD_WLANフォルダにある「CONFIG」ファイルに「IFMODE=1」と書き足します。
CONFIGは隠しフォルダになっているのですが、MacやLinuxの場合はTerminalで普通にアクセスできます。
Windowsでも隠しフォルダにアクセスできるツールを使えばOKです。

今回、FlashAir Developersで紹介されているものと同様に秋月電子通商の「SDカードスロットDIP化モジュール」を使いました。
意外に盲点なのですが、このDIP化モジュールはFlashAirの全てのピンが引き出されている訳ではありません。
そのため、FlashAirのGPIOピンを全て使えるわけではありません。

具体的には、DIP化モジュールの取り扱い説明書の回路図を参照すると分かります。
下図の通り、DAT1とDAT2はプルアップ抵抗で3.3Vに繋がっているだけなので、ピンが引き出されていません。
つまり、FlashAirの0x04と0x08を使えません。(使うにはジャンパーを半田付けする必要があります。)

半田付けなしで使用できるピンの対応付けは次のようになります。
「Arduino」列は今回接続したArduino側のピン、「用途」列は今回の用途です。
  1. ビット
    割り当て
    FlashAirDIP化
    モジュール
    Arduino用途
    0x01
    CMD
    SDI
    D5
    ルンバON
    0x02
    DAT0
    SDO
    D4
    スリープ enable/disable
    0x10
    DAT3
    CS
    D2
    ソフトウェアリセット
    -
    3.3V
    VCC
    3.3V
    FlashAirへの給電
    -
    GND
    GND
    GND
    GND

ArduinoとFlashAirの接続

上の表の配線を使って、Arduinoには次のような動作をさせます。
  1. D2ピンのHIGH/LOWが変化するとソフトウェアリセットでスリープから復帰する。
  2. D5ピンがHIGHになっていたらルンバに赤外線送信する。
  3. D4ピンがHIGHになっていたらスリープする。
ルンバの起動にしか使わないのであればD5ピン無しでも可能です。
とは言え、DAT2, DAT3 を外部に引き出すと、WiFi経由で合計3ビット分のコマンドを表現できるので、
8種類(2の3乗)の赤外線コードを出し分けられます。
そのような将来的な拡張を考慮してD5ピンにFlashAirのCMDを割り当てました。

Node-REDからFlashAir

Node-REDからFlashAirへは次のようにHTTPリクエストを出すことでArduinoを動作させます。
  1. FlashAirの全てのピンをLOWにする。つまりFlashAirに0x00を送信する。(http://〜/command.cgi?op=190&CTRL=0x1f&DATA=0x00)
  2. 一秒待つ(1.がFlashAir側で確実に受け取り完了させるため)
  3. FlashAirの0x10, 0x01, 0x02 をHIGHにする。つまり、FlashAirに0x13を送信する。(http://〜/command.cgi?op=190&CTRL=0x1f&DATA=0x13)
このリクエストによって、DAT3(0x10)がLOWからHIGHになります。
つまり、ArduinoのリセットピンをLOWからHIGHに変えることで、Arduinoをスリープから復帰させます。

スリープ復帰時にArduinoがCMD(0x01)を参照してルンバに赤外線コードを送信してくれれば、ルンバが起動します。

さらにその後、ArduinoがDAT0(0x02)を参照し、HIGHの時にスリープさせることで省電力化します。
本当は、LOWでスリープさせた方が省電力になるのかもしれません。
ところが、FlashAirは最初に電源が入った時点で全てのGPIOピンをHIGHにしてしまいます。
つまり、DAT0=LOWでスリープに入るようにしてしまうと、電源投入時にスリープしなくなってしまいます。
DAT0=HIGHをスリープに割り当てておけば電源投入直後にいきなりスリープしてくれます。

Arduinoのコーディング

以上の動作をArduinoに組み込むと次のようになります。
赤外線の記憶と送信に関しては前回と同じなので省略します。
#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!");
}

// リセットからの復帰時はloopさえ動けば良いので特に処理無し
void wakeup(){
  Serial.println("Wakeup");
}

// Arduino起動直後は全てHIGHになっているがSETUP_FLAGを使って無視させる
// 一旦LOWに落ちてからHIGHになると清掃開始
void loop() {
  //Serial.println(".");
  Serial.println("Run");
  if ( ! SETUP_FLAG ) {
    if ( digitalRead(PIN_SD_CLEAN) == HIGH ){
      Serial.println("Run Roomba in clean mode");
      loadBuffer(0);
      executeIR();
      executeIR();
      executeIR();
    }
    // FlashAirの他のピンを引き出してコマンド拡張する場合はここに書く
  }
  SETUP_FLAG = false;
  delay(500);
  check_sleep(digitalRead(PIN_SD_SLEEP));
  delay(500);
}

// sleep pinがHIGHだったらスリープ
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();
}

完成

以上で完成です。
テレビ、プロジェクター、ルンバを音声操作できました。
紹介しませんでしたが、Node-REDを工夫すると1つの音声コマンドで複数機器を同時に動作させることも可能です。
この動画の例ではプロジェクターを起動する際にアンプの音声出力をテレビからプロジェクターに切り替えています。

0 件のコメント:

コメントを投稿