miau's blog?

X61 Tablet で加速度センサを使ったアプリが動かない件

まだ Thinkpad X61 Tablet のセットアップをガシガシやっているところですが、ふと「加速度センサで遊んでみよう」と思い立ちまして。ちょっと試してみるだけのつもりが、動かすのは結構大変だったので、そのお話。(関係あるかわからないけど、対象は X61T の SXGA+ モデルです。)

なんだか長文になっちゃいましたが、X61T を買って「加速度センサを体験したいけど、動くアプリが見つからないよ!」という方は、末尾の「とりあえず試したい方は」の項だけ見ていただければいいかと。




■とっかかり

「加速度センサ Thinkpad」とかでググるといろいろ見つかるわけですが。

Thinkpad加速度センサを使ったソフトウェア・リンク集 / 平林 純@「hirax.net」の科学と技術と男と女/Tech総研

前から気になってたので、まずはここから。wktkしながら試してみたけど、ことごとくうまくいかない。あれー?

netswitch! | Thinkpad の加速度センサをRubyから使う

この Ruby のやつも試してみたけど、やっぱりだめ。
Ruby なのでソースが見れるわけで、ちょっといじってみたけど、どうも unpack の対象バッファが 0 バイトっぽい。
もしかしてデバイスの取得に失敗してるとか?

Active Protection System - ThinkWiki

ここやその他のサイトに載ってる Perl のサンプルも試してみたけど、やっぱり同じエラー。
でも Perl ならもっと深く調査できるよ!

■Perl で調べてみる

まずは //./ShockMgr ってデバイスがちゃんと存在するか確認するために、デバイスの一覧を出力してみる。

use Win32API::File qw(:ALL);
my $osTargetPath = "\0" x 65536;
QueryDosDevice( [], $osTargetPath, length($osTargetPath) ) or die "$^E";
print join "\n", split /\0/, $osTargetPath;

ちゃんと //./ShockMgr ってのはあるみたい。
・・・ん?よく見たらエラーチェックしてないじゃん、このサンプル。ちゃんとチェックしようよ。

use Win32API::File qw(:ALL);
my $file = createFile("//./ShockMgr", "r ke") or die "Can't get ShockMgr device" or die "$^E";;
DeviceIoControl($file, 0x733fc, [], 0, my($buf), 0x24, my($bytes), []);
die $^E if $^E;

とすると、

パラメータが間違っています。 at - line 4.

お。エラー出た。なんかパラメータがよくないっぽい。

■Sensor.dll

といっても、インターフェイスが公開されているわけではないので、どういうパラメータを渡していいものか・・・。
と色々調べてたら、こんなページにたどり着いた。

IBM Active Protection und die Sensor.dll英訳

どうも DeviceIoControl の上階層に Sensor.dll ってのがいるらしい。この DLL を解析して DeviceIoControl の引数を調べたって話みたいで・・・つまり普通はこの DLL をとっかかりにするんだな。後で気づいたけど、加速度センサでいろいろ作っている hirax.net の中の人も最初は sensor.dll の解析から入っているし。

inside out (hirax.net)

うーん、完全に逆の流れでたどり着いてしまったなぁ・・・。
まあそれはともかく、この Sensor.DLL の ShockproofGetAccelerometerData を OllyDbg で覗いてみると、

