2012年10月14日日曜日

USBスカッとミサイルランチャーを Raspberry Pi で動かしてみた。

 先週は仕事が忙しくて、家に帰ってからも疲れて酒のんで寝てしまって・・・、あんまり、本業の趣味の方に手が付けられませんでした。今週末は、親戚へのお墓参りも兼ねて 一泊でしたが 松之山温泉へ行って「ちとせ」さんにお世話になりました。部屋に源泉かけ流しの露天風呂がついていて、一日中好きなときにお酒片手に温泉につかれるという贅沢な体験ができました。朝日を浴びながら温泉に使って一杯は最高でした。お料理も美味しかったし、また行きたいと思います。ただし、周りには何もありません。自分はスキーもやらないので、ひたすら温泉に使って、お酒を飲むっていう・・・。でも、WiFiのFreeSpotが使えたのは意外でした。(以前に鬼怒川温泉に行ったときは携帯すら危うかった記憶がある。)


 で、温泉で(仕事の心の)疲れを癒して、今日は、久々に新しいキワモノをいじることにしました。今回のターゲットはセンチュリーの「USBスカッとミサイルランチャー」です。といってもセンチュリーがこういうものを開発しているわけはなく、ものとしてはDREAM CHEEKYのSTORM O.I.C というものです。以前にちょっと流行ったUSBミサイルランチャーですが、このモデルにはカメラもついています。となると、やっぱり、こいつを移動ロボットに載せて遠隔あるいは自立によって操作・制御したくなります。もちろんWindows用にはDREAM CHEEKYのサイトから制御アプリをダウンロードして利用することができます。このソフトもカメラに写った画面が表示され、砲台の制御・ミサイル発射ができるようになっていますが、独自に自作プログラムから操作しようとすると調査が必要です。また、ここはせっかくなので将来のロボット搭載も意識して Raspberry Pi での制御を実現したいと思います。

 まずは、Raspberry Pi のUSBにミサイルランチャーを接続して、接続情報を調べます。lsusb でUSBデバイスの認識情報を確認すると
   2123:1010 (空欄)
   0ac8:3450 Z-Star Microelectronics Corp.
というふうに表示されます。上の名前が空欄になっている方がミサイルランチャーでHIDデバイスとして認識されているようです。ミサイルランチャーをUSBに挿すと自分の環境ではそれぞれ "/dev/hidraw2"と"/dev/usb/hiddev0"が追加されました。HIDデバイスとして認識されているならもしかするとファイルデバイスとしてコマンドを書き込んでやれば動いてくれるかもしれません。また、下のZ-Star ・・・がカメラのようです。"/dev/video0"として認識され、"ls /dev/v4l/by-id"では
   usb-Vimicro_Corp._Altair_USB2.0_Camera-video-index0
と表示されます。OpenCVで画像取得してみたところ、320×240及び640×480で画像取得することができました。→ (訂正)当初、PC上のDebian環境では画像取得は問題なくできたんだけど、Raspberry Piに接続して確認したところ、画像取得時にtimeoutしてしまい、たまに取得できるんだけど、ほとんど黒画面という状態でした。

 とりあえず、内蔵カメラでの画像取得はできそうです →(訂正)厳しいかな?別途カメラを付けるのもなんか悔しいんだけど・・・。あとは とりあえず、ミサイルランチャーの制御です。ネットで色々調べてみたところ、この機種ではありませんが、過去に販売されていた同社のミサイルランチャーの制御をLinux上で行なっているサンプルがあったので、それらのコマンド記述を参考(というかコピー)にして "/dev/hidraw*"への書き込みスクリプトで操作しようとしたんですが、うまくいきませんでした。そこで、libusb というのを使うことにしました。使い方とかはこの辺とか、ネットで調べました。libusbのインストールは

   sudo apt-get install libusb-dev

で。


 Raspberry Pi のUSB端子に直接接続した状態だと砲台の駆動時に USB通信エラーになってしまい、途中で動かなくなってしまいました。これは、Raspberry PiのUSB駆動能力が140mA程度にポリスイッチで制限されているからだと思います。そこで、セルフパワー(ACアダプタ付き)のUSBハブを間に入れることで、ミサイル発射まで動かすことができるようになりました。ただし、数回に一度、ミサイル発射時にRaspberry Piのシステムが落ちます。発射時に突入電流やノイズのようなものがあるんでしょうか?でも、間にハブを入れているので、影響するんだろうか?この部分は今のところ原因不明です。
 以下、ソースです。

