SORACOM使ってStay at Home! 〜チャイムを指定時間に鳴らそう

このブログは株式会社ソラコムの「Try! SORACOM キャンペーン」参加ブログです。
このツイートが結論です。
https://twitter.com/1st_ship/status/1261562960935174146

# はじめに

自粛の要請や緊急事態宣言により、普段は出社して仕事をしていた人も毎日、もしくは週の何日かは在宅で仕事することが出てきているのではないでしょうか。かくいう私も、週の２日程度は在宅で業務となっていました。在宅ワークとなると、社内での業務とは違う課題が色々上がってきます。

私のチームでは、「休憩するタイミングがなんかわかんない」という課題が何人かから上げられました。在宅ワークだとまわりに人がいないので、「いくらでも休憩できちゃう」し、逆に「いくらでも働けちゃう」んですね。

会社の場合そのタイミングを作っていたのは「チャイム」でした。休憩時間、開始時間になるとチャイムがなります。これにより業務のリズムが作られていました。

まあ「在宅ワークで会社の時間に縛られることないだろ」とか「自分の休憩時間は自分で適切に決めた方がいい」といった意見はごもっともですし僕もどちらかというとそっち側です。ただ自分もしばらくやってて、何か無いと休憩に入るタイミングを見失う、ということがありましたので、会社のチャイムを自宅で鳴らしてみようと思いました。

一番簡単なのはスマホにチャイムを録音したものを入れ、スマホのアラーム機能で指定時間にチャイムを鳴らすことです。ただこれ準備結構面倒だし、うっかりしてると休日にも鳴っちゃうんですね。

そこで、[SORACOM Inventory](https://soracom.jp/services/inventory/)と[Wio LTE](https://soracom.jp/products/module/wio_lte/)を使って、「外部から指定時間にチャイムを鳴らすデバイス」を作ってみましたのでご紹介します。

# 実現方法

チャイムを鳴らすには何らかの音を鳴らす装置が必要です。Wio LTEのボードを見るとオーディオジャックがついており、これでスピーカーにつなげれば良いのでは？と思ったのですが、これはLTEモデムにつながっていてVoLTE用なんですね。Seeedの方に教えていただきました。

https://twitter.com/matsujirushi12/status/1261087101283561476

なのでGrove端子でつながる外付けのスピーカーあるかな？と思って調べてたところ、こんなのがありました。

https://www.seeedstudio.com/Grove-Recorder-v3-0.html

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542185/732224b1-6516-bad0-a9d4-e21cbb7b157c.png)

最大83秒の音を録音して、再生することができるレコーダーです。再生するのはボタンを押すか、外部ピンを下げれば良いだけのよう。これならプログラムも簡単にできそうですね。あと出力系は5V駆動を要求されることがまあまああるのですが、こちらは3.3V / 5Vの両系統で使用可能とのこと。ハードウェアはこれで決まりです。

