LEGO : LEGO MINDSTORMS NXTとPCを連携(その2) - NintendoDS、Flashでコントロール
- Posted at: 2008年6月12日 11:32
- Update: 2008年7月18日 12:08

iCommandを使って、PCとか他のガジェットからコントロールできるRCカーとか作ってみまーす。構成は↑な感じ。鯖とリモコンはそれぞれソケット通信でデータをやりとりします。Bluetoothのデバイスとして認識されているNXTは、シリアルポートを通じてPCと通信をします。鯖とNXTは、Javaでシリアル通信をするためにRXTXライブラリを使い、NXT上のマイコンに命令を送るわけです。
NintendoDSとMINDSTORMSを連携させてみる。
FlashとDSの連携に使ったServerに、iCommandを組み込んでリモコンにします。DS側はボタンのダンエッジとか少し変更。鯖側は、データを受信してそれをクライント全員に返すだけだったので、JSONを扱えるようにします。DSからボタンごとのステータスが送られてくるので、対応させた動きをiCommandのAPIで追加していきます。
NXTと通信させる。
NXTCommand.open(); // Bluetoothの通信を接続 NXTCommand.close(); // Bluetoothの通信を切断
モーターを動かす。
Motor.A.forward(); // ポートAに接続したモーターを前に回転。 Motor.A.backward(); // ポートAに接続したモーターを後に回転。 Motor.A.stop(); // ポートAに接続したモーターを停止。
ビープ音を鳴らす。
Sound.playTone(n, w); // トーン n を w ミリ秒間鳴らす。
できたもの
とりあえずスレッド部を修正。あとJSONオブジェクト用のクラスも用意。DS側も少し修正。
・Java:ChatServerThread.java
import icommand.nxt.comm.NXTCommand;
import icommand.nxt.Motor;
import icommand.nxt.Sound;
import net.sf.json.*;
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.regex.*;
//チャットサーバスレッド
public class ChatServerThread extends Thread {
private static List<ChatServerThread> threads = new ArrayList<ChatServerThread>();
private Socket socket; // ソケット
public static final int DELAY_MS = 100;
public static final int DIRECTION_FORWARDS = 1;
public static final int DIRECTION_BACKWARDS = 2;
public static int direction;
// ビープ音を設定
private static final short[] note1 = { 2349, 80, 0, 5, 1760, 45, 0, 35 };
private static final short[] note2 = { 1760, 45, 0, 5, 2349, 80, 0, 35 };
private Boolean PowerState = false;
dsKey dsKeys = new dsKey();
// コンストラクタ
public ChatServerThread(Socket socket) {
super();
this.socket=socket;
threads.add(this);
}
// ここから処理
public void run() {
InputStream in = null;
String message;
int size;
Pattern p;
Matcher m;
byte[] w = new byte[10240];
try {
// ストリーム
in =socket.getInputStream();
while(true) {
try {
// 受信待ち
size=in.read(w);
// データのサイズが0なら切断
if (size<=0) throw new IOException();
// 読み込み
message = new String(w,0,size,"UTF8");
// 全員にメッセージ送信
sendMessageAll(message);
// JSON文字列→JSONObject→Beanクラス
p = Pattern.compile("(¥"([a-z]+)¥":([0-9]+).)*");
m = p.matcher(message);
if (m.find()) {
JSONObject jsonObject = JSONObject.fromObject(message);
System.out.println(jsonObject.toString());
dsKey dsKeys = (dsKey) JSONObject.toBean(jsonObject, dsKey.class);
// NXTにコマンドを送信
sendCommandNXT(dsKeys);
}
} catch (IOException e) {
socket.close();
threads.remove(this);
return;
}
}
} catch (IOException e) {
System.err.println(e);
}
}
// 全員にメッセージ送信
public void sendMessageAll(String message) {
ChatServerThread thread;
for (int i=0;i<threads.size();i++) {
thread=(ChatServerThread)threads.get(i);
if (thread.isAlive()) thread.sendMessage(this,message);
}
System.out.println(message);
}
// メッセージ送信
public void sendMessage(ChatServerThread talker,String message){
try {
OutputStream out=socket.getOutputStream();
byte[] w=message.getBytes("UTF8");
out.write(w);
out.flush();
} catch (IOException e) {
}
}
// NXTにコマンドを送信
public void sendCommandNXT(dsKey dsKeys) {
Motor.A.setSpeed(300);
Motor.C.setSpeed(300);
// ↑キーなら両方のモーターを前進
if (dsKeys.getAu() == 1) {
System.out.println("au:"+dsKeys.getAu());
direction = DIRECTION_FORWARDS;
Motor.A.forward();
Motor.C.forward();
}
// ↓キーなら両方のモーターを後進
else if (dsKeys.getAd() == 1) {
System.out.println("ad:"+dsKeys.getAu());
direction = DIRECTION_BACKWARDS;
Motor.A.backward();
Motor.C.backward();
}
// ←キーなら右のモーターだけ前進/後進
else if (dsKeys.getAl() == 1) {
if (direction == DIRECTION_FORWARDS) {
Motor.A.forward();
} else {
Motor.A.backward();
}
Motor.C.stop();
}
// →キーなら左のモーターだけ前進/後進
else if (dsKeys.getAr() == 1) {
if (direction == DIRECTION_FORWARDS) {
Motor.C.forward();
} else {
Motor.C.backward();
}
Motor.A.stop();
}
// STARTボタン
else if (dsKeys.getBs() == 1) {
if (!PowerState) {
// NXTに接続
NXTCommand.open();
// 起動ビープ音を鳴らす
for (int i = 0; i < note1.length; i += 2) {
final short ww = note1[i + 1];
final int n = note1[i];
if (n != 0)
Sound.playTone(n, ww * 10);
try {
Thread.sleep(ww * 10);
} catch (InterruptedException e) {
}
}
sendMessageAll("PowerOn");
PowerState = true;
}
}
// SELECTボタン
else if (dsKeys.getBe() == 1) {
if (PowerState) {
// 停止ビープ音を鳴らす
for (int i = 0; i < note2.length; i += 2) {
final short ww = note2[i + 1];
final int n = note2[i];
if (n != 0)
Sound.playTone(n, ww * 10);
try {
Thread.sleep(ww * 10);
} catch (InterruptedException e) {
}
}
// NXTと切断
NXTCommand.close();
sendMessageAll("PowerDown");
PowerState = false;
}
}
// 入力がなければモーターを停止
else {
Motor.A.stop();
Motor.C.stop();
}
// スレッドをDELAY_MSの間スリープ
try {
Thread.sleep(DELAY_MS);
} catch (Exception e) {
System.out.println(e);
System.exit(1);
}
}
}
・Java:dsKey.java
public class dsKey {
private int au;
private int ad;
private int al;
private int ar;
private int ba;
private int bb;
private int bx;
private int by;
private int bl;
private int br;
private int be;
private int bs;
private int pp;
private int px;
private int py;
public dsKey() {
}
public int getAu() { return au; }
public void setAu(int au) { this.au = au; }
public int getAd() { return ad; }
public void setAd(int ad) { this.ad = ad; }
public int getAl() { return al; }
public void setAl(int al) { this.al = al; }
public int getAr() { return ar; }
public void setAr(int ar) { this.ar = ar; }
public int getBa() { return ba; }
public void setBa(int ba) { this.ba = ba; }
public int getBb() { return bb; }
public void setBb(int bb) { this.bb = bb; }
public int getBx() { return bx; }
public void setBx(int bx) { this.bx = bx; }
public int getBy() { return by; }
public void setBy(int by) { this.by = by; }
public int getBl() { return bl; }
public void setBl(int bl) { this.bl = bl; }
public int getBr() { return br; }
public void setBr(int br) { this.br = br; }
public int getBs() { return bs; }
public void setBs(int bs) { this.bs = bs; }
public int getBe() { return be; }
public void setBe(int be) { this.be = be; }
public int getPp() { return pp; }
public void setPp(int pp) { this.pp = pp; }
public int getPx() { return px; }
public void setPx(int px) { this.px = px; }
public int getPy() { return py; }
public void setPy(int py) { this.py = py; }
}
・DS:arm9
#include <nds.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dswifi9.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
//---------------------------------------------------------------------------------
#define VCOUNT (*((u16 volatile *) 0x04000006))
#define PEN_DOWN (~IPC->buttons & (1 << 6))
touchPosition touchXY;
int touch_was_down = 0;
int my_socket;
static int old_x = 0;
static int old_y = 0;
static int shape_x = 0;
static int shape_y = 0;
static int shape_width = 4;
static int shape_height = 4;
static char *sendBuf;
//---------------------------------------------------------------------------------
// Dswifi helper functions
// WiFi タイマー関数
// sgIPのアップデート
void Timer_50ms(void) {
Wifi_Timer(50);
}
// ARM7へFIFOメッセージを通知する関数
void arm9_synctoarm7() { // FIFOメッセージを送る
REG_IPC_FIFO_TX=0x87654321;
}
// 割り込みハンドラ
// ARM7からのFIFOメッセージを取得する
void arm9_fifo() { // FIFOメッセージの入力をチェック
u32 value = REG_IPC_FIFO_RX;
if(value == 0x87654321) Wifi_Sync();
}
// フレームバッファで短型を動かす
//---------------------------------------------------------------------------------
void draw(int x, int y, uint16* buf, uint16 c) {
//---------------------------------------------------------------------------------
buf += y * SCREEN_WIDTH + x;
int i;
for(i = 0; i < shape_height; i++) {
uint16* line = buf + SCREEN_WIDTH * i;
int j;
for(j = 0; j < shape_width; j++) {
*line++ = c;
}
}
}
// Vblank割り込み時の処理。
//---------------------------------------------------------------------------------
void VblankHandler() {
//---------------------------------------------------------------------------------
sendBuf = (char *)malloc(128);
int keys[16] = {0};
scanKeys();
touchXY = touchReadXY();
int held = keysHeld();
int down = keysDown();
int up = keysUp();
if(held & KEY_UP) {
keys[0] = 1;
}
if(held & KEY_DOWN) {
keys[1] = 1;
}
if(held & KEY_RIGHT) {
keys[2] = 1;
}
if(held & KEY_LEFT) {
keys[3] = 1;
}
if(held & KEY_A) {
keys[4] = 1;
}
if(held & KEY_B) {
keys[5] = 1;
}
if(held & KEY_X) {
keys[6] = 1;
}
if(held & KEY_Y) {
keys[7] = 1;
}
if(held & KEY_R) {
keys[8] = 1;
}
if(held & KEY_L) {
keys[9] = 1;
}
if(down & KEY_START) {
keys[10] = 1;
}
if(down & KEY_SELECT) {
keys[11] = 1;
}
if(!touch_was_down && PEN_DOWN) {
touch_was_down = 1;
keys[12] = 1;
keys[13] = touchXY.px;
keys[14] = touchXY.py;
iprintf("\x1b[5;0H");
iprintf("Touch x / y = %03d / %03d\n", touchXY.px, touchXY.py);
}
else if(touch_was_down && !PEN_DOWN) {
touch_was_down = 0;
}
else if(touch_was_down && PEN_DOWN) {
keys[12] = 2;
keys[13] = touchXY.px;
keys[14] = touchXY.py;
iprintf("\x1b[5;0H");
iprintf("Touch [ %03d / %03d ]\n", touchXY.px, touchXY.py);
}
old_x = shape_x;
old_y = shape_y;
shape_x = touchXY.px;
shape_y = touchXY.py;
draw(old_x, old_y, VRAM_A, RGB15(5, 5, 5));
draw(shape_x, shape_y, VRAM_A, RGB15(0,31,0));
if(held || down || up) {
sprintf(sendBuf,"{\"au\":%d,\"ad\":%d,\"al\":%d,\"ar\":%d,\"ba\":%d,\"bb\":%d,\"bx\":%d,\"by\":%d,\"bl\":%d,\"br\":%d,\"bs\":%d,\"be\":%d,\"pp\":%d,\"px\":%d,\"py\":%d}\n",
keys[0], keys[1], keys[2], keys[3], keys[4], keys[5], keys[6], keys[7], keys[8], keys[9], keys[10], keys[11], keys[12], keys[13], keys[14] );
send( my_socket, sendBuf, strlen(sendBuf), 0 );
}
iprintf("\x1b[6;0H");
iprintf("Keys [ U D L R A B X Y L R E S ]");
free(sendBuf);
VBLANK_INTR_WAIT_FLAGS |= IRQ_VBLANK;
}
//---------------------------------------------------------------------------------
int main(void) {
//---------------------------------------------------------------------------------
powerON(POWER_ALL);
videoSetMode(MODE_FB0); //フレームバッファモード
videoSetModeSub(MODE_0_2D | DISPLAY_BG0_ACTIVE); //グラフィックエンジンのモードを指定。アクティブにして使用するBGも設定
vramSetMainBanks(VRAM_A_LCD, VRAM_B_LCD, VRAM_C_SUB_BG, VRAM_D_LCD); //VRAMの使用領域設定
SUB_BG0_CR = BG_MAP_BASE(31);
BG_PALETTE_SUB[255] = RGB15(31,31,31);
consoleInitDefault((u16*)SCREEN_BASE_BLOCK_SUB(31), (u16*)CHAR_BASE_BLOCK_SUB(0), 16);
// ARM7 の WiFi 機能を初期化するためのFIFOメッセージを送る
{
REG_IPC_FIFO_CR = IPC_FIFO_ENABLE | IPC_FIFO_SEND_CLEAR; // FIFOの有効&クリア
u32 Wifi_pass= Wifi_Init(WIFIINIT_OPTION_USELED);
REG_IPC_FIFO_TX=0x12345678;
REG_IPC_FIFO_TX=Wifi_pass;
*((volatile u16 *)0x0400010E) = 0; // Timer3 無効
irqInit();
irqSet(IRQ_TIMER3, Timer_50ms); // Timer IRQ のセット
irqEnable(IRQ_TIMER3);
irqSet(IRQ_FIFO_NOT_EMPTY, arm9_fifo); // FIFO IRQ のセット
irqEnable(IRQ_FIFO_NOT_EMPTY);
irqEnable(IRQ_VBLANK); // Vblank IRQ のセット
REG_IPC_FIFO_CR = IPC_FIFO_ENABLE | IPC_FIFO_RECV_IRQ; // FIFO IRQ 有効
Wifi_SetSyncHandler(arm9_synctoarm7); // WiFi ライブラリに ARM7への通知用関数をセットする
// Timer3 のセット
*((volatile u16 *)0x0400010C) = -6553; // 6553.1 * 256 cycles = ~50ms;
*((volatile u16 *)0x0400010E) = 0x00C2; // enable, irq, 1/256 clock
while(Wifi_CheckInit()==0) { // ARM7 の初期化終了待ち
while(VCOUNT>192); // VBlank 待ち
while(VCOUNT<192);
}
}
// WiFi 初期化終了 ここからWiFi ライブラリが使えるようになります
// 単純な接続
{
int i;
// Wifi_AutoConnect では、AOSS 機能は使えないらしいです。
// WEPの設定をDS本体から読んできます。
Wifi_AutoConnect(); // 接続要求
while(1) {
i=Wifi_AssocStatus(); // ステータスチェック
if(i==ASSOCSTATUS_ASSOCIATED) {
iprintf("Connected successfully!\n");
break;
}
if(i==ASSOCSTATUS_CANNOTCONNECT) {
iprintf("Could not connect!\n");
while(1);
break;
}
}
}
// 接続成功したら、ここからソケットインターフェイスを使ってインターネットに接続することができます。
// gethostbynameでIPアドレスに返す。直接IPでも。
struct hostent *myhost = gethostbyname( "10.0.2.1" );
iprintf("Found IP Address!\n");
// SOCK_STREAM 型のソケットを作る。TCPってこと。
my_socket = socket( AF_INET, SOCK_STREAM, 0 );
iprintf("Created Socket!\n");
// 指定したポートにソケットが繋がるか尋ねてみる。
struct sockaddr_in sain;
sain.sin_family = AF_INET;
sain.sin_port = htons(16000); // ポートを指定。なんとなく16000にしてみた。
sain.sin_addr.s_addr= *( (unsigned long *)(myhost->h_addr_list[0]) );
connect( my_socket,(struct sockaddr *)&sain, sizeof(sain) );
iprintf("Connected to server!\n");
// 接続確認のメッセージを送る。
//char *sendBuf = "CONNECTED SUCCESS\n";
//send( my_socket, sendBuf, strlen(sendBuf), 0 );
/*
必要ならこの辺で返ってきたメッセージを受信させるといいんじゃないかな。
*/
while(1) {
VblankHandler();
swiWaitForVBlank();
}
//return 0;
}
動かしてみる。
動くと結構うれしいw。DSの通信周りが不安定なんですぐ落ちるのが難点。WiFiのタイマー周りだと思うんだけどなぁ。。
FlashとMINDSTORMSを連携させてみる。
もーあとはソケット通信できて、JSON形式で送れればなんでもいいわけで。Flashとも連携させましょう。まずリモコンのパネルとかこしらえます。通信しましょう。動きますw。
うごいたうごいた。でもこれFlashで動いてるかどうかわかんよな。。
いろいろ作りたいなぁ
流行りじゃないのか情報ソースが少ないのが問題ですかね。センサー使ってなんか動かしたい!Gainerとか触ってみたいけどー、電子工作はちょっと。。みたいな人にはおすすめ。
あ、データにJSON形式を使ってるのはiCommandの仕様じゃないです。都合のいいプロトコルを使えばMIDIだったり、MAX/MSPとかProcessingをリモコンにできるかと。
関連エントリ:
- (その1) - iCommandのインストール
- (その2) - NintendoDS、Flashでコントロール