ProcessingでLEGO MINDSTORMSを操作する(2)

お盆ですね。あいかわらず仕事の切りが悪い状況が続いているのですが、気を取り直して前回に引き続き、ProcessingからBluetoothシリアル通信経由でLEGO MINDSTORMSを動かしてみたいと思います。

今回は、MINDSTORMSのセンサー情報の取得に挑戦してみます。

MINDSTORMSのセンサー情報の取得方法

Bluetoothシリアル通信を使ったMINDSTORMSのセンサー情報の取得は、次の手順で行います。

  1. SETINPUTMODEコマンドで、使用する入力ポートとセンサーの種類を指定する
  2. GETINPUTVALUEコマンドでセンサー情報を要求する
  3. MINDSTORMSから返信されたセンサー情報を取得する

MINDSTORMSがサポートするコマンドの種類や送信方法については前回の記事を参考にしてください。

内容を順に説明します。

入力ポートとセンサーの種類の設定

まず、センサーを接続するMINDSTORMSの入力ポート番号とセンサーの種類を指定します。設定にはSETINPUTMODEコマンドを使用します。

f:id:tkitao:20140811214826p:plain

入力ポートはMINDSTORMS本体上で1と表記されている左端のものが、コマンド上では0番と、数字が1つずれているので注意してください。

設定は初期化時に一度だけ行います。

センサー情報の要求

次に、GETINPUTVALUEコマンドでMINDSTORMSにセンサー情報を要求します。初期化時に設定したポート番号と同じ番号を指定します。

f:id:tkitao:20140811214830p:plain

要求を一度行うとセンサー情報が一回分返ってきますので、継続的にセンサー情報を取得したい場合は、定期的にセンサー情報を要求し続ける必要があります。

センサー情報の取得

最後にGETINPUTVALUEコマンドに対して返信されたセンサー情報を取得します。

Bluetoothを使ったシリアル通信の場合、最初の2バイトでデータ全体のサイズが指定されるので、まず2バイト受信してサイズを確定させた後、指定サイズ分データを受信するという2段階で取得作業を行います。

実際に試してみる

それでは実際にセンサー情報を取得してみます。今回は一番シンプルな接触センサー(Touch Sensor)を使ってみます。

f:id:tkitao:20140811214635j:plain

ちなみに接触センサーのセンサー番号は5番になります。

以下コード全文です。

import processing.serial.*;

Serial serial = null;
int recvSize = 0;
byte[] sendData = new byte[256];
byte[] recvData = new byte[256];

void setup() {
  // シリアルポートを開く
  String[] ports = Serial.list();

  for (String port : ports) {
    if (port.contains("cu.NXT")) {
      println("open " + port);
      serial = new Serial(this, port, 9600);
      break;
    }
  }

  if (serial == null) {
    println("can't find ports for NXT");
    exit();
  }

  // 0番の入力ポートに接触センサーを設定する
  sendCommand(0x00, 0x05, 0x00, 0x01, 0x00); // SETINPUTMODE
}

void draw() {
  // 0.5秒に1度、0番の入力ポートの情報を要求する
  if (frameCount % 30 == 0) {
    sendCommand(0x00, 0x07, 0x00); // GETINPUTVALUES
  }

  // MINDSTORMSからのデータを受信する
  if (recvSize == 0) { // パケットサイズを受信
    if (serial.available() > 2) {
      recvSize = serial.read() + (serial.read() << 8);
    }
  } else { // 指定サイズのデータを受信
    if (serial.available() >= recvSize) {
      for (int i = 0; i < recvSize; i++) {
        recvData[i] = (byte)serial.read();
      }
      receiveCommand(recvData, recvSize);
      recvSize = 0;
    }
  }
}

void sendCommand(int... data) {
  // 送信内容を表示
  print("send: ");
  for (int i = 0; i < data.length; i++) {
    print(hex((byte)data[i]) + " ");
  }
  print("\n");

  // コマンドを送信する
  sendData[0] = (byte)(data.length & 0xff);
  sendData[1] = (byte)(data.length >> 8);
  for (int i = 0; i < data.length; i ++) {
    sendData[i + 2] = (byte)data[i];
  }
  serial.write(sendData);
}

void receiveCommand(byte[] data, int size) {
  // 受信内容を表示
  print("recv: ");
  for (int i = 0; i < size; i++) {
    print(hex(data[i], 2) + " ");
  }
  print("\n");

  // 入力ポート情報の時は接触センサーの値を表示する
  if (data[0] == 0x02 && data[1] == 0x07) { // GETINPUTVALUES
    int rawValue = (data[8] & 0xff) + ((data[9] & 0xff) << 8); // 符号を無視させるため、0xffとANDを取ってint型にしている
    println("touch sensor value = " + rawValue);
  }
}

やることが大分複雑になってきたため、今回はコマンド送信部をsendCommand、受信部をreceiveCommandという関数に分け、多少構造化しています。

出力結果

実際の出力結果です。

f:id:tkitao:20140811214257p:plain

ちょっとわかりづらいのですが、接触センサーを押した際にターミナルのtouch sensor valueの値が、1023から181に変化しています。

まとめ

2回にわたりProcessingによるMINDSTORMSの操作方法を説明してきましたが、これ以上複雑な操作を行うには専用の補助クラスがないと厳しく、ボリューム的にサンプルの域を越えてしまいそうです。

最近メンテナンスされていないようだったので今回は取り上げませんでしたが、この先はNXTCommのような拡張ライブラリを利用する(あるいは自作する)方が無難そうです。

正直、ProcessingとMINDSTORMSをつなげる必要性のある人が、今日本にどれくらいいるのか疑問なところもあるので、MINDSTORMSの記事は今回で一旦区切りをつけて、次回はまた別の面白そうなことをやってみたいと思います。(その前に仕事の区切りもつけないと…)