外部からマイコンに対して命令を送るのは通常は結構大変なのですが、ソラコムにはSORACOM Inventoryというデバイス管理サービスがあり、これを使うと簡単に命令を送ることができます。マイコン用のSORACOM Inventoryエージェントは前回のブログで紹介した、簡単マイコン遠隔操作プログラム「[easi](https://github.com/1stship/easi)」が使えます。

まずスピーカーから音を鳴らすのに使えそうなLwM2Mのオブジェクトを探します。

https://www.openmobilealliance.org/wp/omna/lwm2m/lwm2mregistry.html

を見ると、オブジェクトID:3339に「Audio Clip」というオブジェクトがあります。良い感じですね。（実際にやることはデジタルピンのLOW → HIGHなのでオブジェクトID:3201「Digital Output」でも問題ないんですが、なんとなく用途にあったものを使いたいということで）

https://www.openmobilealliance.org/tech/profiles/lwm2m/3339.xml

このオブジェクトの中を見ると、リソースID:5523に「Trigger」というExecuteオペレーションがあります。これを再生ボタンとして使えば良さそうです。

SORACOM InventoryはSORACOMのWeb APIやCLIから呼び出すことができます。今回は定時に鳴らしたいので、AWSのCloudWatch EventsのスケジュールでAWS Lambdaの関数を呼び出し、LambdaからSORACOMのWebAPIを呼び出せば良いでしょう。

構成としてはこうなります。(構成はVS CodeのDraw.io拡張で書きました。便利！)

![flow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542185/8f80b832-6385-7c1d-14bf-3aee9bf37bc5.png)

では実装してみましょう！

# 実装

レコーダーには事前に会社のチャイムを録音しておきました。

マイコンの方はeasiを[ダウンロード](https://github.com/1stship/easi/releases)して、ちょっと修正して使います。オブジェクトID:3339、インスタンスID:0のオブジェクトを追加し、オブジェクトID:3339、インスタンスID:0、リソースID:5523に対してExecuteされたら、D38ピンが 100ms の間LOWになるハンドラを書けば良いので、こんな感じです。(長くなるのでコメントや他のオブジェクトは削除してます)

```c
#include "easi.h"
#include <WioLTEforArduino.h>

Lwm2m lwm2m;
WioLTE wio;

void setup() {
  delay(200);
  wio.Init();
  wio.PowerSupplyLTE(true);
  logText("POWER ON LTE");
  delay(500);
  if (!wio.TurnOnOrReset()) {
    logText("Turn on error");
    return;
  }

  if (!wio.Activate("soracom.io", "sora", "sora")) {
    logText("Connect error");
    return;
  }
  
  delay(1000);
  lwm2mInit(&lwm2m, "wiolte");
  udpInit(&lwm2m.bootstrapUdp, "bootstrap.soracom.io", 5683);

  pinMode(WIOLTE_D38, OUTPUT);
  digitalWrite(WIOLTE_D38, HIGH);
  addInstance(3339, 0);
  setExecuteResourceOperation(3339, 0, 5523, &chime);

  while (!lwm2mBootstrap(&lwm2m)){ }
}

void loop() {
  if (!lwm2mCheckEvent(&lwm2m)){
    delay(100);
  }
}

void chime(Lwm2mTLV *tlv){
  digitalWrite(WIOLTE_D38, LOW);
  delay(100);
  digitalWrite(WIOLTE_D38, HIGH);
}
```

簡単ですね。コンパイルして、無事書き込みが終わりました。D38にレコーダーをつなげて、電源入れておけば待ち受け状態になります。

次はLambdaのコードです。個人的な好みでRubyで書きます。（いつの間にかLambdaのRubyが2.7になってる！）まあ単にHTTPSリクエストするだけなので、どんな言語でも書けるでしょう。

```ruby
require 'json'
require 'net/http'
require 'uri'

def lambda_handler(event:, context:)
  auth = get_auth
  execute_chime(auth)
  { statusCode: 200, body: JSON.generate('Chime Executed') }
end

def get_auth
  uri = URI.parse("https://api.soracom.io/v1/auth")
  request = Net::HTTP::Post.new(uri)
  request.content_type = "application/json"
  request["Accept"] = "application/json"
  request.body = JSON.generate({
    "authKey" => ENV["AUTH_KEY"],
    "authKeyId" => ENV["AUTH_KEY_ID"]
  })
  req_options = { use_ssl: true }
  response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
    http.request(request)
  end
  
  JSON.parse(response.body)
end

def execute_chime(auth)
  uri = URI.parse("https://api.soracom.io/v1/devices/#{ENV["DEVICE_ID"]}/3339/0/5523/execute")
  request = Net::HTTP::Post.new(uri)
  request["Accept"] = "application/json"
  request["X-Soracom-Api-Key"] = auth["apiKey"]
  request["X-Soracom-Token"] = auth["token"]
  req_options = { use_ssl: true }
  response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
    http.request(request)
  end
end
```

事前に発行しておいた[SAMユーザー](https://dev.soracom.io/jp/start/sam/)の認証情報をAUTH_KEY_IDとAUTH_KEY環境変数に設定します。Wio LTEが接続されるとDEVICE_IDはコンソールに出ていますので、それをDEVICE_ID環境変数に設定します。

これでLambda側の準備は完了です。

最後にCloudWatchEventsを設定しましょう。スケジュールがCron式のイベントを、CloudWatch Eventsに設定します。時間はUTCで指定するので、朝10時に鳴らす場合は1時にします。土日には鳴らないように週は2-6に設定して、これを必要な時間の数だけ設定します。(これ結構面倒なので、1個のイベントで複数のCron式設定できるようにならないかな。。)

![Chime.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/542185/5f27b9f4-46cd-1d55-d44c-2e24cc83b164.png)

あとは時間になれば鳴るはず。

# 実行

時間になるとチャイムが鳴りました！
といっても画面では音が伝わりにくいのでTwitterに動画を投稿しておきました。こちらをご覧ください。
https://twitter.com/1st_ship/status/1261562960935174146

# おわりに

外部からの命令で音を鳴らす、というのは今回はチャイムに使いましたが、なんらかの異常が発生した時のアラートなんかにも使えそうですね。

またその後MP3が流せるモジュールもある
https://www.seeedstudio.com/Grove-MP3-V3-p-4297.html
という情報もいただき、こちらも入手しましたので、これが使えるようになれば録音した一種類の音だけではなく、状況に応じて様々な音を鳴らすこともできそう。

皆さんも在宅勤務のお供に[Wio LTE](https://soracom.jp/products/module/wio_lte/)と[easi](https://github.com/1stship/easi)はいかがでしょうか？結構色々できると思いますので、ぜひ試してみてください！