ドアチャイムをM5StickCで拡張する

## 解決したい課題
コロナ禍以降、自宅で作業することが多くなっている方も多いかと思います。実は自宅で作業するときに一つ困っていることがあります。それは作業部屋ではドアチャイムが聞こえないというものです。これは次の図のような間取りで①のところにドアチャイムがあり、②のところに作業机があり、それぞれの距離が離れている上に壁がある配置になっていることが原因に挙げられます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55138/f14a6d55-0052-3b7b-6802-06dd82e08cc1.png)

ドアチャイムが聞こえないと荷物が受け取れず、配送業者の方にもご迷惑になってしまうので、今回はご家庭にあるものを使ってこの問題を解決していきたいと思います。

## パッと解決できないか考える
どんな問題も難しく考えれば何処までも難しく考えられてしまいます。そういうときは過剰にインベストしてしまい、仮に解決できたとしても見合ったリターンが得られない状況に陥ります。パッと解決できないかを考え、まずは軽く見積もりましょう。

### 案１：物理で攻める
音は空気の振動であり、人間の耳に聞こえるのは20Hzから20kHzと言われています。つまりドアチャイムの振動を作業部屋まで引き込めれば良いわけです。パッと思いつく簡単な方法は皆さんご存知の伝声管でしょう。伝声管は音を伝えるための管で金属管が一般的かと思います。よく知られているものだと天空の城ラピュタに登場する海賊ドーラ一家の母船のタイガーモス号で使用されているシーンが印象的です。また媒体はなんであれ物理的な振動が伝われば良いので管の代わりにワイヤーで伝える機構でも良いでしょう。糸電話っていうんですけどね。これらのアプローチはどちらも大きなメリットがあります。なんせ物理振動をわざわざアナログとかデジタルみたいな電気信号に変換する必要すらありませんからね。ソフトウェアエンジニアをやっているとどうしてもデジタルのアプローチありきで考えてしまいますが、使わないで済むならそれに越したことはありません。電子工作をしていてもアナログ-デジタル変換はいろんな部品が必要で大変ですものね。物理は大事です。

しかし大きな欠点がありました。伝声管を設置するには物理的な太さが必要であり、扉や壁を通さなくてはなりませんが、賃貸であるため原状復帰を考えるとそういった工事は行えません。また、糸電話の糸なら壁を這わすことは可能かも知れませんが、糸が物に触れると急激に減衰するため、現実的な音量を得ることができないでしょう。これらの課題が解決できないため、この案は没となりました。

### 案２：音量を上げる
音を部屋から部屋に「伝える」ことにこだわりすぎてしまった結果、難しく考えすぎてしまったようです。そもそも音はわざわざ伝えなくても、防音壁でもない限り音量を上げれば隣の部屋にも聞こえます。単純に音量が上がれば隣の部屋にいても聞こえるでしょう。
しかし設定で上げられる最大音量にはしてみたのですが、残念ながら仕事部屋からは聞こえませんでした。もし、これ以上の音量を出そうとするとアンプが必要になり、そこまでするとコストも増えるので他の策を考えたほうが良いでしょう。更に純粋にうるさいと猫からも不評だったため没としました。

### 案３：マイクとスピーカーで延長する。
案１と案２はどちらも直球過ぎました。そこまで単純に解決できるものでは無さそうです。素直に電子機器の力を借りる策を考えましょう。マイクをドアチャイムの前に置き、スピーカーを仕事部屋に置けばチャイムが鳴ったときに仕事部屋でも聞こえます。そんなに難しい問題ではありませんでした。
だが待ってください、常時電源をいれておく必要があり、リビングの音が常に仕事部屋に流れているわけです。つまり会話の音も常に流れることになり、プライバシーの問題があります。そうでなくてもリビングでテレビでもつけようものなら煩くて仕方がありません。もっとスマートな解決策が必要なようです。

## 課題の整理とソリューション
あれこれ考えたけどなかなか良い案がでてきません。とはいえ普段開発をしていても上手い具合に嵌るソリューションは簡単には出てこないものです。ここまで出てきた課題を改めて整理してみましょう。

### 課題１：物理的に遠い
リビングと仕事部屋はなんだかんだで距離があります。また案１でも課題に挙がったとおり、賃貸のため壁に穴を開けてコードを通すことはできないのでコードの引き回しはしたくありません。幸い２つの部屋の両方でWiFiは安定しているので、WiFiを使えば通信自体は行えます。

### 課題２：チャイムが鳴っていることの判定をどうするか
チャイムが鳴っているときにインターホンから電気的に信号を取り出せたら簡単なのですが、インターホンにそれ用のインターフェースはありません。改造すればできるでしょうが、賃貸の設置物のため、退去時に原状復帰が必要であることを考えると現実的ではありません。今回やりたいことからすると、スピーカーから一定量の音がでていることさえ検知できれば良いため、スピーカーにマイクを押し当てて設置し、音量で判定すれば良さそうです。

