Help us understand the problem. What is going on with this article?

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

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

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


今回は、

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

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

きっかけ

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

できました。

構成

構成図

実際動作映像

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

必要なもの

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

後述しますが、あって便利だったもの

  • Wifiルーター
  • 特定地域向け IoT SIM (plan-D)もう1枚

実装とか

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

Lambda

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

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

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 と接続する(コンソール版)

M5Stack

こちらのコードのベースは、
M5stack で作る ON AIR サインで手に入れる平穏な在宅勤務環境
に記載されていたソースコードです。

#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)使わなきゃだめかーと思っていたのですが、
よくよく考えてみると、紹介されていた(adafruit/Adafruit_MQTT_Library - GitHub)を使えば問題ないってことに気づく始末。
・・・が、
SORACOM Beamには繋がるようになったけど、メッセージは取れない。
っていう状況になり、MacにMosquitto入れて、AWS IoT CoreやSORACOM BeamにPub/Subしてみたりして、
証明書再設定して、ようやくメッセージ取得に成功。
取れてしまえば、あとは、文字位置とかの調整でした。

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

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

動画の続きを。。。

  • DeepSleep時のアクション
    DeepSleepしているときは、ボタンを押しても、通信してないので、当然表示は切り替わりません。
    ボタンA(一番左側)を押すと、目覚める(再起動)ようにしてます。(動画の最後の方)

  • 再起動後(上の続き)
    再起動して、通信可能な状態になると、ちゃんと切り替わります。

寝てる間は通信しないだろうというのは、あくまで想像だったので、
該当のM5Stackの3GモジュールにセットしてあるAir SIMの通信状況を見てると、
DeepSleep中(2020/4/15 10時ぐらいにDeepSleepしているので、22時ぐらいまで)は
通信してないということわかりました。
通信状況

まとめ

よかったところ

  • 実はボタン使ったリモートワーク改善自体は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を返すエンドポイントを用意しました。)試せたので、良かった。

ダメだったところ

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

で使ってるの?

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

2020/05/26 更新

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

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

SORACOM Funk対応

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

Kenichiro-Wada/iotbutton-to-m5stack

Special Thanks

  • ソラコム小熊さん
  • ソラコム松下さん
  • オルターブース木村さん

参考資料

ベース

AWS IoT Core関係

Arduino関係

MacでPub/Sub

その他

keni_w
ガンダムと銀河英雄伝説を養分に生きるアラフォーエンジニア。 なお、こちらへの掲載内容は私自身の見解であり、会社の立場、戦略、意見を代表するものではありません。
http://www.a-mon-seul-desir.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした