キノコの自省録

日々適当クリエイト

IoT智絵里アラームを作ろうと思う(8) - ソフトウェア編

下書きから発掘。何かを書き直そうとしたっぽいんですが、全く覚えていないのでそのままポスト。

とりあえず智絵里アラームのソフト編です。どんな構造になっているのか説明します。

設計

ソフトウェアの機能ブロックを図に表すとこんな感じになっています。大まかに4つのブロックに分かれています。

f:id:kinokorori:20180512154444p:plain:w600

今回、この4ブロックは全てプロセスで分割されています。

アラーム設定プロセス

 アラームをユーザに設定させるクライアントと、設定された情報を外部に提供するWebサーバです。サーバ自体は今回Node.jsで実装しています。クライアントサイドはAngularJS+Bootstrap4を利用しています。

 このアラーム設定プロセス自体は、最初智絵里アラームの外部PCにデプロイしていましたが、現在は智絵里アラーム(Raspberry Pi)内部にデプロイしています。データベースはSQLiteを使用していますが、別のRDBMSでも構いませんし、Amazon RDSのように、別サーバにあっても構いません。

アラーム取得プロセス

 Raspberry Pi上で実行されるプロセスです。アラーム設定プロセスから、アラーム情報を定期的に取得します。現時刻と照らし合わせて、アラームを再生すべきと判断した場合、アラーム再生プロセスを起動します。智絵里アラーム起動と同時に、このプロセスも起動します。アラーム取得というか、タスクスケジューラみたいなものですね。

 アラーム設定プロセスへは、HTTP GETでアクセスして、アラーム情報を取得します。アラーム設定プロセスとのインタフェースにHTTPを利用していると言い換えても良いです。

アラーム再生プロセス

 Raspberry Pi上で実行されるプロセスです。アラーム取得プロセスからキックされます。アラームを再生するとともに、GPIOの信号をチェックして、スヌーズやアラームオフを制御します。アラーム再生が完全に終了すると、プロセスを終了します。

 アラーム時刻が被る可能性があるため、再生プロセスは複数存在する場合があります。

シャットダウンプロセス

 Raspberry Pi上で実行されるプロセスです。GPIOの信号をチェックして、必要に応じてシャットダウンを行うプロセスです。今回の実装では、スヌーズボタンを長押しを監視しています。智絵里アラーム起動と同時に、このプロセスも起動します。

この通り、モノリシックなプログラムではなく、機能間をできるだけ疎にして、逆にWebサーバも特別な存在ではなく、システムのプロセスの1つという扱いで設計しています。

※マイクロサービスアーキテクチャをちょっとだけ意識したところがありますが、組み込み機器ということもあって全く不完全です。組み込みソフトって、基本的にプロセスをバラバラにするという考え方はあまり見ない気がします。

機能

アラームとしての実装した機能は以下の通りです。

  • 最大10個のアラーム設定
  • アラームごとの有効無効切り替え
  • スヌーズするかどうか
  • アラーム繰り返し指定
    • 1回、指定した日付のみ
    • 曜日繰り返し
    • 毎日定時刻

キャプチャ貼った方が早いですね。この通り。

f:id:kinokorori:20180512171347p:plain:w600

データベース設計

上記アラーム設定を保存するデータベーステーブルはこんな感じです。

CREATE TABLE alarms (_id INTEGER PRIMARY KEY
        , alarm_id INTEGER NOT NULL
        , epoch INTEGER DEFAULT 0
        , enabled INTEGER DEFAULT 0
        , snooze INTEGER DEFAULT 0
        , repeat_type INTEGER DEFAULT 1
        , repeat_week_bit INTEGER DEFAULT 0)

repeat_typeは1がワンショット、2が曜日指定、3が毎日です。repeat_week_bitは、曜日指定のビットマスクが設定されています。

ワンショットだけ年月日時分秒が必要ですが、他のリピートタイプの場合は時分秒まで必要ありません。かといってdateとtimeを別々に指定するようにするとTimeOffsetの影響を受けてしまって面倒なため、UNIX時間で保存、ワンショットの場合は年月日時分秒、それ以外はUNIX時間の時分秒だけをクライアント側で解釈するようにしました。

アラーム駆動シーケンス

やや細部省略していますが、シーケンス図は次の通り。

f:id:kinokorori:20180520212841p:plain

OSが立ち上がった後、アラーム設定サーバと、アラーム取得プロセスと、シャットダウンプロセスが立ち上がります。アラーム取得プロセスは、定期的にサーバから情報を引っ張ってきて、必要に応じてアラーム再生プロセスを立ち上げます。条件についてはシーケンス図に書いてません。シャットダウンの動作も図から省略してしまいましたが、ここは本質ではないので良いでしょう。

この通り、データの取得はポーリングですが、WebSocketやXMPPなどで、設定変更を通知するようにした方がスマートだと思います。

アラーム取得プロセスは、アラーム時刻の30秒前になると、再生プロセスを立ち上げます。なので、アラーム設定のWebを通じたキャンセルは30秒以上余裕が必要です。それ以降は手で止めてくれということです。再生プロセスの引数には、UNIX時間とスヌーズの有無を渡します。

上記の通り、アラームの種別は、年月日時分秒を直接指定したもの、毎日特定の時刻に再生するもの、特定の曜日の特定の時刻のみ再生するものの3種類があります。年月日時分秒直接の場合、UNIX時間がそのまま使えるので問題ありませんが、他の2つは、UNIX時間を年月日時分秒に直さないとアラームが鳴らせません。なので、RaspberryPiのTimeOffsetを日本時間(UTC+9:00)に設定しておく必要があります。

アラーム再生プロセスは、起動後、引数として渡されたUNIX時刻になるまでウェイトし、時間になったら再生します。スヌーズボタンの入力を確認したら一度再生を停止して3分間のスリープに入ります。アラームOffを検出した場合、プロセスを終了します。

アラームのUNIX時間変換

毎日指定時刻にアラーム、曜日指定アラームについては、UNIX時間がアラーム設定サーバから渡されないため、クライアント側で変換する必要があります。

まとめ

ソフトの話をすると、どうしてもお固くなってしまいます。。。なので、エントリ書く気もあんまり起きないんですよね。書いてて一番楽しかったのがデコレーション編。

今普通に使用していますが、なかなか良い感じです。全く光ったりしないので、動いているのかどうかわかりにくいなど、ちょっとしたカスタマイズは必要かな、というところ。

ともあれ、智絵里アラーム自体のエントリはこれで終了です。