## 登り方
課題がクリアになったので登り方を考えます。マイクを繋いだデバイスとスピーカーを繋いだデバイスの２つを準備し、マイクをインターホンのスピーカー部分に貼り付けチャイムの音を拾い、一定以上の音を検知すると、もう片方のデバイスのスピーカーを鳴らせば要件は満たせそうです。
ざっくりとした配置は次の図のようになるでしょう。

![20230209.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55138/51c26b7a-15a5-aa9c-cd8a-3933f66c51d9.png)

## ハードウェア選定
登り方もそこまで難しくないところまで整理できたので、有り物でパッと作ってしまいたいと思います。
パッと思いつくものといえばエンジニアなら誰でも数個は持っているRaspberry Piでしょう。次の写真のものはRaspberry Pi Zeroでシリーズの中でも小型のものです。可愛いですよね。長年デスクトップLinuxを使っている身としては、ミドルウェアよりも下回りは馴染みがあるので僕的にも使い勝手が良いです。ただLinuxを動かし、その上にPythonやRubyで書いたプログラムを動かすとなると、プログラムを常駐させるために、systemdでサービスとして動かすとかcronで起動させるとかシステム周りのことを考える必要がでてきます。今回やりたいことはシンプルなので、そのプログラムだけを動かせるもう少し楽なハードウェアが他に無いか考えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55138/d62a225b-6eeb-6c44-f5b3-aa4c877dadc2.png)

僕はもともとAndroidアプリ開発者だったので古いAndroidデバイスは何台も余っています。Androidデバイスはカメラ・マイク・ディスプレイ・GPS・各種センサーが付いている上に、それらがJava/Kotlinで開発できるアプリからほぼ全て制御できるので、こういった工作には使い勝手が良いです。とはいえややオーバースペックなので、もっとシンプルな他のものを探します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55138/8af81b33-7876-9ef3-e088-93042859f8e6.png)

シンプルにいくならArduinoも良さそうなのですが、WiFi経由で通信するとなると、`Arduino MKR WiFi 1010` のようにWiFiを持ったボードにするか、WiFiモジュールを別でつける必要があり、やや面倒です。

この辺りはベストを求めると何処までもいけてしまいオーバーインベストになりがちです。最近だとESP32と呼ばれるWi-FiとBluetoothを内蔵する安価なマイコンがあり、個人的にも慣れているのでこのハードウェアで行きたいと思います。ESP32は小さなボードですがWi-Fiにちゃんと繋がる上にArduino IDEやVSCodeなどでコードを書いて簡単に動かすことができます。Arduinoシリーズに比べるとGPIOピンの数の少なさがやや心もとないですが、今回の工作程度なら十分でしょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55138/bb2fd448-b661-9cc9-db5e-c49da0eb664b.png)

しかし今回のユースケースを考えるとESP32自体がWi-Fiに繋がるとはいえ、アイクやスピーカーを取り付けないといけません。そう考えると開発ボードを２台用意し、取り付けたりするのはなんだかんだで面倒です。なんせこれは「開発ボード」ですので、もう少し一般的なものを使いたいと思います。

というところで、あれこれ考え、エンジニアなら誰でも持っている **M5StickC Plus** を使ってやっていきたいと思います。M5StickCはM5Stackシリーズのプロダクトの１つで、小さい筐体の中にディスプレイやボタン、通信モジュール、各種センサーなど、いろいろな機能が搭載されたモジュールです。搭載されている機能はプロダクトごとに異なりますが、GPIOピンやGroveの互換ソケットもついているため、必要に応じてモジュールを追加することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55138/961aaa1f-83a4-2175-7612-6aa5117f4bea.png)

