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つツメがついていて、それを外した後は結構無理矢理ガッてやらないと外れませんでした。
どこにも書いてなくて恐る恐るやってたら凄い時間かかった。