RoombaをXBeeで制御してみる

Roomba(500シリーズ, 560)をXBee Series 2で制御してみました。
先駆者が多数いるので、今更感ありますが、家電を自分で操作できるって楽しい。


XBeeは、
Baud Rateを115200に、DLをFFFFにした位で他はそんなに触っていません。
いわゆる透過モード(ATモード)で、単純にシリアルの信号をワイヤレスにしただけって感じです。


Roombaは、ROIと呼ばれるAPI( http://www.irobot.lv/uploaded_files/File/iRobot_Roomba_500_Open_Interface_Spec.pdf )が公開されていて、
更に本体にDINインターフェースがあるので、シリアルで通信可能です。
ピンアサインは下記。

(via http://blog.majide.com/2010/04/roomba-usb-connected/)


繋ぐ際に気をつける事は、Vpwrが16V近く流れているので、下手に5Vの端子にすると死ぬ。
ので、FTDIを使う際は上部図のように、GNDのみ共通化する。



XBeeを使う際は、もちろんXBeeのVccは3.3Vなので、ちゃんとレギュレータかませてからやりましょう。今回は秋月の変換基板使いました( http://akizukidenshi.com/catalog/g/gP-05060/ )。
VpwrはCN1の1ピンに繋ぐ事。間違えても、CN2の1ピンには繋がないで(死ぬから)。
Rx, Txは直接つないでも大丈夫みたい(GND共通化忘れずに)。


僕は最初にFTDI( https://www.sparkfun.com/products/9873 )で直接繋いで、コマンドの疎通を行なってから、XBeeで実験してみました。



シリアルのモニターは、最近X-CTU(Windowsでのみ動作)についているAssemble Packetがコマンドそのままで信号送れるので、その便利さを覚えてそちらを使ってます。


送信コマンドの書式は、おおまかには下記の通り。

[OP Code][Data Bytes:1][Data Bytes:2][Data Bytes:3][Data Bytes:4][ETC]

例: 前進
0x89 0x00 0xC8 0x80 0x00

例: 後退
0x89 0xFF 0x38 0x80 0x00

例: 停止
0x89 0x00 0x00 0x00 0x00

例: 回転
0x89 0x00 0xC8 0xFF 0xFF

例: LEDを緑に
0x8B 0x04 0x00 0x80



P5で簡易コード書いてみた
GUIで操作できるようにしてますが、OSCでも制御できるようにしています。

// ルンバを制御するコマンドを送るプログラム
// created at 2013.10.12
// 
// Arduinoを介して行う場合は(int commandMode = USE_ARDUINO)に、
// XBeeから直接制御する場合は(int commandMode = USE_XBEE)にする
//
// P5のGUIアプリから操作する方法と、OSCから信号受けてそれを送る両方に対応済
//
import java.awt.*;
import javax.swing.*;
import processing.serial.*;
import controlP5.*;
import oscP5.*;
import netP5.*;

final int WINDOW_WIDTH   = 640;
final int WINDOW_HEIGHT  = 540;
final int USE_XBEE       = 1;
final int USE_ARDUINO    = 2;
final int BAUD_RATE      = 115200;
final int OSC_PORT       = 8000;
final String OSC_ADDRESS = "127.0.0.1";
final String COM_PORT    = "/dev/tty.usbserial-XXX";

ControlP5 controlP5;
Textfield ipAddressField, commandField;
Serial serialDevice;
OscP5 oscP5;

String comPort = COM_PORT;
int commandMode = USE_XBEE; // or USE_ARDUINO

void setup() {
  size(WINDOW_WIDTH, WINDOW_HEIGHT);
  smooth();
  background(color(255));
  frameRate(30);

  try {
    if (comPort.equals("")) {
      comPort = (Serial.list())[0];
    }
    serialDevice = new Serial(this, comPort, BAUD_RATE);
    
    println("Serial port: " + COM_PORT);
  }
  catch (Exception e) {
    println("Serial initialized error:" + e);
  }

  try {
    OscProperties oscProperties = new OscProperties();
    oscProperties.setDatagramSize(10000); 
    oscProperties.setListeningPort(OSC_PORT);

    oscP5 = new OscP5(this, oscProperties);
  }
  catch (Exception e) {
    println("[!exception]OSC Error:" + e);
  }

  initWindow();
}

void initWindow() {
  controlP5 = new ControlP5(this);

  // title
  Textlabel titleLabel = controlP5.addTextlabel("labelT", "Roomba Control Panel", 20, 20);
  titleLabel.setColorValue(0xCC9999);
  titleLabel.setHeight(30);
  titleLabel.setWidth(400);
  titleLabel.setFont(ControlP5.synt24);

  // menu frame
  fill(10, 10, 10);
  rect(20, 70, WINDOW_WIDTH - 40, WINDOW_HEIGHT - 120);

  createDirectButton();
}

void createDirectButton() {
  controlP5.Button sm1Button = controlP5.addButton("Start", 2, 30, 80, 100, 20);
  sm1Button.setId(10);
  controlP5.Button sm2Button = controlP5.addButton("Stop", 2, 140, 80, 100, 20);
  sm2Button.setId(11);

  controlP5.Button sm3Button = controlP5.addButton("Forward", 2, 30, 120, 100, 20);
  sm3Button.setId(12);
  controlP5.Button sm4Button = controlP5.addButton("Backward", 2, 140, 120, 100, 20);
  sm4Button.setId(13);

  controlP5.Button sm5Button = controlP5.addButton("Turn1", 2, 30, 160, 100, 20);
  sm5Button.setId(14);
  controlP5.Button sm6Button = controlP5.addButton("Turn2", 2, 140, 160, 100, 20);
  sm6Button.setId(15);

  controlP5.Button sm7Button = controlP5.addButton("Sound", 2, 30, 200, 100, 20);
  sm7Button.setId(16);
  //controlP5.Button sm8Button = controlP5.addButton("Test", 2, 140, 200, 100, 20);
  //sm8Button.setId(17);

  controlP5.Button sm9Button = controlP5.addButton("LED(G)", 2, 30, 240, 100, 20);
  sm9Button.setId(18);
  controlP5.Button sm10Button = controlP5.addButton("LED(R)", 2, 140, 240, 100, 20);
  sm10Button.setId(19);

  controlP5.Button sm11Button = controlP5.addButton("Max Clean", 2, 30, 280, 100, 20);
  sm11Button.setId(20);
  controlP5.Button sm12Button = controlP5.addButton("Back Dock", 2, 140, 280, 100, 20);
  sm12Button.setId(21);
}

void draw() {
}

void controlEvent(ControlEvent theEvent) {
  int id = theEvent.controller().id();
  println("got a control event from controller with id " + id);

  sendSignalToRoomba(id);
}

void oscEvent(OscMessage receivedMessage) {
  String message = "### received an osc message." + 
    " addrpattern: " + receivedMessage.addrPattern() + 
    " typetag: " + receivedMessage.typetag() + 
    " timetag: " + receivedMessage.timetag();
  println(message);

  try {
    OscArgument packet = receivedMessage.get(0);
    int id = packet.intValue();
    sendSignalToRoomba(id);
  }
  catch (Exception e) {  }
}

void sendSignalToRoomba(int id) {
  // debug
  //serialDevice.write("A");
  //boolean k = true; if (k == true) { return; }

  println("send signal to roomba: " + id);
  
  // with arduino mode
  if (commandMode == USE_ARDUINO) {
    serialDevice.write(55 + id);
    println("send: " + (char)(55 + id));
  }
  // direct mode
  else if (commandMode == USE_XBEE) {
    // start(create connnection)
    if (id == 10) {
      serialDevice.write(0x80); // 128
      serialDevice.write(0x82); // 130
    }
    // stop
    if (id == 11) {
      serialDevice.write(0x89); // 137
      serialDevice.write(0x00);
      serialDevice.write(0x00);
      serialDevice.write(0x00);
      serialDevice.write(0x00);
    }
    // forward
    if (id == 12) {
      serialDevice.write(0x89); // 137
  
      // Velocity: 0x00c8 = 200
      serialDevice.write(0x00);
      serialDevice.write(0xFF);
  
      serialDevice.write(0x80);  // Radius: 0x8000 = Straight
      serialDevice.write(0x00);
    }
    // backward
    if (id == 13) {
      serialDevice.write(0x89); // 137
  
      // Velocity: 0xFF38 = 65336
      serialDevice.write(0xFF);  
      serialDevice.write(0x00);
  
      serialDevice.write(0x80);  // Radius: 0x8000 = Straight
      serialDevice.write(0x00);
    }
    //  Turn in place clockwise
    if (id == 14) {
      serialDevice.write(0x89); // 137

      serialDevice.write(0x00);
      serialDevice.write(0xC8);

      serialDevice.write(0xFF);
      serialDevice.write(0xFF);
    }
    // Turn in place counter-clockwise
    if (id == 15) {
      serialDevice.write(0x89); // 137

      serialDevice.write(0x00);
      serialDevice.write(0xC8);

      serialDevice.write(0x00);
      serialDevice.write(0x01);
    }
    // Sound
    if (id == 16) {
      serialDevice.write(0x8C); // 140

      serialDevice.write(0x00); // song number 0-4
      serialDevice.write(0x01); // song length 1-16

      serialDevice.write(0x43); // 67
      serialDevice.write(0x20); // 32
  
      serialDevice.write(0x8D); // 141
      serialDevice.write(0x00); // 0
    }
    // Test
    if (id == 17) {
      serialDevice.write(0x90); // 144

      serialDevice.write(0x00); // brush
      serialDevice.write(0x00); // kurukuru
      serialDevice.write(0x00); // vacuum
    }
    // LED Green
    if (id == 18) {
      serialDevice.write(0x8B); // 139
      serialDevice.write(0x04);
      serialDevice.write(0x00);
      serialDevice.write(0x80);
    }
    // LED Red
    if (id == 19) {
      serialDevice.write(0x8B); // 139
      serialDevice.write(0x04);
      serialDevice.write(0xFF);
      serialDevice.write(0x80);
    }
    // Max Clean
    if (id == 20) {
      serialDevice.write(0x88); // 136
    }
    // Back Dock
    if (id == 21) {
      serialDevice.write(0x8F); // 143
    }
    // forward
    if (id == 22) {
      serialDevice.write(0x89); // 137
      serialDevice.write(0xFF);
      serialDevice.write(0x38);
      serialDevice.write(0x01);
      serialDevice.write(0xF4);
    }
  }
}


OSCで送る場合は、下記のような感じで。
Target ip address: 127.0.0.1
Target port: 8000

/test ,i [command id]

[command id]に10~19の任意の数値を入れます。



あ、あと上部のカバー、4つツメがついていて、それを外した後は結構無理矢理ガッてやらないと外れませんでした。
どこにも書いてなくて恐る恐るやってたら凄い時間かかった。