はじめに
下記の続きです。
リアルタイムで内容を考えているため、ご意見・ご要望があれば、ぜひご連絡下さい。
コードはgithubで公開中
前回からの変更点と決定事項
ビルドツール
ビルドツールをAutotoolsからCMakeに変更しました。
CMake version 2.8以降が必須環境となります。
プロダクトコード
プロダクトコードはC言語にします。
あまり複雑なコードを書く予定がないため、生C言語で続ける予定です。コードが複雑になれば、glibの利用を検討します。
作るもの
キーボードの「A」ボタンを押すと、caps lockのLEDがON/OFFする。
とりあえずここから始めて、その場のノリと思いつきとフィードバックで仕様拡張していきます。
今回掲載するコードを除き、TDDで作っていきます。
最終的には、ラズパイなどの別ハードで、キーボード以外のハードウェアを使用し、ちゃんと移植できたよね、めでたしめでたし、としたいですね!
現時点で本当に具体的なハードウェアを選定していないです。ほら、その方が実際の開発っぽいでしょ?
問題提起
さて、作るものに戻って、キーボードの「A」ボタンを押すと、caps lockのLEDがON/OFFすると言われてプログラムを作るとき、どのような実装になるでしょうか?
愚直に作ると、概ね次のような疑似コードになるのではないでしょうか。
static void mainloop() {
do {
キーイベントを取得
if (Aボタンが押された) {
write(LEDをトグル);
}
} while (何らかの条件を満たすまで);
}
int main() {
mainloop();
return 0;
}
上の疑似コードを実際に作ると次のようなコードになりました。
libevdevの動作は、前回のライブラリ理解のためのテストで確認しましたね。
#define KEYBOARD_DEVICE "/dev/input/event2"
#define LED_DEVICE "/sys/class/leds/input2::capslock/brightness"
#define KEY_RELEASED 0
#define KEY_PRESSED 1
static void mainloop() {
struct libevdev *dev = NULL;
int key_fd = open(KEYBOARD_DEVICE, O_RDONLY|O_NONBLOCK);
int rc = libevdev_new_from_fd(key_fd, &dev);
if (rc < 0) {
fprintf(stderr, "Failed to init libevdev (%s)\n", strerror(-rc));
exit(1);
}
int led_fd = open(LED_DEVICE, O_WRONLY|O_NONBLOCK);
if (led_fd < 0) {
fprintf(stderr, "Failed to init LED device.\n");
exit(1);
}
bool led_on = false;
do {
struct input_event ev;
rc = libevdev_next_event(dev, LIBEVDEV_READ_FLAG_NORMAL, &ev);
if (rc == 0) {
if (ev.type == EV_KEY && ev.code == KEY_A && ev.value == KEY_PRESSED) {
led_on = !led_on;
char buf[2];
snprintf(buf, 2, "%d", led_on ? 1 : 0);
write(led_fd, buf, 2);
}
}
} while (rc == 1 || rc == 0 || rc == -EAGAIN);
libevdev_free(dev);
close(key_fd);
close(led_fd);
}
int main() {
mainloop();
return 0;
}
Linux環境がある方は実際に動かすことができます。
前回記事を参考に、キーボードデバイスとLEDデバイスを確認し、2つのマクロ(KEYBOARD_DEVICE, LED_DEVICE)のパスを修正して下さい。
ビルドし、build/bad_example/bad_exampleを実行すると、「A」キーを押すたびにLEDがONされたりOFFされたります(環境によりroot権限が必要です)。
さて、このコードは一見、問題がないように見えますが、多くの問題を内包しています。
- 全てのコードが意図通りに動作していることが明確でない。(エラー処理、終了時の動作確認ができない)
- ロジックとハードウェアが密結合しており、ハードウェア変更、追加がロジックに影響する(オープン・クローズドの原則違反)。
- mainloop関数を変更する理由が複数存在する(単一責務の原則違反)。例えば、キーイベント追加、LEDデバイス追加、双方ともにmainloopへの修正が発生する。
これらの理由のため、機能拡張を繰り返していくと、機能追加が次第に困難になり、デグレを起こさずに機能追加することが難しくなります。
次回からは、今回のコードをTDDで開発するとどうなるか、を実際にやってみて、順に機能を追加していきます。