- 参考リンク
  - [スイッチサイエンス様のM5StickC Plusの販売ページ](https://www.switch-science.com/products/6470)

今回使用するM5StickCはマイクもスピーカーも搭載されているのですが、取り回しや音量の都合があるため、それぞれのモジュールを追加します。マイクモジュールはケーブルで繋いでドアチャイムのスピーカー部に設置します。スピーカー側はHAT型のスピーカーモジュールを使用しました。


## 通信の方法
マイク側とスピーカー側のデバイスそれぞれがWi-Fiに繋げるわけですが通信内容はどうしましょう？やりたいことは簡単なのでシンプルにしたいものです。ローカルネットワークのためUDPでの通信としてもパケットロスの問題は無いので、UDPの１パケットで動作とかでも良いかもしれません。しかし、UDPのパケットであれこれするのは正直考えることが多いので面倒です。仮にTCPにしたとしても独自プロトコルにするとデバッグが大変です。
そういえばデバッグといえば丁度良いツールがありますよね。そう、Webブラウザです。
Webエンジニアなら誰しもtelnetで `GET /index.html HTTP/1.0` を叩いて動作確認をされたことがあると思います。内容的にはテキストと改行コードだけで表現できるので非常にシンプルです。なのでHTTPで特定のパスにアクセスされたらスピーカーを鳴らす方向で行きたいと思います。

スピーカー側のデバイスでHTTPサーバーを動かす場合、マイク側のデバイスからリクエストを送るため、ホスト名かIPを固定する必要があります。mDNSなどでホスト名を指定するとわかりやすいのですが、家の中のLANでしか通信しないので横着してIPを固定することにします。家のルーターは一般的なYAMAHA RTX830なので簡単な設定でMACアドレスからIPを固定できました。

## コードを書く
ハードウェアも通信の方法もクリアになったのでコードを書いていきましょう。M5StackシリーズはArduino IDEやVSCodeでコードを書けます。C/C++やPythonなど使えます（正しくはそれらの亜種）が、純粋に慣れているので今回はC/C++で書いていきます。

- スピーカー側（サーバー）
  - 電源が入るとWi-Fiに接続する
  - HTTPサーバーを立ち上げる
  - `/ring` にアクセスがあるとスピーカーを鳴らす
- マイク側（クライアント）
  - 電源が入るとWi-Fiに接続する
  - マイクを監視し、音圧が一定以上になるとサーバーにリクエストを投げる
    - リクエストを投げるといってもTCPで接続して固定のStringを投げるだけ

ソースコードはとりあえず動けばいいところで最低限で実装しました。
GitHubのリポジトリ: https://github.com/cattaka/DoorBellExtension

## 動作確認
一通りコードが書けたら動作確認しましょう。
片方のマイクを軽く叩いて擬似的に音を検知させ、動作するか確認をします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55138/32ddef5c-c760-52cb-f379-9d61a215cc6a.png)

次にスピーカー側のデバイスを実際の設置場所の仕事部屋に設置して確認します。
最後に実際のチャイムを鳴らして反応するか確認します。Production環境での確認は怠ってはいけません。念には念をいれて確認しましょう。

## 実運用とトラブル
これで完璧と思いたいところですが、どんなものでも最初から完璧に動くことは無いものです。むしろ一発できちんと動いたら逆に不安になるのがエンジニアの性です。幸いなことに実際に運用して数日が経過すると、いくつかの課題や不具合が見えてきました。

### ミーティング中に鳴り始めると煩い
ミーティング中に鳴リ始めると「ぅぉぉおおおお」っとびっくりしてパニクるものの、無情にもスピーカーは鳴り続けて大変なことになります。カメラの向こう側の視聴者にも聞こえてしまうため止めようとするのですが、さすがはM5StickC、バッテリーを内蔵しているので電源コードを引っこ抜いても爆音で鳴り続けます。そして電源はボタンを長押しが必要なため、数秒間は鳴り続けることになります。また、電源コードを引っこ抜いたら引っこ抜いたで後から挿し直すのを忘れ、電池が切れてしまい、時間が経ってから動いてないことに気づいて問題が拡大してしまいました。一時的に止めたいときに電源コードを抜くのはワークアラウンドとしては有効ですが、こういうことが常態化するとヒューマンエラーを誘発し、二次災害がどんどん大きくなる典型例です。
このような課題は放置すると運用上のリスクへと容易に発展するので対策を講じます。この課題はボタンを一度押すと３０秒ほどスピーカーをオフになる機能を追加することで対応しました。M5StickCはデフォルトでボタンがついているので、こういう機能をパッと作れるのが良いところですね。

### 気がついたら動かなくなっていた
設置して最初のうちはちゃんと動いていたのですが、稀に動いていないことがあることに気づきました。様子からするとマイク側かスピーカー側のデバイスのいずれかがネットワークから切断されているようでしたが、確実な原因は特定できませんでした。自分の書いたプログラム側の問題かもしれませんし、単純にWiFiが切断された後、再接続に失敗しているのかも知れません。この現象は１〜２週間に１回くらいの頻度で起こったため、再現性が低く、原因の特定が困難です。最初のうちは気づいたときに手で再起動していたのですが、やがて動いているのかどうかを疑わないといけなくなってしまい、精神衛生上よくありませんでした。
このままでは運用に差し支えるため、２つのデバイスのそれぞれが異常を検知したら再起動するという力技を取りました。アプローチとしては一定間隔で無害なHTTPリクエストを送信するようにし、マイク側のデバイスはリクエストに失敗したら再起動を、スピーカー側のデバイスは一定時間以上通信が途絶えたら再起動するようにしています。
力技で綺麗な解決策とは行きませんでしたが信頼性が低くて使い物にならないよりはマシです。機械もシステムも動いてなんぼです。ただし仕事で偶発的にバグるのを放置すると後で忘れたときに自分に返ってくるので覚悟しましょう。

## おわりに
ご家庭にあるM5StickCとソフトウェアの力で自宅のドアチャイムが聞こえないという課題を解決しました。M5StickCはWiFiに繋がったり、マイクやスピーカーと行った各種センサーも容易に接続できるので、こういった課題を解決するのに丁度よいモジュールです。今回の課題に限らず、身近な課題を解決するのに丁度よいソリューションだと思うので、これを読んでくれた方も身近な課題の解決に是非試してみて貰えればと思います。