ドアロック状態通知の仕組みを作る
家を出たとき最後に鍵をかけていたことを確認できて安心したいという動機から自作を試みてみました。
もちろん、アクティブに鍵を操作できる製品は何種類も売られていますが、 ドア内施工にて電源の引き回しが必要になったり、高価な電池をこまめに入れ替えする必要があったりします。
ただ、そもそもドアのそばにいないときにドアロックを操作することが役に立つのって外出や家の奥に入ってからドアロックしてないことに気づいてドアロックする場合のみですね。
現状、困っているのは日々外出するときに鍵をかけることはルーティン的に実行してるんですが、車に乗った直後などに「あれ?この鍵をかけた記憶は今の記憶?先日の記憶?」っていう混乱があって、念のため鍵をロックしたのか確認に行くとやはり鍵がかかっていたっていうことが頻発しているからです。
こういう時にスマホの通知を見れば最後に施錠したかどうかが確認できます。
対象
実用レベルを想定していて、安全面についての考慮を含むためソフトウェアの挙動を理解をするためには前提知識は多少必要になります。
なので「動かして運用したい人」と「中の仕組みに興味ある人」とで書き分けてみます。ただし、「動かして運用したい人」であってもコマンドラインでの作業は必須です。
動作イメージ
構成
4コンポーネントあります。
- nRF52系マイコン+IMU+電池
- ラズパイ3~5+ACアダプター+ケース
- PWAによるアプリケーション
- 通知を行うコマンドラインツール
nRF52系マイコン+IMU+電池
MicroBit:V2と電池ボックスを使って実装します。
普段システムオフ(電源オフ)状態で起動したらBLEデバイスとして動作
- リセットスタート
- IMU割り込み起動セットアップ
- BLEアドバタイズ
- 接続待ち
- レポート通知
- 不要な機能をOFFにしてシステムオフ
という挙動をTinyGoを使って実装しています。
ラズパイ上の実装
BLEデバイスの発見と読み取った情報に基づいてプッシュ通知を代行
- BLEスキャン
- 該当デバイスの発見及び接続
- レポート通知待ち
- レポート受信でパース
- サブスクライブ情報フォルダをスキャン
- サブスクライブ毎にWebPushサーバーにプッシュ要求をポスト
- 最初に戻る
以上の実装をGoを使って実装しています。また、後半のスキャン&ポストには後述の通知用コマンドラインツールを利用します。
PWA実装とコマンドラインツールについて
- PWAによるアプリケーション
- 通知を行うコマンドラインツール
別記事にてまとめてあります。
サーバーレスな通知の仕組み
コード全体
GitHub - SWITCHSCIENCE/DoorLockMon
git clone https://github.com/SWITCHSCIENCE/DoorLockMon
cd DoorLockMon
- firmware/: MicroBit:V2用のソースコード
- ble-forwarder/: 中継用プログラムコード
ファームウェアの解説
TinyGoを使って実装しています。
TinyGoについて
- Go言語でマイコンプログラミングを行うためのサブセット
- 書き込みツール連携も充実しています
- BLE周りや各種センサドライバーなどが充実しつつあります
- ただし、BLEのボンディング機能が未実装
ビルドと書き込みにあらかじめ必要なツール
- Gnu Make
- Go v1.22以降
- TinyGo v0.32以降
- OpenOCD v0.12以降
Windows(scoop利用): <- Windows推奨
- scoop install make go tinygo openocd
Windows(winget利用):
- winget install ezwinports.make
- winget install GoLang.Go
- winget install tinygo-org.tinygo
- Download&Install: https://github.com/openocd-org/openocd/releases/download/v0.12.0/openocd-v0.12.0-i686-w64-mingw32.tar.gz
MacOS(Homebrew利用):
- brew install make go tinygo openocd
初回乱数キー生成
これはセキュリティを担保するために運用者毎に異なるランダムキーを生成します。 なので初めてビルドする前に1度だけ実行してください。
cd DoorLockMon
go generate ./...
ここで生成されるキーは中継器とデバイスとで一致している必要があります。(乱数キー生成の前後で異なるビルド同士では通信できません)
ファームウェアビルド&書き込み
cd DoorLockMon/firmware
# MicroBit:V2をUSBケーブルでPCに接続
go mod tidy
make softdevice
make flash
中継器のセットアップ
中継ツールのビルド
cd DoorLockMon/ble-forwarder
GOOS=linux GOARCH=arm GOARM=7 go build .
通知ツールのビルド
GitHub - SWITCHSCIENCE/notify-toolContribute to SWITCHSCIENCE/notify-tool development by creating an account on GitHub.
https://github.com/SWITCHSCIENCE/notify-tool
git clone https://github.com/SWITCHSCIENCE/notify-tool
cd notify-tool
GOOS=linux GOARCH=arm GOARM=7 go build .
Systemd用ユニットファイル
(ユーザー名やインストールフォルダを変更した場合は適宜修正してください)
doorlockmon.service:
[Unit]
Description=Door Lock Mon
After=network.target
[Service]
Restart=on-failure
WorkingDirectory=/home/pi
ExecStart=/home/pi/ble-forwarder
Restart=yes
User=pi
[Install]
WantedBy=multi-user.target
ラズパイセットアップ
- 「RaspberryPi Imager」にてRaspberry-Pi-OS(64-bit)またはRaspberry-Pi-OS-Lite(64-bit)をインストールし、piユーザーとネット接続およびSSH可能な状態を作成
- 先ほどビルドしたバイナリ2つとservice設定ファイルをラズパイに転送:
- scp ble-forwarder pi@raspberrypi.local:/home/pi/
- scp notify-tool pi@raspberrypi.local:/home/pi/
- scp doorlockmon.service pi@raspberrypi.local:/home/pi/
- sshログインし、service設定を移動し自動起動する設定を行う
- chmod a+x ble-forwarder notify-tool
- sudo mv doorlockmon.service /etc/systemd/system/
- sudo systemctl daemon-reload
- sudo systemctl start doormlockmon
- sudo systemctl enable doormlockmon
- ./notify-tool init
- ./notify-tool subscribe
- 表示されるQRコードまたはURLを通知を受けたいデバイスで開く
- iOSのみ「ホーム画面に追加」を行い、アイコンから開きなおす
- 表示画面にてSubscribeをクリックし、表示JSONをsshのプロンプトに貼り付けENTERキー
- 必要な通知を受けたいデバイスがあれば上記を繰り返す
以上でセットアップは完了です。
動作テスト
- MicroBitを動かすと起動しますLEDマトリクスを上向きにするとロック状態と判定しLEDは「○」表示に
- それ以外の向きはアンロック状態として判定してLEDは「×」表に
- 中継器がデバイスを発見・接続してきたら機器認証を行います
- 機器認証にパスしたらロック状態と電池残圧を中継器に送ります
- デバイスはその後システムオフとして超低消費電力状態に移行します
- 中継器は受け取った情報を通知に載せ通知ツールをキックします
- その後中継器はスキャンモードで待機します
- 通知対象のデバイスそれぞれに通知が届きます。
ファームウェアの詳細
このセクションの内容はファームウェアの挙動について理解を深めたい方向けの情報です。
MicroBit:V2のブロックダイアグラム
- nRF52833がメインマイコン
- nRF52820はデバッガ兼UART中継USBインターフェースマイコン
- 今回利用したいのは「Motion Sensor(IMU)」
回路図における、I2C_INT_INTという信号線には以下の3要因の割り込みが論理和で入ってきます。
- Interface MCUのコマンド処理完了割り込み
- IMUの加速度センサ割り込み
- IMUの磁気センサ割り込み
以上のいずれかが発生したらLowアクティブになります。
割り込みでMCUを起動する
I2C_INT_INTにLowを検出したらシステムオフ状態のマイコンを起動する設定: (この手法はnRF5xマイコンの多くで利用可能です)
const I2C_INT_INT = machine.Pin(25)
nrf.P0.PIN_CNF[I2C_INT_INT].Set(
nrf.GPIO_PIN_CNF_DIR_Input<<nrf.GPIO_PIN_CNF_DIR_Pos |
nrf.GPIO_PIN_CNF_PULL_Pullup<<nrf.GPIO_PIN_CNF_PULL_Pos |
nrf.GPIO_PIN_CNF_SENSE_Low<<nrf.GPIO_PIN_CNF_SENSE_Pos,
)
このように設定しておくことで、システムオフ状態のメインマイコンは、IMU割り込みの発生から起動することができるようになります。
ただし、インターフェースマイコンはUSBケーブルの脱着時にも割り込みを発生させますので、そのままだと、I2C_INT_INTはアサート状態のままになってしまいます。
なので、インターフェースマイコンのI2Cポートにコマンドを送信して割り込みを解除する必要があります。
このコマンドに関する仕様はここにまとまっています。
-
nop_cmd
を送れば割り込み解除 - Interface Power mode を 0x08 にする
- Power LED Sleep state を 0x00 にする
以上の設定をやっておくと、電池駆動の際、インターフェースMCUはシステムオフ状態になり、LEDも点灯しなくなります。
IMUの設定
MicroBit:V2に乗っているIMU「lsm303agr」はtinygo.org/x/drivers/lsm303agrにてサポートされています。
- 6D割り込みという設定をしておきます
- これによりダイスの面が変更されたことを認識して割り込みをアサートするようになります
このあたりの細かい機能についてはサポートが無いので直接I2Cのレジスタ書き込みで設定します。
乱数キーの用途
TinyGoのBLEライブラリがボンディング未対応のため、接続機器認証を行うために利用しています。
- アドバタイズに乱数を載せて発信
- 中継器はその乱数と生成済み乱数キーを含んでMD5ハッシュを生成
- デバイスの状態通知を要求する際、そのハッシュを送信
- デバイスは同じハッシュ計算をあらかじめすましていて要求を受信したらそのハッシュが合致することを確認してから応答を返す
以上のシーケンスで未知の中継器やデバイスに対し接続したりデータを送ったりしないようになっています。
まとめ
- 電池駆動により省電力な施錠監視の仕組みが実現できました
- PWA用プッシュ通知を利用することでサーバーレスで通知の仕組みを実現できました
- TinyGoのBLEではセキュアペアリング機能がまだ使えないですが、簡易的にセキュアに実装しました
- TinyGo用のBLEライブラリはGoとPCの組み合わせでも利用可能なので、同じ記述でマイコン側とPCやラズパイ側の実装を書けわけることができます
- 中継にラズパイ相当が必要にはなってしまいますがラズパイはホームオートメーションハブとして優秀です
- 2か月ほど自宅で運用中ですが、家族で施錠確認ができるので安心して出かけられ便利です