#include <stdio.h>
#include <unistd.h>

#include <usb.h>

#define HID_REPORT_SET (0x09)
#define BULK_OUT (0x02)

#define MISSILE_VENDOR_ID (0x2123)
#define MISSILE_PRODUCT_ID (0x1010)
#define MISSILE_CMD_HEADER (0x02)
#define MISSILE_CMD_DOWN (0x01)
#define MISSILE_CMD_UP (0x02)
#define MISSILE_CMD_LEFT (0x04)
#define MISSILE_CMD_RIGHT (0x08);
#define MISSILE_CMD_FIRE (0x10);
#define MISSILE_CMD_STOP (0x20);

const int USB_TIMEOUT = 3000;
const int BUF_SIZE = 8;

int main(int argc, char **argv)
{
 struct usb_bus *bus;
 struct usb_device *dev;
 usb_dev_handle *udh;
 struct usb_config_descriptor *config;
 struct usb_interface *interface;
 struct usb_interface_descriptor *altsetting;
 unsigned char buf[BUF_SIZE];
 int result = -1;
 int i = 0, j = 0;
 
 // 初期化。
 usb_init();
 usb_find_busses();
 usb_find_devices();
 
 // デバイスチェック。
 for(bus = usb_get_busses(); bus; bus = bus->next)
 {
  for(dev = bus->devices; dev; dev = dev->next)
  {
   if(dev->descriptor.idVendor == MISSILE_VENDOR_ID &&
    dev->descriptor.idProduct == MISSILE_PRODUCT_ID)
   {
    // デバイスオープン。
    if((udh = usb_open(dev)) == NULL)
    {
     // オープンに失敗。
     printf("usb_open Error.(%s)\n", usb_strerror());
     exit(-1);
    }
    
    printf("found usb missile launcher\n");

    config = &dev->config[0];
    interface = &config->interface[0];
    altsetting = &interface->altsetting[0];
    
    if (usb_set_configuration(udh, config->bConfigurationValue) < 0)
    {
     if (usb_detach_kernel_driver_np(udh,
              altsetting->bInterfaceNumber) < 0)
     {
      printf("usb_set_configration() error.\n");
      usb_close(udh);
      return -1;
     }
    }
    
    // インタフェースの使用をシステムに通知。
    if (usb_claim_interface(udh, altsetting->bInterfaceNumber) < 0)
    {
     // インタフェース使用要求でエラー。
     printf("claiming interface error\n");
    }
    else
    {
     for (j = 0; j < 6; j++)
     {
      // 送信データ構成。
      buf[0] = MISSILE_CMD_HEADER;
      switch (j)
      {
       case 0:
        buf[1] = MISSILE_CMD_RIGHT;
        break;
       case 1:
        buf[1] = MISSILE_CMD_LEFT;
        break;
       case 2:
        buf[1] = MISSILE_CMD_UP;
        break;
       case 3:
        buf[1] = MISSILE_CMD_DOWN;
        break;
       case 4:
        buf[1] = MISSILE_CMD_STOP;
        break;
       case 5:
        buf[1] = MISSILE_CMD_FIRE;
        break;
       default:
        break;
      }
      for (i = 2; i < 8; i++)
      {
       buf[i] = 0x00;
      }
      
      // 送信。
      result = usb_control_msg(
       udh,
       USB_ENDPOINT_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
       HID_REPORT_SET,
       0,
       0,
       (char*)buf,
       sizeof(buf),
       USB_TIMEOUT); 
      if (result < 0)
      {
       // 送信エラー。
       printf("usb write error.\n");
      }
      else
      {
       printf("send data.\n");
      }
      
      sleep(1);
     }
     
     // インタフェースの解放。
     usb_release_interface(udh, 0);
    }
    
    // デバイスクローズ。
    usb_close(udh);
    
    return 0;
   }
  }
 }

 return -1;
}