前回、Raspberry Pi で音を鳴らしてみましたが、今度はインターフェースの改善を試みたいと思います。

Raspberry Pi を操作するのにこれまでは TeraTerm でSSH接続して操作してきましたが、ホストマシンを立ち上げる必要もあり、Raspberry Pi の電源を落とすのにも一苦労(という程の苦労でもないのですが)します。

かと言って、Raspberry Pi に電源スイッチを(物理的に)増設するほどでもない。
何よりソフトソフトしていたいのです。(半田ゴテとかとは無縁の人なので。自分は。)

◆Raspberry Pi にジョイスティックを接続
一昔前に、PS3 の純正ジョイスティックのボタンがヘタってきたために買ったジョイスティック(GAMETECH ワイヤレスバトルパッドターボ3)がありましたので、それでどうにかしたいと思います。

# ジョイスティック?ジョイパッド?ゲームパッド?コントローラ?
# ちなみにアレは何て呼ぶのが正解なんでしょうか?
# とりあえずここではジョイスティックで統一しておきます。

ワイヤレスバトルパッドターボ3 はUSBの無線アダプタと、コントローラとに分かれています。
現在のうちの Raspberry Pi は↓のような構成になっています。緑色に光ってるのが無線アダプタです。

ss_rpi_connect01

このジョイスティックの無線アダプタであるUSBを接続したところ、
/var/log/messages に下記のようなログが出てました。
usb 1-1.2: new full-speed USB device number 10 using dwc_otg
usb 1-1.2: New USB device found, idVendor=1915, idProduct=001e
usb 1-1.2: New USB device strings: Mfr=1, Product=2, SerialNumber=0
usb 1-1.2: Product: PS3 wireless GAME PAD
usb 1-1.2: Manufacturer: ACRUX
input: ACRUX PS3 wireless GAME PAD as /devices/platform/bcm2708_usb/usb1/1-1/1-1.2/1-1.2:1.0/0003:1915:001E.0006/input/input5
hid-generic 0003:1915:001E.0006: input,hidraw0: USB HID v1.11 Gamepad [ACRUX PS3 wireless GAME PAD] on usb-bcm2708_usb-1.2/input0
また、/dev/input/js0 が増えました。
ここまでは何もしていないですが、正常に認識できているようです。

◆理想のジョイスティックキー入力検出を求めて...
さて、これだけだと、ジョイスティックのボタンをポチポチ押しても、無反応です。
少し Google 先生と相談です。。。

pygame という python のモジュールでジョイスティックの入力が取得可能らしいですね。。。
python... 馴染みがないし、分かりませーん。C とシェルくらいしかまともには使えませーん。

その他、いくつか試してみましたが、、、
キー入力をポーリングで検出するのはどうにかしたいですねー。

sleep しながらループしないと top コマンドで CPU 使用率が100%です。
→ sleep 時間が長いと CPU 使用率は下がるが、キー入力を取りこぼす。
     キー入力の反応が悪く、キモチ悪い。
→ sleep 時間が短いと、キー入力の反応はいいものの、CPU 使用率が上がる。
     0.1秒 sleep だとこの処理だけで、CPU 使用率が 10~20% いってしまいます。
     また、反応がいいとはいいつつも、0.1秒 sleep でも少し違和感を感じました。

Raspberry Pi はただでさえ貧相なCPUなので、キー入力がない時は素直に寝ていて欲しいです。
(待ち受け(idle) 時は1%だってCPUを使って欲しくないのです。)

◆Raspberry Pi でのジョイスティックのキー入力検出
自分の求める要件としては、下記4点です。
・ボタンを押した時の動作はシェルで好きに書く事ができる(カスタマイズが可能)。
・キー同時押しが検出可能。(L1+L2+R1+R2+Start+Select でシャットダウンしたいからです。)
・ポーリングでのキー入力検出ではなくて、イベントドリブン(キー入力がある時だけ起きてくる)のがいい。
・キー入力の反応がサクサク。違和感を感じない。

もう少し頑張って探そうかとも思いましたが、どうせ頑張るなら自分で作る方向のベクトルで頑張った方がいろいろ近道かと思い、いろいろ参考にしつつ C で作ってみる事にしました。

