あのボタンを押したらM5Stackに今何してるを表示させる

**[2020/05/26追記 SORACOM UG Tokyo Online # 15 のLT内容に合わせてアップデート]**

COVID-19の影響でリモートワークな人です。

---
今回は、

**あのボタンを押したらM5Stackのディスプレイに今何してるか表示させる**

っていうのをやってみました。

## きっかけ
きっかけはソラコム小熊さんが投稿されてた
[M5stack で作る ON AIR サインで手に入れる平穏な在宅勤務環境](https://blog.soracom.jp/blog/2020/04/02/on-air-sign-on-m5stack/)
です。
ボタン使ってできるんじゃないかなーって思って、誰か(ソラコム某エバンジェリスト)が作るんじゃないかなーとちょっとボールを投げたら、
見事に投げ返されたので、まあ、面白いからやってみぜよと。
まあ、事実としては、居間でテレワークしている人がいるので、会議中に居間行きたくないので、それがわかるようにも使えそうだしってこともありまして。。。
技術力の問題で時間かかってしまいました。。。

## できました。
### 構成
<img width="1046" alt="構成図" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72752/b96a6308-25ba-c789-cb1f-0ce3925d08c8.png">

### 実際動作映像
ボタンと文字の紐付けはこんな感じ。
シングルクリック : **ON  MEETING** （ミーティング中的な）
ダブルクリック : **OFF MEETING** （ミーティング終わったよ）
長押し: **END OF WORK** （仕事終わったよ）
色は適当に選んでいるので、明暗がおかしくなっていてすみません。

- シングルクリック
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">シングルクリック <a href="https://t.co/7o9BHyCBq3">pic.twitter.com/7o9BHyCBq3</a></p>&mdash; Kenichiro Wada (@Keni_W) <a href="https://twitter.com/Keni_W/status/1250586790597025792?ref_src=twsrc%5Etfw">April 16, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

- ダブルクリック(シングルクリックしたあとの状態から)
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ダブルクリック <a href="https://t.co/FoMl4PgS6G">pic.twitter.com/FoMl4PgS6G</a></p>&mdash; Kenichiro Wada (@Keni_W) <a href="https://twitter.com/Keni_W/status/1250586924848316417?ref_src=twsrc%5Etfw">April 16, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

- 長押し(ダブルクリックしたあとの状態から)
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">長押し <a href="https://t.co/lMebp89CKC">pic.twitter.com/lMebp89CKC</a></p>&mdash; Kenichiro Wada (@Keni_W) <a href="https://twitter.com/Keni_W/status/1250587369490640896?ref_src=twsrc%5Etfw">April 16, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## 必要なもの
- [M5Stack Basic 3G 拡張ボード セット](https://soracom.jp/products/kit/3g_module_m5stack_set/) : ¥12800 + Tax
- [特定地域向け IoT SIM (plan-D)](https://soracom.jp/products/sim/plan-d/) : ¥852
- IoTボタン
    - [AWS IoT 1-Click デバイス](https://aws.amazon.com/jp/iot-1-click/devices/)に載っているもの。
        - AWS IoT Enterprise Buttonって販売終了してたんですね。日本国内で、新規で買う場合は、[LTE-M Button powered by AWS](https://soracom.jp/products/gadgets/#728) 一択になるのかな？
- SORACOMアカウント
- AWSアカウント
- Arduinoに対応したIDEを搭載したパソコン

M5Stack Basic 3G 拡張ボード セット、去年のソラコムさんのイベントのクイズで当ててて、マジよかったです。

### 後述しますが、あって便利だったもの
- Wifiルーター
- 特定地域向け IoT SIM (plan-D)もう1枚

## 実装とか
- コードは別途Githubにあげます。
- M5Stackおよび3G 拡張ボードのセットアップは参考文献にあります。

### Lambda
コードのベースは、
[SORAZINE 技術組 Vol.2019F](https://techbookfest.org/event/tbf07/circle/5691885884538880)の「IoTを活用した遠隔操作の概要と実践」の章に記載されていたLambdaのコードです。
Node.jsで書こうかと思ったんですが、ちょっとスピード勝負でってことで、流用しました（言い訳）。
久々のPython。作業用のMacのPythonが2.7で焦ったのは内緒。

**2020/05/26コード変更 SORACOM Funk対応にアップデート**

```python
import json

#　メッセージ
CLICK_TYPE_MESSAGE = {
  'SINGLE': 'ON  MEETING',
  'DOUBLE': 'OFF MEETING',
  'LONG':'END OF WORK'
}
#　LCDのカラー
CLICK_TYPE_COLOR = {
  'SINGLE': '0xF800',
  'DOUBLE': '0x000F',
  'LONG': '0xFD20'
}
# ボタンのクリックのタイプ
CLICK_TYPE_INT = {
  'SINGLE': 1,
  'DOUBLE': 2,
  'LONG':3
}

import boto3
iot = boto3.client('iot-data')

def lambda_handler(event, context):
    print(json.dumps(event, ensure_ascii=False))
    if 'deviceEvent' in event:
        click_type = event['deviceEvent']['buttonClicked']['clickType']  # LTE-M Button powered by AWS
    else:
        click_type = event['clickTypeName'] # LTE-M Button for Enterprise/LTE-M Button Plus
    try:
        message = CLICK_TYPE_MESSAGE[click_type]
        color = CLICK_TYPE_COLOR[click_type]
        status = CLICK_TYPE_INT[click_type]
    except KeyError:
        return
    body = {
        'message': message,
        'color': color,
        'status':status
    }
    payload = json.dumps(body, ensure_ascii=False)
    # AWS IoT CoreのMQTT BrokerへPublish
    iot.publish(topic='iotbutton/status', payload=payload, qos=0)
    response = {
        'statusCode': 202,
        'body': json.dumps(event, ensure_ascii=False)
    }
    return response
```

一応こだわった点は、
別にいっぱい配るとかそういうわけではないですが、
できる限りクラウド側(Lambda)に処理は任せようって思ったので、Payload増えちゃうけど、
メッセージ、表示色はLambda側で定義してます。定義しているだけですけど。
色とかメッセージ変えたければ、Lambda更新して完了にはなるので、変更は容易かと。
Arduino繋いで、IDE立ち上げて、修正して、デプロイして、動作確認。。。よりは良いかなと。

### AWS IoT Core With SORACOM Beam
完全にここを参考してます（それでもうまく繋がらなくて苦戦したけれども）。

[SORACOM Beam を使用して AWS IoT と接続する(コンソール版)](https://dev.soracom.io/jp/docs/aws_iot_guide_console/)


### M5Stack
こちらのコードのベースは、
[M5stack で作る ON AIR サインで手に入れる平穏な在宅勤務環境](https://blog.soracom.jp/blog/2020/04/02/on-air-sign-on-m5stack/)
に記載されていたソースコードです。

```c#

#include <M5Stack.h>

#define TINY_GSM_MODEM_UBLOX
#include <TinyGsmClient.h>

#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_Client.h"
#include <ArduinoJson.h>

// 接続先 ここではsoracom beamのエンドポイントを指定してます。
#define MQTT_SERVER "beam.soracom.io"
#define MQTT_SERVERPORT 1883
#define READ_TIMEOUT 5000

// MQTTのトピック
const char TOPIC[] PROGMEM = "iotbutton/status";

TinyGsm modem(Serial2); /* 3G board modem */
TinyGsmClient ctx(modem);
Adafruit_MQTT_Client mqtt(&ctx, MQTT_SERVER, MQTT_SERVERPORT);
Adafruit_MQTT_Subscribe buttonStatusIndicator = Adafruit_MQTT_Subscribe(&mqtt, TOPIC);

void setup() {
  Serial.begin(115200);
  M5.begin();
  M5.Lcd.clear(BLACK);
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.setTextSize(2);
  M5.Lcd.println(F("M5Stack + 3G Module"));

  M5.Lcd.print(F("modem.restart()"));
  Serial2.begin(115200, SERIAL_8N1, 16, 17);
  modem.restart();
  M5.Lcd.println(F("done"));

  M5.Lcd.print(F("getModemInfo:"));
  String modemInfo = modem.getModemInfo();
  M5.Lcd.println(modemInfo);

  M5.Lcd.print(F("waitForNetwork()"));
  while (!modem.waitForNetwork()) M5.Lcd.print(".");
  M5.Lcd.println(F("Ok"));
  M5.Lcd.clear(BLACK);
  M5.Lcd.setCursor(0,0);

  M5.Lcd.print(F("gprsConnect(soracom.io)"));
  modem.gprsConnect("soracom.io", "sora", "sora");
  M5.Lcd.println(F("done"));
  M5.Lcd.clear(BLACK);
  M5.Lcd.setCursor(0,0);

  M5.Lcd.print(F("isNetworkConnected()"));
  while (!modem.isNetworkConnected()) M5.Lcd.print(".");
  M5.Lcd.println(F("Ok"));
  M5.Lcd.clear(BLACK);
  M5.Lcd.setCursor(0,0);

  M5.Lcd.print(F("My IP addr: "));
  IPAddress ipaddr = modem.localIP();
  M5.Lcd.println(ipaddr);

  mqtt.subscribe(&buttonStatusIndicator);
  M5.Lcd.clear(BLACK);
  M5.Lcd.setCursor(0,0);

  // スリープからの復帰をAボタンに割り当て
  M5.setWakeupButton(BUTTON_A_PIN);

  M5.Lcd.print(F("Startup complete!"));
  delay(2000);
}

void loop() {
  M5.update();

  if (M5.BtnA.wasPressed()) {
    M5.Lcd.setCursor(0,0);
    M5.Lcd.printf("Good Morning!");
    delay(10000);
    M5.Lcd.clear(BLACK);
    M5.Lcd.setCursor(0,0);
  }

  // MQTT接続
  connectMqtt();

  Adafruit_MQTT_Subscribe *subscription;
  while ((subscription = mqtt.readSubscription(READ_TIMEOUT))) {
    if (subscription == &buttonStatusIndicator) {
      String lastread = String((char*)buttonStatusIndicator.lastread);
      lastread.trim();
      StaticJsonDocument<200> jsonDoc;

      DeserializationError err = deserializeJson(jsonDoc, lastread);
      if (err) {
        Serial.print(F("deserializeJson() failed: "));
        return;
      }
      // JSONデータを割りあて
      const char* message = jsonDoc["message"];
      unsigned int color = strToHex(jsonDoc["color"]);
      unsigned int statusType = jsonDoc["status"];

      // statusTypeってないですが、念の為
      if (statusType == 0) {
        continue;
      }
      if (statusType == 1) {
        // シングルクリックの場合
        M5.Lcd.setBrightness(200);
        showMessage(color, message);
      } else if (statusType == 2) {
        // ダブルクリックの場合
        M5.Lcd.setBrightness(200);
        showMessage(color, message);
      } else if (statusType == 3) {
        // 長押しの場合
        showMessage(color, message);
        delay(30000);
        // 表示消す
        M5.Lcd.clear(BLACK);
        M5.Lcd.setBrightness(0);
        // Deep Sleepモードに移行
        setDeepSleep();
      }
    }
  }
}

// メッセージ表示部
void showMessage(uint16_t bgColor, const char* message) {
  M5.Lcd.fillScreen(bgColor);
  M5.Lcd.setCursor(30, 100);
  M5.Lcd.setTextSize(4);
  M5.Lcd.println(F(message));
}

// 色指定の文字列を16真数に変換
uint16_t strToHex(const char* str) {
  return (uint16_t) strtoull(str, 0, 16);
}

// Deep Sleepモード設定
void setDeepSleep() {
  M5.Power.begin();
  if (!M5.Power.canControl()) {
    //can't control.
    return;
  }
  // 12時間Deep Sleep
  M5.Power.deepSleep(SLEEP_HR(12));
}
// MQTT接続部
void connectMqtt() {
  int8_t ret;
  if (mqtt.connected()) {
    return;
  }

  while ((ret = mqtt.connect()) != 0) {
    M5.Lcd.println(F(mqtt.connectErrorString(ret)));
    M5.Lcd.println(F("Retrying MQTT connection in 5 seconds ..."));
    mqtt.disconnect();
    delay(5000);
  }
}
```

最初Subscriberのところ、完全に理解間違ってて、別のClient([Arduino Client for MQTT](https://github.com/knolleary/pubsubclient))使わなきゃだめかーと思っていたのですが、
よくよく考えてみると、紹介されていた([adafruit/Adafruit_MQTT_Library - GitHub](https://github.com/adafruit/Adafruit_MQTT_Library))を使えば問題ないってことに気づく始末。
・・・が、
SORACOM Beamには繋がるようになったけど、メッセージは取れない。
っていう状況になり、MacにMosquitto入れて、AWS IoT CoreやSORACOM BeamにPub/Subしてみたりして、
証明書再設定して、ようやくメッセージ取得に成功。
取れてしまえば、あとは、文字位置とかの調整でした。

一旦作り終えて、やったーって思っていたのですが、これ、定期的にメッセージがないか通信が発生しているわけで、
使う想定では、長押ししたら業務終了なので、そのあとは、まあ通信しなくてもいいかなと思いました。
電源落としできればよかったんですが、USB給電中は停止できないようだったので、
長押ししたあとは、deepSleep()呼び出してます。時間等の指定が必要だったので、とりあえず12時間は寝てるようにしてます（同じぐらい寝てたい。。。）。
でも、強制的に叩き起こさないとダメなこともあるので、ボタンA(一番左側)を押すと、目覚める（再起動）するにようにしてます。

この記事書いてて思ったのですが、
30秒でDeepSleepするようにしていますが、
1時間ぐらい変わらなかったら、Deep Sleepするようにした方がいいのかな？
って思っているので、別途アップデート予定。
あと、文字長に合わせて、文字サイズ変えられるように(今は4固定)したほうが良さげかな。
あと、起動したら、普通に仕事中って出してもいいのかもとかも。。。

動画の続きを。。。

- DeepSleep時のアクション
DeepSleepしているときは、ボタンを押しても、通信してないので、当然表示は切り替わりません。
ボタンA(一番左側)を押すと、目覚める（再起動）ようにしてます。(動画の最後の方)
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">DeepSleep時のアクション <a href="https://t.co/F0uuNeAlj6">pic.twitter.com/F0uuNeAlj6</a></p>&mdash; Kenichiro Wada (@Keni_W) <a href="https://twitter.com/Keni_W/status/1250587607760695297?ref_src=twsrc%5Etfw">April 16, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

- 再起動後(上の続き)
再起動して、通信可能な状態になると、ちゃんと切り替わります。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">再起動後 <a href="https://t.co/3EYd6dUwip">pic.twitter.com/3EYd6dUwip</a></p>&mdash; Kenichiro Wada (@Keni_W) <a href="https://twitter.com/Keni_W/status/1250587877605400576?ref_src=twsrc%5Etfw">April 16, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

寝てる間は通信しないだろうというのは、あくまで想像だったので、
該当のM5Stackの3GモジュールにセットしてあるAir SIMの通信状況を見てると、
DeepSleep中(2020/4/15 10時ぐらいにDeepSleepしているので、22時ぐらいまで)は
通信してないということわかりました。
<img width="442" alt="通信状況" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72752/19b68a49-964f-3eb3-3511-3cde374209d5.png">

## まとめ
### よかったところ
- 実はボタン使ったリモートワーク改善自体は2つ目で、M5Stackも使ったので、実装、検証含めて楽しかった。
- ほんとIoTって総合格闘技だわと思いました。
    - クラウド側(Lambda)は大幅に手を抜いたけど、
- 上述の通り、SORACOM Beamへはアクセスできているけど、AWS IoT Coreからメッセージ取れなかったことがあって、切り分けとかできたこと。
    - Macから繋がないとダメで、どーするってなった時に、ちょうどWifiルーターを中古で手に入れたので、それにSORACOM Airさして、切り替えた上で、SORACOM Beamのエンドポイントリクエストして、取れるか試せた。
      - Wifiルーターは持ってって損はないって実感。イ◯◯スさんありがとう。
      - 書いてて思ったけど、SORACOM Endorse使えた？
- SORACOM BeamというかSORACOMプラットフォーム側で、証明書情報を保持できるので、クライアント(M5Stackには絶対持たせられる気がしない)にその辺の情報を持たなくていいのは、ほんと楽ですね。
Beam、これがサービス開始当初からあったと思うと、やっぱりすごいな。 
- ついでにSORACOM PONG(正式名称ではないけどw [デバイスからのPING要求にECHO REPLYを返すエンドポイントを用意しました。](https://blog.soracom.jp/blog/2020/02/27/pong-soracom-io/))試せたので、良かった。

### ダメだったところ
- まだまだArduinoの理解不足は否めない（写経中心だったので）
- AWS IoT Coreも同じ、まだまだ理解不足。もっと触ってみます。
- 通勤時間だった時間(約1時間)を使うと言いつつ、1時間以上やってたことはまあまああり。

## で使ってるの？
使ってません。ちゃんちゃんw
会議室とか、Web会議、オンラインセミナーに使えるかもーとか思ってはいますが。。。

## 2020/05/26 更新
- 2020/05/26 SORACOMUG Tokyo Online #15 でこの件でLTしたので、その資料をば。。。

[ボタンを押したらM5Stackに今何してるを表示させる](https://speakerdeck.com/kwada/botanwoya-sitaram5stacknijin-he-siteruwobiao-shi-saseru)

- 発表内で行ったFunk対応を反映。Funk対応した場合の構成図は以下になります。
    - Funkの設定は、公式のGetting Started「[SORACOM Funk を利用して AWS Lambda を呼び出し Slack へ通知する](https://dev.soracom.io/jp/start/funk_aws_lambda/)」または
「[SORACOM Funkを使ってみた。](https://qiita.com/keni_w/items/4b511f89c8dbd259a78d)」(手前味噌)をご覧ください。


<img width="1056" alt="SORACOM Funk対応" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72752/8eb038d9-e7d4-f077-36ad-47e06861fd19.png">

- LambdaとM5Stack(Arduino)のソースコードを以下で公開してます。

[ Kenichiro-Wada/iotbutton-to-m5stack](https://github.com/Kenichiro-Wada/iotbutton-to-m5stack)

## Special Thanks
- ソラコム小熊さん
- ソラコム松下さん
- オルターブース木村さん

## 参考資料
### ベース
- [M5stack で作る ON AIR サインで手に入れる平穏な在宅勤務環境](https://blog.soracom.jp/blog/2020/04/02/on-air-sign-on-m5stack/)
- [SORAZINE 技術組 Vol.2019F](https://techbookfest.org/event/tbf07/circle/5691885884538880)
 
### AWS IoT Core関係
- [SORACOM Beam を使用して AWS IoT と接続する(コンソール版)](https://dev.soracom.io/jp/docs/aws_iot_guide_console/)
- [AWS IoT とは](https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/what-is-aws-iot.html)
- [MosquittoのPub/Sub通信におけるQoS毎の通信フロー](https://l-w-i.net/t/mosquitto/flow_001.txt)
    - QoSってなんやねんって調べていた時に見つけたサイト。会社の同僚の方のサイトでしたw

### Arduino関係
- [プロトタイプ向けマイコンモジュール M5Stack と 3G 拡張ボードをセットアップする](https://dev.soracom.io/jp/start/m5stack/)
- [m5stack/m5-docs - GitHub](https://github.com/m5stack/m5-docs/tree/master/docs/ja)
- [API Reference - ArduinoJson](https://arduinojson.org/v6/api/)
    - 特に[Migrating from version 5 to 6](https://arduinojson.org/v6/doc/upgrade/)
- [Convert string as hex to hexadecimal](https://stackoverrun.com/ja/q/12274246)

### MacでPub/Sub
- [MQTTの挙動をMacで確認する](https://minor.hatenablog.com/entry/2015/02/08/221819)
- [MacとRaspberryPiにMosquitto入れてみた](https://qiita.com/segur/items/0536ced1fe741b55713d)

### その他
- [初めての「技術ブログ」書き方のご紹介](https://blog.soracom.jp/blog/2018/04/18/howto-techblog/) 
    - 初めてじゃないけどw、ちょっと見直してみたw