イベントドリブンな設計 〜 Akerunつくったエンジニアのそこそこ大規模な組み込みプログラミング

こんにちは。Akerunエンジニアの @ishturk です。

Akerun Advent Calendar 6日目の記事です。よろしくお願いします。

今日は、AkerunのようなIoTデバイスの組み込み設計のお話です。

以前、このような記事を書きました。

qiita.com

今回は、もう少し具体的な内容を書いてみたいと思います。

イベントドリブンな設計

IoTガジェットや家電など、人が「操作」するものは、イベントドリブンで考えると、とても設計しやすくなります。Akerun Proも然り。結果、ステートマシンとの親和性が最高!

イベントドリブンとは

「Aという事象が発生したらαという処理をする」というものをイベント駆動とかイベントドリブンとか呼びます。ここで事象のことを「イベント」と呼びます。 これらを組み合わせてソフトウェアを組み上げていくことをイベントドリブンな設計と(僕は)呼んでます。

プログラマはイベントが発生することをしばしば「発火する」という表現を好んで使います。 代表的なイベントの種類としては、

  • ユーザー操作
  • センサ値などの値の変化
  • 時限(定期)イベント

があります。次項以降では、これらのイベントを扱うケースを具体的に考えてみます。

イベントドリブンな設計の利点

モジュールをカプセル化しやすい

直接的ではないですが、結果としてモジュールの責任を分離しやすいです。イベントを送る側と受ける側が独立(状態変数などを参照しないように)するのが重要です。

処理を追加しやすい

ユースケースが増えた場合、どう組込むか悩む場面が多いですね。この設計の場合、ユースケースの入り口を「イベントの発生」と捉えれば、一連の流れを実装しやすいです。また、処理を使いまわす場合は、同一のイベント(パラメータを変えることでその後の処理をフィットさせる)で実現できるのでコードの冗長化も回避し易いです。

コードの可読性

(慣れもありますね)

どう設計するのか [概要]

以前の記事で書いたように、ステートマシンで設計するのがベスプラです。 イベントが発火したとき、どのように振る舞うかが状態に依って決定され、処理を行います。

どう設計するのか [モジュール設計]

各モジュールが受け持つ範囲を明確にしておくことがポイントです。

たとえば次のような感じ

  • イベントを発生させるモジュール
  • イベントを配送するためのインターフェース
  • イベントを受け取るステートマシン
  • ステートマシンがアクションとして呼び出すアプリ

という責任分担をして、下のような関連にするとシンプルに実装できそうです。

f:id:photosynth-inc:20171206010419p:plain

どう実装するのか

サンプルコードを書いてみましょう。先述のモジュール構成とほぼ対応しています。

// アプリ処理
void application_unlock(void) {
    // unlock処理
}

void application_lock(void) {
    // lock処理
}

// ステートで判断してアプリ処理を呼び出す
void state_func_locked(event e) {
    switch (e.signal) {
        case E_EVENT_BUTTON:
            if (e.param == PUSHED) {
                application_unlock();
            }
            break;
        default:
            break;
    }
}

void state_func_unlocked(event e) {
    switch (e.signal) {
        case E_EVENT_BUTTON:
            if (e.param == PUSHED) {
                application_lock();
            }
            break;
        default:
            break;
    }
}

// イベントをステートに配送
state_valiable v = STATE_LOCKED;
void state_machine_dispatch(event e) {
    switch (v) {
        case STATE_LOCKED:
            state_func_locked(e);
            break;
        case STATE_UNLOCKED:
            state_func_unlocked(e);
            break;
        default:
            break;
    }
}

// イベントをステートマシンに配送
void event_manager_notify_event(event e) {
    state_machine_dispatch(e);
}

// ボタンが押されたイベントを発生
void operation_facade_proc(operation_type_e type) {
    switch (type) {
        case BUTTON_PUSHED: {
            event e = {E_EVENT_BUTTON, PUSHED}
            event_manager_notify_event(e);
            break;
        }
        default:
            break;
    }

}

// ボタンが押されたら呼ばれる関数
void button_driver_pushed(void) {
    operation_facade_proc(BUTTON_PUSHED);
}

最後に

ステートマシン信者の僕(ら)にはイベントドリブンな考えはとても強い武器になりますね。

明日も自分が担当です!

告知

弊社ではエンジニアを募集しています!

www.wantedly.com