自作したジョイスティック入力検出デーモン(jsinputd と命名)が下記です。(結構、真面目に書きました。)
$ curl -O http://sstea.blog.jp/raspi/csrc/jsinputd.c
下記で Raspberry Pi 上でもビルドできると思います。-DDEBUG はなくても可。
(もちろんホスト側のクロスコンパイラでクロスビルドでも全然問題ありません。)
$ gcc jsinputd.c -o jsinputd -O2 -DDEBUG

jsinputd.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/joystick.h>

#ifdef DEBUG
#define ERROR(...)    (fprintf(stderr, "<JSINPUTD:E> " __VA_ARGS__), -1)
#else /* !DEBUG */
#define ERROR(...)    (-1)
#endif /* !DEBUG */

struct joystick_struct {
    int fd;
    unsigned char nr_buttons;
    unsigned char nr_sticks;
    short *button;    /* button[nr_buttons] */
    short *stick;    /* stick[nr_sticks] */
    char *input;    /* input[nr_btn * 2 + nr_stk * 7 + 1] */
};

static int joystick_output(struct joystick_struct *js, const char *out_file);
static int joystick_get_input(struct joystick_struct *js);
static int joystick_get_info(struct joystick_struct *js);
static int joystick_init(struct joystick_struct *js, const char *dev_file);
static void joystick_exit(struct joystick_struct *js);
static void joystick_main(const char *dev_file, const char *out_file);

static int joystick_output(struct joystick_struct *js, const char *out_file)
{
    FILE *fp = stdout;
    unsigned char i, nr_btn, nr_stk;

    if (out_file != NULL) {
        fp = fopen(out_file, "w");
        if (fp == NULL) {
            return ERROR("fopen(%s)\n", out_file);
        }
    }

    nr_btn = js->nr_buttons;
    nr_stk = js->nr_sticks;

    for (i = 0; i < nr_btn; i++) {
        sprintf(&js->input[i * 2], "%2d", js->button[i] ? 1 : 0);
    }
    for (i = 0; i < nr_stk; i++) {
        sprintf(&js->input[nr_btn * 2 + i * 7], "%7d", js->stick[i]);
    }
    js->input[nr_btn * 2 + nr_stk * 7] = '\0';

    fprintf(fp, "%s\n", js->input);

    if (out_file != NULL) {
        fclose(fp);
    }

    return 0;
}

static int joystick_get_input(struct joystick_struct *js)
{
    int ret;
    struct js_event ev;
    unsigned char num;

    do {
        ret = read(js->fd, &ev, sizeof(struct js_event));
        if (ret != sizeof(struct js_event)) {
            return ERROR("read : %d\n", ret);
        }
    } while (ev.type & JS_EVENT_INIT);

    num = ev.number;
    switch (ev.type) {
    case JS_EVENT_BUTTON:
        if (num < js->nr_buttons) {
            js->button[num] = ev.value;
        }
        break;
    case JS_EVENT_AXIS:
        if (num < js->nr_sticks) {
            js->stick[num] = ev.value;
        }
        break;
    default:
        break;
    }

    return 0;
}

static int joystick_get_info(struct joystick_struct *js)
{
    int ret, fd = js->fd;
#ifdef DEBUG
    char name[64];
#endif /* DEBUG */


    ret = ioctl(fd, JSIOCGBUTTONS, &js->nr_buttons);
    if (ret < 0) {
        return ERROR("ioctl(JSIOCGBUTTONS)\n");
    }
    ret = ioctl(fd, JSIOCGAXES, &js->nr_sticks);
    if (ret < 0) {
        return ERROR("ioctl(JSIOCGAXES)\n");
    }
#ifdef DEBUG
    ret = ioctl(fd, JSIOCGNAME(sizeof(name)), &name);
    if (ret < 0) {
        return ERROR("ioctl(JSIOCGNAME)\n");
    }
    printf("name=\"%s\", buttons=%d, sticks=%d\n", name, js->nr_buttons, js->nr_sticks);
#endif /* DEBUG */


    return 0;
}

