2014年1月25日土曜日

USB Sniper RIfle を Raspberry Pi で動かしてみた。

 以前に Raspberry Pi で USBスカッとミサイルランチャーを動かしたことがあるんですが、先日ネットを徘徊していたところ、上海問屋でUSBスナイパーライフルというのが売っていたので、じゃあ、解析してみようかなということで、ポチリました。




 ものとしては、Axpro というところの製品のようです。スカッとミサイルランチャーのときのようにUSB SniferでUSBに流れているパケットを調べようとしたところ、非常に複雑でコマンドの長さも上下左右・発射それぞれバラバラでした。これは、解析は無理かな?って思ったんですが、デバイスマネージャをよく見たら、Serial-USB変換デバイスとしてCOMポートとして認識されています。ミサイルランチャーの時はHIDデバイスとして認識されていたので、直接バイナリデータを送ってやることで制御出来ましたが、今回はそうは行きません。ただ、Raspberry PiでもこのデバイスがUSB-Serial変換デバイスとしてttyとして見えてしまえば、あとはファイルへコマンドを書き込めばいいだけです。
 さて、改めて、COMポートデバイスとしてスナイパーライフルを解析してみたところ、実際にやりとりされているコマンドは極めて簡単で、テキストでUP-xxとかLEFT-xx、FIRE-xxみたいなコマンドに終端文字LFを付けて送信しているだけです。実際には、開始時に何らかの初期化コマンドを送っていて、これには何種類かあるようなのですが、今回はキャプチャした内容から適当に一種類を選択して初期化時に送信します。あとは、UP-30とかLEFT-60とか方向にハイフンと数字30、60、90で移動角度の大きさ具合が選べるような感じのコマンドをLF付きで送ります。コマンドに対する応答もレスポンスしているみたいですが、今回は無視です。
 それと、カメラも付いているので、これも映るといいな。

まずは、Raspberr Pi でデバイスをUSB-Serialとして認識してくれるか?
試しに接続したところ、無事、"/dev/ttyUSB0"として認識してくれているようです。これなら、制御は行けそう。次にカメラ・・、こちらもOK。USBカメラとして認識しています。ならば、OpenCVでとりあえずキャプチャした画像を表示してみましょう。




 Raspberry Pi に USBスナイパーライフルを接続して、WindowsからXで接続してOpenCVのcvWaitkey でカーソルとスペースキーを取得してライフルを制御して、カメラ画像をOpenCVのフレームに表示しています。意外と簡単に動かせました。ただ、カメラの画像表示は1~2秒遅延するので、カメラの画像を見ながらの操作でターゲッティングするのは無理そう・・・。

 スカッとミサイルランチャーのときはArduinoを使って、こんなのを作ったんだけど、今回のはサイズが大きいし、外部電源が必要なので自走化は難しそう。

今回のソースはこんな感じ。

#define _POSIX_SOURCE 1

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <sys/signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include <time.h>
#include <sys/time.h>

#include <opencv/cv.h>
#include <opencv/highgui.h>

// capture size.
#define CAP_WIDTH 320
#define CAP_HEIGHT 240

// grobal variable definition.
int fd;  // file descriptor.
struct termios oldTermIo; // for keep now setting.

// prototype.
int Serial_Open(char *serialDev);
void Serial_Close(void);
int Serial_PutChar(unsigned char c);
int Serial_PutString(char *s);
unsigned char Serial_GetChar(void);

int main(int argc, char *argv[])
{
 char rBuffer[1024];
 char sBuffer[256];
 char *serialDev = "/dev/ttyUSB0";
 char c;

 CvCapture *capture = 0;
 IplImage *frame = 0;

 // create capture.
 capture = cvCreateCameraCapture(0);

 // capture size.
 cvSetCaptureProperty(capture, CV_CAP_PROP_FRAME_WIDTH, CAP_WIDTH);
 cvSetCaptureProperty(capture, CV_CAP_PROP_FRAME_HEIGHT, CAP_HEIGHT);

 // clear buffer.
 memset(rBuffer, '\0', 1024);
 memset(sBuffer, '\0', 256);

 // open serial port.
 Serial_Open(serialDev);
 sleep(1);

 // create window.
 cvNamedWindow("Sniper Rifle", CV_WINDOW_AUTOSIZE);

 // initialize.
 Serial_PutString("@CPSM\n");

 while(1)
 {
  // capture and show.
  frame = cvQueryFrame(capture);
  cvShowImage("Sniper Rifle", frame);

  c = cvWaitKey(50);
  if (c == 27)
   break;  // ESC
  switch (c)
  {
  case 82: // ^
   Serial_PutString("UP-60\n");
   break;
  case 84: // v
   Serial_PutString("DOWN-60\n");
   break;
  case 81: // <
   Serial_PutString("LEFT-90\n");
   break;
  case 83: // >
   Serial_PutString("RIGHT-90\n");
   break;
  case 32: // space
   Serial_PutString("FIRE-2580\n");
   break;
  default:
   break;
  }
 }

 // dispose OpenCV.
 cvReleaseCapture(&capture);
 cvDestroyWindow("Sniper Rifle");

 // close serial port.
 Serial_Close();

 return 0;
}

int Serial_Open(char *serialDev)
{
 struct termios newTermIo;

 // open device.
 fd = open(serialDev, O_RDWR | O_NOCTTY);
 if (fd < 0)
 {
  perror(serialDev);
  exit(-1);
 }

 // keep old setting.
 tcgetattr(fd, &oldTermIo);

 // new setting.
 newTermIo.c_iflag = 0;
 newTermIo.c_oflag = 0;
 newTermIo.c_cflag = 0;
 newTermIo.c_lflag = 0;
 newTermIo.c_line = 0;
 memset(newTermIo.c_cc, '\0', sizeof(newTermIo.c_cc));
 //bzero(newTermIo.c_cc, sizeof(newTermIo.c_cc));

 // 9600bps, 8bit, no parity, 1 stop bit, local, read enable
 newTermIo.c_cflag = B9600 | CS8 | CLOCAL | CREAD;
 // ignore parity error.
 newTermIo.c_iflag = IGNPAR;
 // raw mode.
 newTermIo.c_oflag = 0;
 // non-canonical, no echo
 newTermIo.c_lflag = 0;
 // inter-character timer
 newTermIo.c_cc[VTIME] = 0;
 newTermIo.c_cc[VMIN] = 1;

 // clear line.
 tcflush(fd, TCIFLUSH);

 // set.
 tcsetattr(fd, TCSANOW, &newTermIo);

 return (0);
}

void Serial_Close(void)
{
 tcsetattr(fd, TCSANOW, &oldTermIo);
 close(fd);
}

int Serial_PutChar(unsigned char c)
{
 if (write(fd, &c, 1) != 1)
  return (-1);

 return (0);
}

int Serial_PutString(char *s)
{
 if (write(fd, s, strlen(s)) <= 0)
  return (-1);

 return (0);
}

unsigned char Serial_GetChar(void)
{
 unsigned char c;
 
 read(fd, (char *)&c, 1);

 return (c);
}