2012年6月6日

USB操作できる電源タップPTU2F3をLinuxから操作する


先日秋葉原にいったら、こんなものが売られていました。

USBから電源のON/OFFができる電源タップです。

これいいな! と思ったけども、どうやらこの電源タップを操作できるソフトはWindows用のものしかないらしいです。

しかもこの製品自体、現在では製造されていないらしいのです。

Linuxユーザーの自分としては、これを我が家の自宅サーバーにつなげて、リモートからの操作ができるようになりたいと思っておるのです。

「ないんだったら、自分で作ればいいじゃない!」

と神の啓示をいただきました



調べてみると、これを操作するソフトを独自で作り上げていた猛者がすでにおりました。

PTU2F3 をバグったSDKなんて使わないで直接操作しよう

流石です。僕が作る必要もな
いえいえ、これは.Net Frameworkを使ってるらしいのでやっぱりLinuxでは使えそうにもありません。
wine使えば動くのかもしれないけど、それだとコンソールから動かせないしなぁ。

やっぱり自作の道でした。





上記のページにはPTU2F3でどういうメッセージのやり取りがされているのかも書かれてあったので、比較的簡単に作れそうな気がします。

しかし、そもそもなんですが僕はUSBのアーキテクチャなんて知らないもんですから、そこから勉強する必要があるわけでした。

USBの基本アーキテクチャ

めんどくさがりの自分でもさらっと理解できる程度のページを発見。

なるほど。USBデバイスの操作にはエンドポイントと呼ばれる論理情報が必要なわけか。




次は、PTU2F3からエンドポイントを調べます。

まずはPC(CentOS)にPTU2F3を接続し、以下のコマンドを打つ。


[root@YKHG01 ~]# lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 003 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 004 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 005 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 003 Device 005: ID 0711:0028 Magic Control Technology Corp.

lsusbこまんどで、デバイスがどのポートに接続されているかを確認できます。

ルートハブというのがUSBポートそれ自身だと思ってください。

デバイスは一番下のBus003 Device005と書かれているものがPTU2F3であるという推測ができます。





次にこのデバイスの詳細を調べます。


[root@YKHG01 ~]# lsusb -v -s 003:005
Bus 003 Device 005: ID 0711:0028 Magic Control Technology Corp.
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               1.10
  bDeviceClass            0 (Defined at Interface level)
  bDeviceSubClass         0
  bDeviceProtocol         0
  bMaxPacketSize0         8
  idVendor           0x0711 Magic Control Technology Corp.  idProduct          0x0028   bcdDevice            2.01
  iManufacturer           1 MCT Corp_
  iProduct                3 0123456
  iSerial                 0
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength           41
    bNumInterfaces          1
    bConfigurationValue     1
    iConfiguration          0
    bmAttributes         0x80
      (Bus Powered)
    MaxPower              100mA
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           2
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      0 No Subclass
      bInterfaceProtocol      0 None
      iInterface              0
        HID Device Descriptor:
          bLength                 9
          bDescriptorType        33
          bcdHID               1.10
          bCountryCode            0 Not supported
          bNumDescriptors         1
          bDescriptorType        34 Report
          wDescriptorLength      25
          Report Descriptor: (length is 25)
            Item(Global): Usage Page, data= [ 0x00 0xff ] 65280
                            (null)
            Item(Local ): Usage, data= [ 0x01 ] 1
                            (null)
            Item(Main  ): Collection, data= [ 0x01 ] 1
                            Application
            Item(Local ): Usage, data= [ 0x02 ] 2
                            (null)
            Item(Global): Logical Minimum, data= [ 0x00 ] 0
            Item(Global): Logical Maximum, data= [ 0xff 0x00 ] 255
            Item(Global): Report Size, data= [ 0x08 ] 8
            Item(Global): Report Count, data= [ 0x08 ] 8
            Item(Main  ): Input, data= [ 0x82 ] 130
                            Data Variable Absolute No_Wrap Linear
                            Preferred_State No_Null_Position Volatile Bitfield
            Item(Local ): Usage, data= [ 0x03 ] 3
                            (null)
            Item(Main  ): Output, data= [ 0x82 ] 130
                            Data Variable Absolute No_Wrap Linear
                            Preferred_State No_Null_Position Volatile Bitfield
            Item(Main  ): End Collection, data=none
      Endpoint Descriptor:        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x02  EP 2 OUT        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0008  1x 8 bytes        bInterval              10
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0008  1x 8 bytes
        bInterval              10