static int joystick_init(struct joystick_struct *js, const char *dev_file)
{
    int ret, array_size;
    unsigned char nr_btn, nr_stk;
    unsigned char *p;

    ret = open(dev_file, O_RDONLY);
    if (ret < 0) {
        return ERROR("open(%s)\n", dev_file);
    }
    js->fd = ret;

    ret = joystick_get_info(js);
    if (ret < 0) {
        close(js->fd);
        return ret;
    }
    nr_btn = js->nr_buttons;
    nr_stk = js->nr_sticks;

    array_size = (nr_btn + nr_stk) * sizeof(short);
    p = malloc(array_size + nr_btn * 2 + nr_stk * 7 + 1);
    if (p == NULL) {
        close(js->fd);
        return ERROR("malloc\n");
    }
    js->button = (short *)p;
    js->stick = (short *)&p[nr_btn * sizeof(short)];
    js->input = (char *)&p[array_size];

    return 0;
}

static void joystick_exit(struct joystick_struct *js)
{
    free(js->button);
    close(js->fd);
}

static void joystick_main(const char *dev_file, const char *out_file)
{
    struct joystick_struct js;

    if (joystick_init(&js, dev_file) == 0) {
        do {
            if (joystick_output(&js, out_file) < 0) {
                break;
            }
        } while (joystick_get_input(&js) == 0);

        joystick_exit(&js);
    }
}

/*
 * arg1 : device file (default is /dev/input/js0)
 * arg2 : output file (default is stdout)
 */

int main(int argc, char **argv)
{
    char *dev_file = "/dev/input/js0";
    char *out_file = NULL;

    if (argc >= 2) {
        dev_file = argv[1];
    }
    if (argc >= 3) {
        out_file = argv[2];
    }

    joystick_main(dev_file, out_file);

    return ERROR("exit\n");
}

使用方法としては、
・第一引数にデバイスファイル(省略時は /dev/input/js0)
・第二引数に出力ファイル(省略時は標準出力)
を指定する感じです。
→ "Usage: jsinputd [device-file] [output-file]"

出力フォーマットは ボタン入力は 0 か 1、スティック入力は -32767 ~ 32767 の範囲で出力します。
順番はボタン0、ボタン1・・・とボタンの数だけ出力し、次いでスティック0からスティックの数分出力されます。

とりあえず、引数なしで実行。
$ ./jsinputd
name="ACRUX PS3 wireless GAME PAD", buttons=13, sticks=6
 0 0 0 0 0 0 0 0 0 0 0 0 0      0      0      0      0      0      0
Start ボタンを押したまま、Select ボタンを押して離して、その後 Start ボタンを離した場合。
下記のようにキー入力に変化があった時だけ動作し、出力が出ます。
結構ヌルサクでいい感じです!!(そして、無操作時は CPU 負荷 0 です!)
$ ./jsinputd
name="ACRUX PS3 wireless GAME PAD", buttons=13, sticks=6
 0 0 0 0 0 0 0 0 0 0 0 0 0      0      0      0      0      0      0
 0 0 0 0 0 0 0 0 0 1 0 0 0      0      0      0      0      0      0
 0 0 0 0 0 0 0 0 1 1 0 0 0      0      0      0      0      0      0
 0 0 0 0 0 0 0 0 0 1 0 0 0      0      0      0      0      0      0
 0 0 0 0 0 0 0 0 0 0 0 0 0      0      0      0      0      0      0


ちなみに、ジョイスティックの無線アダプタを Raspberry Pi から抜くと、ioctl で失敗し、終了します。
それ以外だと、基本的には Ctrl-C で中断しない限り、終了しません。

◆ジョイスティックのキー入力検出結果の取り込み

後は、上記で作成した jsinputd の出力結果をシェルで取り込めば、やりたい事は出来そうです。
標準出力に出されても困るので、/tmp/jsinput にでも出力させておこうと思っています。
この jsinputd の出力結果である /tmp/jsinput をシェルで取り込んでアレヤコレヤするわけです。

jsinputd デーモンは起動時にでも、バックグラウンド実行させておく想定です。
("./jsinputd /dev/input/js0 /tmp/jsinput &" みたいな感じで実行すればいいだけです。上記の想定で、ああいうインターフェースにしました。)

ただし、シェルから /tmp/jsinput を取り込むのに、ポーリングしながら読み込んだのでは本末転倒です。

幸いにも、ファイルに変化があった時だけ通知するという inotify という仕組みが Linux カーネルにはあり、それをシェルから利用できるツールがあります。
まずは inotify-tools をインストールしておきましょう。
$ sudo apt-get install -y inotify-tools

ちょっと長くなってきたので、続きは次回