2000回以上も押された IoT ボタン「"心が今、変わった" ボタン」 その裏側

SORACOM アドベントカレンダー、アンカー担当の松下(ニックネーム: Max)です。
毎年アンカーを担当しており光栄に思っておりますが、そろそろ引き継いでもいいんじゃないかなー？とも思っていますが、あと５回くらいはアンカーをやる気もしています。

今年は [AWS IoT 1-Click が６年の歴史に幕を閉じる](https://aws.amazon.com/jp/iot-1-click/faq/)など、IoT ボタン業界に少なからず動きがありました。

その１つに、"心が今、変わった" ボタンというものがあります。今回は、そのボタンデバイスのお話です。

## 約2200回も押された、 "心が今、変わった" ボタン

まずはこちらの動画をご覧ください。この動画では、ボタンを押すとコインが降り、続いて キャラクターが "KAWATTA(変わった)!" という文字と共に駆け抜けていきます。

![out.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22754/415de992-8bf1-980e-8245-59f2f3739db3.gif)

このボタンとシステムは、2024年7月に開催されたソラコムの IoT カンファレンス「[SORACOM Discovery 2024](https://discovery.soracom.jp/2024/campaign/detail/cp03.html)」（以下、Discovery）で、会場内に設置・展示されたものです。

2024年のテーマ「変える、今ここから」を掲げ、セッションや展示を通じて、多くの「変える・変わる」きっかけを提供することを目指しました。その一環として、このボタンを制作しました。

このボタンの役割は、**会場で見たことや感じたことを通じて、「心を変えることができた、その瞬間」を記録するIoTボタン** としての体験を提供することです。

１日のカンファレンス内で、最終的には約2200回も押していただきました！

## "心が今、変わった" ボタン、デバイス仕様の公開

"心が今、変わった" ボタン（以下、心のボタン）は、６つ作成して会場内に配置しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22754/d0b97df2-31b4-ff90-e731-943f3191f8a7.png)

Google Docs で書いた仕様書をここで公開していきます。

* 仕様書: https://docs.google.com/document/d/1_MrKKW9oKLXOi4OrhdbaUTVbdU1ZTiU6hE6N9p17008/edit?usp=sharing
* ソースコード: https://gist.github.com/ma2shita/a2c8ac168e43c60a07b2c001c83a6ee7

![out.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22754/c14d651a-742b-ea6c-c895-bf5c0f022bc2.gif)

ここからは当時のことを思い出しつつ、実装のポイントをご紹介します。

### (1) 連打に対応したい！ ― マイコンで作成

「IoT ボタン」と聞くと、冒頭でご紹介した IoT 1-Click 対応のボタンや、[ソラコムのIoTボタンシリーズ](https://soracom.jp/store/s_keywords/iot%e3%83%9c%e3%82%bf%e3%83%b3/)を思い浮かべる方もいらっしゃるでしょう（CM: ソラコムのボタンシリーズは今後も取り扱いしています！）。

ソラコムのIoTボタンは、乾電池で動く省電力性を実現するために、ボタン押下時に電源ON → データ送信 → 電源OFF という動作になっており、１サイクルが約8秒かかります。すなわち連打に向いていません。一方で、「すごく心が変わった！」を表現したいは「きっと連打するだろう」と予想しました。

今回は、[Wio LTE JP Edition](https://soracom.jp/store/5301/) という、マイコンと LTE モデムが一体となったデバイスを使い、今回用にプログラムを書きました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22754/a384757d-99b2-2d6b-536e-91e38ed2e9ac.png)

赤い基板のデバイスが Wio LTE JP Edition です。

### (2) 無人運用にしたい！ ― デバイス自身の自己診断と自己復旧

会場は広く、また、常に動作確認ができる担当者はいません。よって無人運用が前提となります。せっかくボタンを押してもらっても、データ送信できてませんでした、、、では悲しすぎます。そこで自己診断に加えて、自己復旧を盛り込みました。

具体的には、一定時間で LTE 通信接続を確認するハートビートと、送信失敗時の再起動を実装しています。

### (3) ファームウェア開発の手間を最小限にしたい！ ― ファームウェアの単一化と、デバイスの一意化の両立

デバイスの運用上、設置情報との紐づけ等を考慮すると一意化（ユニークなID割り当て）は不可欠です。しかし、ファームウェアをそれ毎に作るのは開発の手間です。
IoT データ通信サービス「SORACOM Air for セルラー」には、[通信回線に紐づいたカスタム情報（メタデータ）をユーザーが付与できるサービス](https://users.soracom.io/ja-jp/docs/air/use-metadata/)があります。これを使い、IoT ボタン起動時に、ユニークなIDやデバイスの設定情報をダウンロードして自己構成するようにしています。

SIM が異なれば、おなじメタデータ取得用 URL でも、個別のカスタムデータが取得できるので、ファームウェアはデバイスすべてに同じものを適用できました。

## ファームウェア（デバイス側プログラム）構成

デバイス側では、FreeRTOS で実装しました。
最大の理由は、センサー（接点ボタン）の読み取りと、LTE 通信によるデータ送信という２つの処理を非同期化するためです。

全体の構成は、仕様書にもある通り以下の通りです。併せて[コード](https://gist.github.com/ma2shita/a2c8ac168e43c60a07b2c001c83a6ee7)もご覧ください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22754/a2949ae6-e9b8-3c3e-7862-e6bfc0f0ad27.png)

まずは、デバイス起動時の初期処理 `setup()` です。

1. SORACOM Air のメタデータから自分用の構成情報を読み込む
2. ４つの非同期処理（タスク）の起動と、タスク間の情報をやり取りするキューを作成する

それぞれの非同期タスクは以下の通りです。

* キューへデータを送信するタスク
    * `readButtonTask()`: 接点ボタンの押下回数を記録、キューへ回数を送信
    * `heartbeatTask()`: 一定時間毎にキューへハートビート情報を送信
* キューへのデータ着信をトリガーに処理を開始するタスク
    * `senddataTask()`: キューからのデータを `uni.soracom.io` へ送信
* 装飾目的のタスク
    * `displayProcessingTask()`: データ送信を行っている様子を表現する

全体構成は以上です。ここからは実装の歴史を振り返っていきます。

## 実装の歴史

**連打への対応**:
この企画が持ち上がった際、前節でも紹介した「IoT ボタン」を使おうと思ったのです。ですが、「ボタンがあったら連打するよね、それもぜひカウントしたい」という意見が出ました。たしかにそう思うよ、現場にボタンが合ったら、連打したいですよね？！ということで、マイコンでの実装を考えたのです。

**連打と送信の様子:**
連打している様子も表現したくなり、以下のような実装になりました。全点灯＝データ送信という振る舞いです（ここが `displayProcessingTask()` の役割)。最初はパッシブブザーやMP3再生を考えましたが、会場のざわめきにかき消されそうということで見送り。OLEDディスプレイは良さそうですが、単価が高くて(1,150円くらい)で諦めました。最終的にはLEDバーとなったわけです。

![out.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22754/a36cf810-db41-5674-c55c-a8acdebac758.gif)

**大きいのは大正義、押しやすいボタン**:
こうなると、ボタンは大きいのをつけたくなります。押しやすいですしおすし。モノタロウで大きなボタンを買いました。工業製品はいいですね、頑丈で安心感があります。

**半田付けはしたくない**:
会場に複数個置くことが決まったときに考えたのは「組み立てだけで済ませたい…」でした。幸いなことに Wio LTE JP Edition は、コネクタだけでセンサー接続ができるGroveコネクタを持っており、接点デバイスがつけられることに小躍りしたものです。マイコンの固定には貼り付け型のネジ穴をつかっています。これらを駆使して、組み立て作業はみんなで行えました。助かったー！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22754/c4aba4e6-8828-5b24-9bd0-95b6aef6a613.png)

当時はこんな雰囲気でした。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">オフィスで3⃣人集まって、<br>なにかを作っていたので📸<br><br>来週開催のイベントで設置する何かのボタンらしい🤔<br>そんな何かのボタン⁉️をポチッと押せる🔘🫷<br>国内最大級の <a href="https://twitter.com/hashtag/IoT?src=hash&amp;ref_src=twsrc%5Etfw">#IoT</a> カンファレンス<br>『<a href="https://twitter.com/hashtag/SORACOM?src=hash&amp;ref_src=twsrc%5Etfw">#SORACOM</a> Discovery 2024』<br>2024年7月17日に無料開催です🚀<br><br>イベント詳細&amp;無料申込⬇<a href="https://t.co/gx3r76JFfN">https://t.co/gx3r76JFfN</a> <a href="https://t.co/iHh6Sc6bFy">pic.twitter.com/iHh6Sc6bFy</a></p>&mdash; ソラコム公式(SORACOM) (@SORACOM_PR) <a href="https://twitter.com/SORACOM_PR/status/1810621255189692486?ref_src=twsrc%5Etfw">July 9, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

**送信データの変遷**:
最初は
* `{"pressCount": 5}`

とシンプルなデータフォーマットでした。しかし、ハートビートのデータも混在するようになり、それを無視するためのフラグが必要だったり、また、ボタン個々のIDが必要になったりという事で、バージョン2は

* `{"payloadType": 1, "ebuttonIid", 2, "pressCount": 5}`

となりました。
その後、ハートビートによる再起動を後からでも把握できるようにアップタイム（起動時からの積算時間）、あとはデバイスに適用されているファームウェアバージョンをクラウド側で知るために、バージョン3として

* `{"payloadType": 1, "ebuttonIid": 3, "firmwareVersion": 5, "uptimeS": 21, "pressCount": 5}`

となりました。
このフォーマットをクラウド側の開発と齟齬なく共有するのに「[JSON Schema](https://json-schema.org/)」を使って共有・検証するという事も行っています（オンラインでは https://json-schema.hyperjump.io で行えますね）。

**JSON実装が不要＆データサイズ減を両立「バイナリパーサー」:**
クラウド側への送信は最終的にはバイナリで行っています。
最終的に...というのは、じつは最初はJSON文字列を組み立てて送っていたからです。先にご紹介した通り、途中からデータフォーマットが追加されたりして、私のコーディング力だとバッファオーバー等のバグ盛り込みそうだったので、「何バイト目が何のデータ」というプリミティブな実装で済むバイナリ化をしました。バイナリならデータサイズも減らせるので、通信料にも有利ですし。
マイコンでJSONを組み立てるのには[ArduinoJson](https://arduinojson.org/)を使えば可能ですし、メタデータの読み込みの部分で使っています。ただ、やっぱりバッファサイズとか意識するコーディングが苦手なんですよ、スミマセン。
クラウド側では、先の通りJSONを期待しているので、その変換を[バイナリパーサー](https://users.soracom.io/ja-jp/docs/binary-parser/)で行っているというわけです。

**デバイス依存データを外だし**:
メタデータの実装は、[デバイスや環境に依存する設定情報を SORACOM から取得する](https://users.soracom.io/ja-jp/guides/design-patterns/device-dependency-injection/)で詳解しています。ともかくファームウェアが１つで済んだのは、いろんな管理ごとから解放されて楽でした。

**電源どうしよう？**:
IoT デバイスの悩みは電源に行きつきます。今回は１日の日中限り動作してくれればOKだったため、モバイルバッテリーで済ませてしまいました。しかもレンタル。しっかり計測しておくことでメリハリがついた仕組みにできたというのは学びです。

## おわりと来年

クラウド側が書ききれなかったことが心残りです。AWS Amplifyを使ったという事だけ記しておきます。またの機会に紹介しますので！

いつか公開しようと思い半年経ってしまったけれど、公開できてよかった！このプロジェクトを支えてくれたソラコムの面々にも大感謝です (-人-)

また来年は、このデバイスのパワーアップ版でもいいし、何か別の事でもやろうかなーと思います。
そしてSORACOM 活用したデバイス作りのヒントとして、この記事を役立てて欲しいと願っております！

それでは良いお年を！ (^^)/

[EoT]