Device Status:     0x0000
  (Bus Powered)




確認する場所は赤文字にしてある部分です。
まずは

  idVendor           0x0711 Magic Control Technology Corp.
  idProduct          0x0028 

この2行で、USBポートに刺さっているデバイスを特定します。
idVendorがベンダー(製造者)IDで、idProductがプロダクト(製品)IDです。

      Endpoint Descriptor:

ここから下にエンドポイントの情報が記されています。

        bEndpointAddress     0x02  EP 2 OUT

エンドポイント(というかパイプ)は複数個作成でき、この値はこのデバイスのパイプを指定するためのアドレスになります。
EPをエンドポイントを表し、2はパイプ番号、OUTはデバイスへの出力(デバイスからの入力はIN)を表します。

        wMaxPacketSize     0x0008  1x 8 bytes

デバイスとの通信でやり取りされる最大のパケットサイズです。
PTU2F3は8バイトのデータをやり取りしていることがわかりました。






これでPTU2F3の基本情報を確認することができました。
次にメインであるプログラミングをします。

LinuxでUSBデバイスの操作をするために、libusbというライブラリを利用することができます。


最初は一からコードを組もうと思いましたが、めんdサンプルの出来が良いので上記ページにあるサンプルコードをそのまま使わせていただきました。

で、メッセージの送信部分しか作ってないのでコードの解説は省略します。

とりあえず変更部分のソースを晒しておきます。

unsigned char writeStatus[8] = {2,0,0,0,0,0,0,0};
if(argc == 2){
switch(argv[1][0]){
case '1':
writeStatus[1] = 1;
break;
case '2':
writeStatus[1] = 2;
break;
               case '3':
writeStatus[1] = 3;
break;
default:
break;
}
}
int result=usb_bulk_write(dh, 0x02, writeStatus, sizeof(writeStatus), TIMEOUT);
if(result<0){printf ("Control message error. (%d:%s)\n", result, usb_strerror());}

あ、一箇所だけ解説をば、

unsigned char writeStatus[8] = {2,0,0,0,0,0,0,0};

最初に紹介した人のページではパケットサイズは9バイトで、2バイト目に制御コードを入れていましたが、lsusbの結果から、こちらのほうが妥当だと判断しました。
(実際この制御コードでも動作しました)

で、完成したのがこちら。




とりあえずダウンロードしてコンパイルできれば動かせます。(要libusb-dev)

[root@YKHG01 ~]# gcc -o ptu2f3 main.c -lusb

第一引数にON/OFFしたいタップのポート番号を指定してください。

# ./ptf2f3 0 # 4,5ポート共にONにします。
# ./ptf2f3 1 # 4ポートOFF、5ポートON
# ./ptf2f3 2 # 4ポートON、5ポートOFF
# ./ptf2f3 3 # 4,5ポート共にOFFにします。

はい、こんな感じで動かすことができました。

これでサーバのターミナル上からUSBの制御をすることができるようになりました。

それじゃこれを何に使おうかっていう話ですが。
うちのラボ環境をリモートから制御できるようにしたいわけです。
基本的に商用ルータやスイッチには電源がないので。こういう電源の操作を直接できるタップは本当に役に立つわけです。

多分、このタップが話題にならなかったのはバグだらけでWindowsでしか動作しないSDKしかなかったからじゃないかなぁと思う。
サーバー用途とかで使えるものだと思います。

こんな感じで、うちのネット環境がまた一つ充実してしまった。そういうお話でした。

以上。