002C17C8 /. 55 PUSH EBP
002C17C9 |. 8BEC MOV EBP,ESP
002C17CB |. 83EC 3C SUB ESP,3C
002C17CE |. A1 04402C00 MOV EAX,DWORD PTR DS:[2C4004]
002C17D3 |. 33C5 XOR EAX,EBP
002C17D5 |. 8945 FC MOV DWORD PTR SS:[EBP-4],EAX
002C17D8 |. 53 PUSH EBX
002C17D9 |. 8B5D 08 MOV EBX,DWORD PTR SS:[EBP+8]
002C17DC |. 56 PUSH ESI
002C17DD |. 33F6 XOR ESI,ESI
002C17DF |. 833D 00402C00 >CMP DWORD PTR DS:[2C4000],-1
002C17E6 |. 74 63 JE SHORT Sensor.002C184B
002C17E8 |. 6A 14 PUSH 14 ; /DataSize = 14 (20.)
002C17EA |. 53 PUSH EBX ; |DataAddress
002C17EB |. FF15 44102C00 CALL DWORD PTR DS:[<&KERNEL32.IsBadWrite>; \IsBadWritePtr
002C17F1 |. 85C0 TEST EAX,EAX
002C17F3 |. 75 56 JNZ SHORT Sensor.002C184B
002C17F5 |. 57 PUSH EDI
002C17F6 |. 8D7D C4 LEA EDI,DWORD PTR SS:[EBP-3C]
002C17F9 |. AB STOS DWORD PTR ES:[EDI]
002C17FA |. 6A 2C PUSH 2C ; /n = 2C (44.)
002C17FC |. AB STOS DWORD PTR ES:[EDI] ; |
002C17FD |. 8D45 D0 LEA EAX,DWORD PTR SS:[EBP-30] ; |
002C1800 |. 56 PUSH ESI ; |c => 00
002C1801 |. 50 PUSH EAX ; |s
002C1802 |. E8 6F0D0000 CALL <JMP.&msvcrt.memset> ; \memset
002C1807 |. 83C4 0C ADD ESP,0C
002C180A |. 56 PUSH ESI ; /pOverlapped
002C180B |. 8D45 CC LEA EAX,DWORD PTR SS:[EBP-34] ; |
002C180E |. 50 PUSH EAX ; |pBytesReturned
002C180F |. 6A 2C PUSH 2C ; |OutBufferSize = 2C (44.)
002C1811 |. 8D45 D0 LEA EAX,DWORD PTR SS:[EBP-30] ; |
002C1814 |. 50 PUSH EAX ; |OutBuffer
002C1815 |. 6A 08 PUSH 8 ; |InBufferSize = 8
002C1817 |. 8D45 C4 LEA EAX,DWORD PTR SS:[EBP-3C] ; |
002C181A |. 50 PUSH EAX ; |InBuffer
002C181B |. 68 FC330700 PUSH 733FC ; |IoControlCode = 733FC
002C1820 |. FF35 00402C00 PUSH DWORD PTR DS:[2C4000] ; |hDevice = 00000038
002C1826 |. C745 C4 544150>MOV DWORD PTR SS:[EBP-3C],53504154 ; |
002C182D |. FF15 48102C00 CALL DWORD PTR DS:[<&KERNEL32.DeviceIoCo>; \DeviceIoControl

ということで・・・上記のサイトとは DLL の内容が違う(変更されている?)様子。具体的には
・InputBuffer が 0 bytes → 8 bytes に変更されて、0x53504154 という値が渡されている
・OutputBuffer のサイズが 0x24 bytes → 0x2C bytes に変更されている
と。まあこれに合わせてみましょう。

use Win32API::File qw(:ALL);

my $file = createFile("//./ShockMgr", "r ke") or die "Can't get ShockMgr device" or die $^E;

my $in = pack('LL', 0x53504154, 0);
my $buf = "\0" x 0x2c;
DeviceIoControl($file, 0x733fc, $in, length($in), $buf, length($buf), my($bytes), []);
die $^E if $^E;

my @data = unpack "x4s*", $buf;
print join(',', @data) . "\n";

で、実行すると・・・

0,0,3,0,561,504,561,503,-28377,560,503,-19736,561,561,561,561,503,503,503,504

キター。なんだかわかりませんがデータが取得できました。

いろいろ動かしながら確認したところ、561 が x 軸側の加速度センサ、504 が y 軸側の加速度センサの値っぽい。それに近い値が何箇所かで取れてるけど、時系列データか何かかな?それ以外のデータは 3 がステータスらしいってこと以外不明。ついでに unpack で "x4" とかやって読み飛ばしている 4 bytes は "SPAT" っていう固定文字列みたい。

x, y の範囲は私の PC ではこんな感じ。
 x: 372〜662(奥に倒した場合〜手前に倒した場合)
 y: 363〜649(左に倒した場合〜右に倒した場合)
個体差とか色々あるんだろうけど、ThinkWiki に載ってた値とちょっと違うっぽい。

■Sensor.dll 使おうよ

・・・で、拾えることはわかったんだけども。機種によって呼び出し方が違うってことでしょ?その違いを隠ぺいしてくれている Sensor.DLL を使わないのはどうなのよ?ディスアセンブルしてるような人ならインターフェイスわからないってことも無いだろうし、LoadLibrary で動的リンクすれば普通に呼び出せるはずだよね?
ということでちゃんと Sensor.DLL を使った呼び出し方法を書いてみる。

Interfacing sensor.dll into VB [Archive] - Xtreme Visual Basic Talk

を参考に・・・ってあまり参考にならなかったけど、Perl だとこんな感じで書ける。

use Win32::API;
my $getAccel = Win32::API->new(
'Sensor', 'ShockproofGetAccelerometerData', 'P'
);

my $data = ' ' x 20;
$getAccel->Call($data);
my ($status, undef, $x, $y, $x2, $y2, $unknown1, $x3, $y3, $unknown2) = unpack "s*", $data;

Win32::API::Struct を使ったり、それっぽい引数で呼び出す方法もあるんだけど・・・Win32::API では short 型が使えないっぽいのでこれくらいの書き方がよさげ。
ShockproofGetAccelerometerData() は DeviceIoControl() で返されるデータのうち、先頭の "SPAT" と 0, 0 (2 bytes x 2)を削って、そこから 20 bytes 返しているみたいです。

ちなみに Win32::API は標準モジュールじゃないので、ActivePerl な方は

>ppm install Win32::API

で入れてください。

■感想

これで加速度センサを使う準備は整った。けどあまりネタがないので保留。
(ってか調査だけで疲れた・・・。)

思いつくネタとしては、ピンボールで「台を揺らす」動作の実現かな。(ワインバーグがこの動作は重要だとか言ってたような。)でも「傾ける」んじゃなくて「揺らす」のを前提にしたインターフェイスってのは問題あるか。HDD とか壊れそうだ。

も一つ思いついたのは、地震っぽい揺れ方したら

P2P地震情報 - 地震情報を自動でチェック

で自動的にデータ送るとか。でも、何をもって「地震っぽい」と判断するかは面倒そうだ。

ま、何か実用的な or 面白い UI を思いついて、気軽に作れそうなら実装しますかね。

■とりあえず試したい方は

・・・ということで簡単に動くサンプルがやたら少ないんですが、DeviceIoControl() を使わずに Sensor.DLL を使っているアプリケーションならちゃんと動作するわけで。下記サイトを当たれば動作するアプリケーションを見つけられると思います。

IBM Research | People | Mark A. Smith | SDL

これは何かというと、SDL(3D で色々やるためのライブラリ)に加速度センサで入力を与えられるようにするパッチのようで。ゲームをダウンロードして、その中にある SDL.dll をパッチ済のものに置き換えてやれば、PC を傾けて操作できるようになります。
Neverball はうまく動かせませんでしたが、Tux Racer のほうは動きました。やっぱ触ったことのないインターフェイスって面白いですね。発想が広がるというか。


(2007-08-31 追記)

ShockproofGetAccelerometerData() で返ってくるデータの詳細が不明、みたいなことを書いたけど、SDL.dll のパッチ見れば非公式ながらもだいたいわかりますよね。

> typedef struct _ACCELREPORT {
> INT PresentState; // Current internal state (stable: 0, unstable1: 1: unstable2: 2)
> USHORT LatestRawAccelDataX; // latest raw acceleration data of X axis <-- works!
> USHORT LatestRawAccelDataY; // latest raw acceleration data of Y axis <-- works!
> USHORT LatestAccelDataX; // latest acceleration data of X axis (average in 40ms) <-- Works even better?
> USHORT LatestAccelDataY; // latest acceleration data of Y axis (average in 40ms) <-- Works even better?
> CHAR Temperature; // latest temperature
> USHORT LatestZeroG_X; // latest zero-G offset of X axis <-- Seems to be the current notion of "center"
> USHORT LatestZeroG_Y; // latest zero-G offset of Y axis <-- ""
> } ACCELREPORT, *PACCELREPORT;

んー・・・Sensor.dll が機種間の再を隠ぺいしてくれてると思ったけど、構造体のサイズからして違うし、そうでもないかも。でもより確実なのは確か。ここまでの構造はだいたい一致してるみたいだし。(Temperature が CHAR なのが気になるけど。あと PresentState の値が少し違いそう?)


(2008-07-04 追記)

新しいThinkpad にも対応した加速度センサ値取得プログラム - hirax.net::inside out::2008-02-18

とのことで、Sensor.dll を使うように変更されている様子。仕事が落ち着いたら試したい。


(2008-09-15 追記)

>・InputBuffer が 0 bytes → 8 bytes に変更されて、0x53504154 という値が渡されている

って書いたけど、よく考えたらこれ「SPAT」を ASCII で表現してるだけ?取得した値の先頭に「SPAT」って入ってたけど、自分でそういう値を渡してるからなのかも。

posted at 07:47:14 on 2007-08-30 by miau - Category: Hardwares No Trackbacks - Permalink

TrackBack

このエントリにトラックバックはありません
現在トラックバックは受け付けていません。

Comments

No comments yet

Add Comments

現在コメントは受け付けていません。
お手数ですが、 こちら のコメント欄にでも記載していただければと思います。