AWS IoTの使い方③ Arduinoコマンドで簡単MQTT通信 遠隔操作 監視の方法

ArduinoコマンドMQTT通信で簡単AWS IoT

前回は「AWS(アマゾンウェブサービス)」の「AWS IoT」でビジュアルプログラミングの「UiFlow」を使用したクラウド経由の遠隔操作、データ監視をする方法を紹介しました。

今回はより自由度の高い「Aruduinoコマンド」を使用した「C言語」によるMQTT通信でのクラウド経由の遠隔操作、データ監視をする方法を詳しく紹介します。
動作については前回「UiFlow」で作成したものとほぼ同じ動作です。


「ビジュアルプログラミング」の「UiFlow」を使用して、より簡単にMQTT通信による遠隔操作、データ監視を体験できる方法は以下のリンクで詳しく紹介しています。

AWS IoTの使い方② M5Stack UiFlowで簡単MQTT通信 遠隔操作 監視の方法
M5Stack社のCORE2とM5StickC PlusにENVⅢを接続してAWS IoTを使用したクラウド経由の遠隔操作、データ監視を行う方法を詳しく紹介します。
AWSを使用するには無償期間がありますがアカウントの作成が必要です。
無償期間が終わっても通信のみであれば非常に安価(メッセージ100万件あたり1.2ドル)ですが、データのモニターや解析等のサービスを使用するにはそれなりに費用が発生します。また、無償で使用できる範囲を超えた場合には、費用が発生することをご理解の上、自己責任で実施をお願いいたします。

AWSの利用料金については以下のリンクにてご確認ください。

AWS の料金 - クラウドによるコスト削減 | AWS公式
AWS のご利用料金の仕組みとクラウドを活用したコスト削減方法をはじめ、料金やコスト削減額を簡単に計算できる各種ツールについてご紹介しています。 AWS の料金モデルは。水道や電気などの公共料金のようにサービスを消費した分だけ支払う従量課金制です。利用を停止したときの追加コストや解約料金は発生しません。

私は好奇心だけで無知なまま始めたので、使い方を間違って¥5,000程捨ててしまいましたw
これについては失敗談として以下のリンクで詳しく紹介しています。
こうならないためには「AWS」の「請求状況の確認」と「意図しない課金が発生した場合の停止方法」を知っておくことが大切なため、この方法も以下のリンクで紹介しています。

気軽にAWSでIoTやったら無知で痛い目見た話。課金の止め方。
クラウドでIoTやりたい!好奇心だけでAWS(アマゾンウェブサービス)の無料枠で手軽にできると思ってやったら無知すぎて無駄な課金で¥5,000以上捨ててしまった話。
スポンサーリンク

1.今回実現したいこと

今回はインターネットに接続可能な2個のデバイス(M5Stack社製)を使用して「Arduinoコマンド」でクラウド経由の遠隔操作とデータ監視を行います。
以下から動作や全体の構成、使用するものやプログラミング環境について詳しく紹介します。

・動作紹介、全体の構成

遠隔操作の例としては、親機側(CORE2)から子機側(M5StickC Plus)のLEDを操作します。
子機側からの信号を親機側が受けた時には画面の色を一瞬変化させます。

データ監視の例としては、子機側で測定した温湿度、気圧のデータを親機側からの操作で取得して親機に表示させます。子機側からの操作でも測定データを送信して親機に表示させます。

通信にはMQTTを使用し、全体の構成は下画像のようになります。

AWS MQTT接続でIoT 簡単遠隔操作、監視

・使うもの

使うものとしては下画像のように「M5Stack社」製の2つのデバイス(モノ)とセンサーを使用します。

ArduinoコマンドでAWS IoT 動作テスト

親機としては「CORE2(画像左側)」を使用し、子機には「M5StickC Plus」に環境センサ「ENVⅢ」を接続(画像右側)して温湿度と気圧を測定し表示します。


「CORE2」「M5StickC Plus」「ENVⅢ」については以下のリンクで詳しく紹介しています。

M5Stack CORE2について、デモ画面、基本仕様、端子配列(機能)等を詳しく紹介
CORE2について工場出荷時デモ画面の動作確認方法や外観紹介から基本仕様、端子配列(一覧表にまとめました)、端子機能等を詳しく紹介します。
M5StickC Plusの使い方、初期設定、サンプルプログラム、M5StickCとの違い等を詳しく紹介
M5StickC PlusをArduino IDEやPlatformIOで使うための初期設定やサンプルプログラムでの動作確認の方法です。ビジュアルプログラミングのUiFlowの初期設定についても紹介します。
I2C通信の使い方をサンプルプログラムで詳しく紹介(Arduinoコマンド)
温度と湿度の測定できるセンサ(ENV Ⅲ)からライブラリ使用せずにデータを取得する方法を例にI2C通信の使い方を紹介します。データはM5StickC Plusの液晶表示器に表示します。

使用するものは以下になりますが「M5Stack社」のデバイスなら何でも良いので、慣れた方なら手持ちのデバイスに置き換えて動作確認してみると、より理解が深まると思います。


・Arduinoとは

「Arduino IDE」とはArduino」の開発環境ですが「M5Stackシリーズ」でも使えて無償でダウンロードすることができます。

複雑で専門的な知識を必要とするプログラムが「ライブラリ」としてまとめられており、直感的に理解しやすいコマンドで「ライブラリ」を使用することで、初心者でも簡単に複雑な動作を実現することができます。

プログラミング言語は「C言語」をベースに、マイコンボードを制御するための「Arduino」独自のコマンドを使用して、マイコンボードを動かしながらプログラミングを進めることで「C言語」も自然に身につくため「C言語」の学習にも最適です。

「Arduino IDE」のインストール方法、使い方は以下のリンクで詳しく紹介しています。

M5StackシリーズのためのArduino IDEのインストール方法と初期設定、使い方紹介
ArduinoIDEバージョン2のインストール方法から初期設定、スケッチ例の書き込み、コピペでの使い方まで詳しく紹介します。インストールはArduinoでも同じです。
スポンサーリンク

2.AWS(Amazon Web Services)とは

「AWS」とは「Amazon Web Services」の略で「アマゾン」がインターネット経由で200以上のサービスを提供している「クラウドコンピューティングサービス」のことです。

「AWS」ではクラウドサーバーを使用して情報処理から大容量データ記録、データベースによるデータ管理から機械学習、AIによるデータ分析等、用途に応じて最適なツールを選択して使用することができます。

料金については、各サービスの中から必要なものを使用した分だけ支払えば良いため、長期の契約や複雑なライセンスは必要ありません。

今回使用するIoTの機能「AWS IoT Core」にも様々な機能があり、何十億ものデバイスを安全に接続、管理でき、様々な業種向けにデータ収集、保存、分析するツールも提供されています。

詳細は以下リンクの「AWS」の公式ページでご確認ください。

アマゾン ウェブ サービス(AWS クラウド)- ホーム
Amazon Web Services は、信頼性と拡張性に優れたクラウドコンピューティングサービスを低料金で提供します。アカウント作成は無料。料金はご利用分だけです。
スポンサーリンク

3.通信方式 MQTTとは

今回クラウド経由のデータ通信を行う方法としてMQTT (Message Queuing Telemetry Transport)を使用します。MQTTとは、データパケットの小さい軽量な通信手段です。

HTTP通信では、送信側のリクエストと、受信側のレスポンスデータそれぞれに詳細な情報を含むヘッダーを使用しますが、MQTTのヘッダーは通信に必要な最小限の情報のみです。

MQTTは多くの情報を、高速で、少ない電力で通信することができるため、バッテリ等で動作する携帯デバイス、特にIoTによる遠隔操作、データ監視に最適な通信方法です。

通信方法は「パブリッシュ(送信)サブスクライブ(受信)」パターンを使用して、デバイス間でメッセージを送受信します。これにはブローカーという中継サーバーを使用します。

データの送信先は「トピック」として任意の名前を設定して「トピック」宛に送受信を行います。

  • パブリッシュ(Publish)とは
    メッセージを送信するデバイスが、送信先としてトピック(Topic)を指定して、ブローカーに対してメッセージを発行(送信)することです。
  • サブスクライブ(Subscribe)とは
    メッセージを受信するデバイスが、受信先としてトピック(Topic)を指定して、ブローカーに対してメッセージを購読(受信)することです。
  • ブローカー(Broker)とは
    ブローカーは「パブリッシュ」されたメッセージを「サブスクライブ」しているデバイスに対して、メッセージを配信します。
    トピックに対してのアクセス制御や認証などのセキュリティ機能も提供することができます。

MQTTではシステムやネットワークが、障害や故障などの予期しない状況に対して、影響を最小限に抑えるための複数のバックアップや代替手段を持っています。

例えば、ブローカーAとブローカーBがあって、ブローカーAが故障した場合、ブローカーBが自動的にブローカーAの役割を引き継ぎ、通信を継続することができるため、確実にデータを送信することができます。

4.サンプルプログラミングで動作確認

「コピペ」で動作確認できるように親機(CORE2)と子機(M5StickC-Plus)のサンプルプログラムを準備しました。

それぞれWi-Fi接続や液晶表示のプログラムも書かれていて長いため、先に「AWS IoT」で「MQTT」通信をするための初期設定から送受信方法を抜粋して書いています。

・ArduinoコマンドでMQTT通信する方法

「AWS IoT」で「MQTT」通信するための初期設定から送受信方法は以下のようになります。

AWS IoTの初期設定

「AWS IoT」の初期設定のみを抜粋すると以下のようになります。
※別途Wi-Fi接続の設定が必要ですが、ここでは省略しています。この後の全体のサンプルプログラムで確認してください。

#include <PubSubClient.h>  // ① MQTT通信用ライブラリ

// ② AWS IoT 接続情報
#define DEVICE_NAME "core2_01"              // AWS IoT 登録デバイス名
#define AWS_IOT_ENDPOINT "xxxxxxxxxx.amazonaws.com" // AWSエンドポイント(接続先アドレス)
#define AWS_IOT_PORT 8883                   // AWS接続ポート番号(固定)
#define QOS 0                               // 通信品質設定(0:送信完了の確認をしない,高速)

// ③ 外部ファイル(certs.cpp)から認証情報を取得(この後詳しく紹介しています。)
extern const char *root_ca;       // ルート証明書
extern const char *certificate;   // クライアント証明書
extern const char *private_key;   // プライベートキー

// ④ インスタンス
WiFiClientSecure https_client;          // https通信用
PubSubClient mqtt_client(https_client); // MQTT通信用

// ⑤ AWS IoT初期設定関数 ******************************************
void connect_aws(){
  https_client.setCACert(root_ca);                        // ルート証明書を設定
  https_client.setCertificate(certificate);               // クライアント証明書を設定
  https_client.setPrivateKey(private_key);                // プライベートキーを設定
  mqtt_client.setServer(AWS_IOT_ENDPOINT, AWS_IOT_PORT);  // エンドポイントとポート番号設定
  mqtt_client.setCallback(subscribe);                     // サブスクライブ(受信)した時に呼び出す関数設定
  // ⑥ MQTT接続開始
  while (!mqtt_client.connected())  {                     // MQTT接続完了までループ
    delay(1000);
    if (mqtt_client.connect(DEVICE_NAME)) {               // DEVICE_NAMEに接続完了なら
      mqtt_client.subscribe("topic1", QOS);               // サブスクライブ(受信)するトピックを設定
      // mqtt_client.subscribe("topic2", QOS);            // トピックが複数ある場合は続けて設定
      delay(1000);                                        // 接続完了表示時間
    } else {                                              // 接続失敗なら
      // エラー処理
    }
  }
}

「AWS IoT」の初期設定は以下の手順となります。

①ライブラリの準備
 MQTT通信に必要なライブラリとして「PubSubClient」をインクルードします。
②AWS IoT 接続情報の準備
 「AWS IoT」で設定した「デバイス名」や決められた「エンドポイント(接続先アドレス)」等を設定します。
③クライアント証明書、秘密鍵の準備
 SSL通信のための証明情報を外部ファイルから読み込んで設定します。(この後詳しく紹介)
④インスタンスの準備
 https通信とMQTT通信のために必要なインスタンスを準備します。
⑥MQTT接続開始
 登録した情報でAWSへの接続を開始し、MQTT通信の接続を確立します。
 接続完了したら通信先の「トピック」を設定(複数設定可)します。

ここで設定する「AWS IoT」の接続情報の設定や「エンドポイント」の確認方法は以下のリンクで詳しく紹介しています。

AWS IoTの使い方① 初期設定、デバイス登録、MQTT接続テスト方法。
AWS IoTを使用したクラウド経由での遠隔操作とデータ監視の方法を数回に分けて紹介。今回はAWS IoTを使うための準備方法と動作確認方法を詳しく紹介します。

サブスクライブ(受信)方法

MQTT通信でデータをサブスクライブ(受信)するための方法を抜粋したものは以下になります。

送受信するデータはテキスト形式でも送受信できますが、受信したデータを処理するときに便利なJSON形式で送受信することが一般的なため、JSONでの通信例として紹介します。
#include <ArduinoJson.h>      // ① JSONライブラリ

// ② インスタンス
StaticJsonDocument<200> doc;  // JSONファイル変換用

// ③ 変数宣言
String temp = "0";   // 温度データ文字列格納用
String hum = "0";    // 湿度データ文字列格納用

// ④ AWS IoTサブスクライブ(受信)処理関数 ************************************
void subscribe(char* topic, byte* payload, unsigned int length) {
  Serial.println(topic);                  // 受信したtopicを表示
  deserializeJson(doc, payload, length);  // 受信したjsonデータをdoc(StaticJsonDocument)に変換
  String val1 = doc["temp"];              // docのキー"temp"の値を取り出す
  String val2 = doc["hum"];               // docのキー"hum"の値を取り出す
  temp = val1;
  hum = val2;
  Serial.printf("%s, %s\n", temp, hum);
}

// ⑤ メイン *****************************************
void loop() {
  mqtt_client.loop(); // サブスクライブ(受信)するために必要
  delay(200);         // 遅延時間
}

サブスクライブ(受信)したデータの処理方法は以下の手順になります。

①ライブラリの準備
  JSONデータを扱うために必要なライブラリとして「ArduinoJson」をインクルードします。
②インスタンスの準備
 ArduinoJsonを使用してJSONファイルを扱えるようにインスタンスを準備します。
③変数宣言
 受信したデータを文字列として格納するための変数を準備します。
④サブスクライブ(受信)したデータの処理関数作成
 設定したトピックごとに、データが送信されてきた時の処理を関数として作成しておきます。
 ここではJSON形式で受信したデータの「key(キー)」ごとに「value(値)」を抽出して文字列に変換してシリアル出力しています。
⑤メイン処理内にサブスクライブ(受信)状態で待ち受けるよう設定
 メイン処理内に「mqtt_client.loop();」を記入することで、サブスクライブするたびに④の関数を呼び出せるようになります。

パブリッシュ(送信)方法

MQTT通信でデータをパブリッシュ(送信)するための方法を抜粋したものは以下になります。

// ① AWS IoTパブリッシュ(送信)処理関数
void publish() {
  // ② 送信データを文字列として準備
  dtostrf(s_tmp, 4, 1, temp);     // 温度データを文字列に変換(値, 文字桁数, 小数点桁数, 格納先文字配列)
  dtostrf(s_hum, 4, 1, hum);      // 湿度データを文字列に変換

  // ③ 送信データをJSON形式に変換
  char json_str[100];             // 送信するjsonデータ格納用文字列
  doc.clear();                    // JSONデータをクリア
  doc["temp"] = temp;             // 温度データをjsonデータのキーと値(バリュー)でセット
  doc["hum"] = hum;               // 温度データをjsonデータのキーと値(バリュー)でセット
  serializeJson(doc, json_str);   // doc(StaticJsonDocument)をjsonデータに変換

  // ④ トピックを指定して送信
  mqtt_client.publish("topic1", json_str); // jsonデータをトピック"topic1"に送信
  Serial.println(json_str);       // JSONデータ確認用に表示
}

データをパブリッシュ(送信)する方法は、以下の手順になります。

①パブリッシュ(送信)するデータの処理関数作成
 設定したトピックごとに送信するデータの処理を関数として作成しておきます。
②送信するデータを文字列に変換
 データをJSON形式に変換するためにまず文字列として準備します。
 浮動小数点数の桁数等を指定するにはArduinoの独自関数である「dtostrf」が便利です。
③JSON形式に変換
 送信するデータを「ArduinoJson」ライブラリでkey(キー)」と「value(値)」を設定してJSON形式に変換します。
④トピックを設定して送信
 JSONデータを「トピック」を指定してパブリッシュ(送信)します。

・セキュリティ証明書参照ファイルの準備

MQTTで安全な「SSL通信」を行うための「クライアント証明書」と「秘密鍵」は別ファイルに文字列として準備して読み込ませます。

メインのプログラムに以下のように「extern」をつけて文字列変数を準備することで、別ファイルを参照して証明書等の情報を読み込ませることができます。

// 外部ファイル(certs.cpp)から認証情報を取得
extern const char *root_ca;       // ルート証明書
extern const char *certificate;   // クライアント証明書
extern const char *private_key;   // プライベートキー

外部ファイルとして、メインのプログラムファイルと同じ場所に「certs.cpp」というファイルを作成して以下のように証明情報を文字列で記入します。

証明情報は証明書等の各ファイルをメモ帳等で開いて内容をコピーし、以下の「xxxxxxxx」の部分と置き換えて作成してください。

文字列を「R” ”」の中に記入することで、この中にある改行は無視され、連続した文字列として扱われるようになります。
const char *root_ca = R"(-----BEGIN CERTIFICATE-----
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
-----END CERTIFICATE-----
)";

const char *certificate = R"(-----BEGIN CERTIFICATE-----
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
-----END CERTIFICATE-----
)";

const char *private_key = R"(-----BEGIN RSA PRIVATE KEY-----
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
-----END RSA PRIVATE KEY-----
)";
今回は証明書等の情報をデバイスのメモリに文字列として読み込ませていますが、セキュリティ的には読み取られる可能性があり、あまりよろしくないので動作確認用としてご理解ください。
デバイスの紛失や証明情報の流出の可能性がある場合はAWSで発行した証明情報を削除して新しく発行し直すようにしてください。

・ホスト(親機)側サンプルプログラム

親機として使用する「CORE2」のサンプルプログラムの全ては以下のようになります。
※下コード(黒枠)内の右上角にある小さなアイコンのクリックでコピーできます。

Wi-Fi接続のための「SSID」「パスワード」や「AWS IoT」の「エンドポイント(接続先アドレス)」や「クライアント証明情報等」はお使いの環境に合わせて設定してください。
#include <M5Core2.h>
#include <ArduinoJson.h>      // JSONライブラリ
#include <WiFiClient.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>     // MQTTライブラリ

#define WIFI_SSID "xxxxxxxxxxxx"            // WiFi接続SSID(自分の環境に合わせてください)
#define WIFI_PASSWORD "xxxxxxxxxxxxx"       // WiFi接続パスワード(自分の環境に合わせてください)
#define DEVICE_NAME "core2_01"              // デバイス名
#define AWS_IOT_ENDPOINT "xxxxxxxxxxxx.amazonaws.com" // AWSエンドポイント(接続先アドレス)を設定
#define AWS_IOT_PORT 8883                   // AWS接続ポート番号(固定)
#define QOS 0                               // 通信品質設定(0:送信完了の確認をしない,高速)
// 外部ファイル(certs.cpp)から認証情報を取得
extern const char *root_ca;       // ルート証明書
extern const char *certificate;   // クライアント証明書
extern const char *private_key;   // プライベートキー
// インスタンス
WiFiClientSecure https_client;          // https通信用
PubSubClient mqtt_client(https_client); // MQTT通信用
StaticJsonDocument<200> doc;            // JSONファイル変換用
// 変数宣言
String temp = "0";   // 温度データ文字列格納用
String hum = "0";    // 湿度データ文字列格納用
String press = "0";  // 気圧データ文字列格納用
// 関数 *****************************************
// 液晶タイトル表示
void lcdTitle(uint32_t color) {
  M5.Lcd.fillScreen(color); // 背景色
  M5.Lcd.setTextFont(4);    // フォント 1(8px), 2(16px), 4(26px)
                            // 6(36px数字-.:apm), 7(7セグ-.:), 8(75px数字-.:)
  M5.Lcd.drawFastHLine(0, 0, 320, WHITE);           // 指定座標から平行線
  M5.Lcd.fillRect(0, 1, 320, 27, (uint16_t)0x098a); // タイトルエリア背景
  M5.Lcd.drawFastHLine(0, 29, 320, WHITE);          // 指定座標から平行線
  M5.Lcd.setTextColor(WHITE);                       // 文字色
  M5.Lcd.drawCentreString("AWS IoT MQTT HOST", 160, 4, 4);  // 指定座標を中心に文字表示(x, y, font)
}
// WiFi接続
void connect_wifi() {
  M5.Lcd.setTextFont(4);                  // フォント設定
  M5.Lcd.println("WiFi Connecting to ");
  M5.Lcd.println(WIFI_SSID);              // SSID表示
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);   // Wi-Fi接続開始
  while (WiFi.status() != WL_CONNECTED) { // Wi-Fi接続完了するまでループ
    M5.Lcd.print(".");
    delay(2000);
  }
  M5.Lcd.println("Connected");
  M5.Lcd.printf("IP: %s\n\n", WiFi.localIP().toString().c_str()); // IPアドレス表示
}
// AWS IoTパブリッシュ(送信)関数
void publish() {
  char json_string[100];            // 送信するjsonデータ格納用文字列
  doc.clear();
  doc[""] = "";                     // 空のjsonデータのキーと値(バリュー)
  serializeJson(doc, json_string);  // doc(StaticJsonDocument)をjsonデータに変換
  mqtt_client.publish("device/01/local", json_string); // jsonデータをトピック"device/01/local"に送信
}
// AWS IoTサブスクライブ(受信)した時のJsonを処理して表示
void subscribe(char* topic, byte* payload, unsigned int length) {
  Serial.println(topic);                  // 受信したtopicを表示
  deserializeJson(doc, payload, length);  // 受信したjsonデータをdoc(StaticJsonDocument)に変換
  String val1 = doc["temp"];              // docのキー"temp"の値を取り出す
  String val2 = doc["hum"];               // docのキー"hum"の値を取り出す
  String val3 = doc["press"];             // docのキー"press"の値を取り出す
  temp = val1;
  hum = val2;
  press = val3;
  Serial.printf("%s, %s, %s\n", temp, hum, press);
  lcdTitle(DARKCYAN); // 液晶表示タイトル表示関数(背景色渡し)
  delay(500);  
  lcdTitle(BLACK);    // 液晶表示タイトル表示関数(背景色渡し)
}
// AWS IoT初期設定関数
void connect_aws(){
  https_client.setCACert(root_ca);                        // ルート証明書を設定
  https_client.setCertificate(certificate);               // クライアント証明書を設定
  https_client.setPrivateKey(private_key);                // プライベートキーを設定
  mqtt_client.setServer(AWS_IOT_ENDPOINT, AWS_IOT_PORT);  // エンドポイントとポート番号設定
  mqtt_client.setCallback(subscribe);                     // サブスクライブ(受信)した時に呼び出す関数設定
  // MQTT接続開始
  M5.Lcd.println("MQTT Connection");
  while (!mqtt_client.connected())  {                     // MQTT接続完了までループ
    M5.Lcd.print(".");
    delay(1000);
    if (mqtt_client.connect(DEVICE_NAME)) {               // DEVICE_NAMEに接続完了なら
      M5.Lcd.println("Connected!!");
      mqtt_client.subscribe("device/host", QOS);          // サブスクライブ(受信)するトピックを設定
      // mqtt_client.subscribe("topic", QOS);             // トピックが複数ある場合は続けて設定
      delay(1000);                                        // 接続完了表示時間
    } else {                                              // 接続失敗なら
      M5.Lcd.printf("Failed!!, rc=%d\n", mqtt_client.state());
    }
  }
}
// 初期設定 *****************************************
void setup() {
  M5.begin();           // 本体初期化
  Serial.begin(9600);   // シリアル通信開始

  connect_wifi();   // Wi-Fi接続関数呼び出し
  connect_aws();    // AWS IoT初期設定関数呼び出し
  lcdTitle(BLACK);  // 液晶表示タイトル表示関数(背景色渡し)
}
// メイン *****************************************
void loop() {
  M5.update();        // ボタン状態更新
  mqtt_client.loop(); // サブスクライブ(受信)するために必要
  // 本体ボタン処理
  if (M5.BtnA.wasPressed()) {
    Serial.println("BTN_A ON!");  // 標準のシリアル出力
    lcdTitle(DARKCYAN);           // 液晶表示タイトル表示関数(背景色渡し)
    publish();                    // publish処理関数呼び出し
  }
  // 取得データ表示
  M5.Lcd.setTextFont(4);                  // フォント
  M5.Lcd.setCursor(10, 50); M5.Lcd.setTextColor(WHITE, BLACK);  // 座標指定(x, y) // 文字色、背景
  M5.Lcd.printf("Temp : %s C'", temp);    // 温度表示
  M5.Lcd.setCursor(10, 110);              // 座標指定(x, y)
  M5.Lcd.printf("Hum : %s %%", hum);      // 湿度表示
  M5.Lcd.setCursor(10, 170);              // 座標指定(x, y)
  M5.Lcd.printf("Press : %s hPa", press); // 湿度表示
  delay(200); // 遅延時間
}

・ローカル(子機)側サンプルプログラム

子機として使用する「M5StickC Plus」のサンプルプログラムの全ては以下のようになります。
※下コード(黒枠)内の右上角にある小さなアイコンのクリックでコピーできます。

Wi-Fi接続のための「SSID」「パスワード」や「AWS IoT」の「エンドポイント(接続先アドレス)」や「クライアント証明情報等」はお使いの環境に合わせて設定してください。
#include <M5StickCPlus.h>     // ヘッダーファイル
#include <ArduinoJson.h>      // JSONライブラリ
#include <WiFiClient.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>     // MQTTライブラリ
#include "M5_ENV.h"           // ENVⅢ用ライブラリ

// MQTT通信設定
#define WIFI_SSID "xxxxxxxxxxxx"            // WiFi接続SSID(自分の環境に合わせてください)
#define WIFI_PASSWORD "xxxxxxxxxxxxx"       // WiFi接続パスワード(自分の環境に合わせてください)
#define DEVICE_NAME "m5stickc_plus_01"      // デバイス名
#define AWS_IOT_ENDPOINT "xxxxxxxxxxx.amazonaws.com" // AWSエンドポイント(接続先アドレス)を設定
#define AWS_IOT_PORT 8883                   // AWS接続ポート番号(固定)
#define QOS 0                               // 通信品質設定(0:送信完了の確認をしない,高速)
// 外部ファイル(certs.cpp)から認証情報を取得
extern const char *root_ca;       // ルート証明書
extern const char *certificate;   // クライアント証明書
extern const char *private_key;   // プライベートキー
// インスタンス
WiFiClientSecure https_client;          // https通信用
PubSubClient mqtt_client(https_client); // MQTT通信用
StaticJsonDocument<200> doc;            // JSONファイル変換用

// 環境センサENVⅢ設定
SHT3X sht30;          // 温湿度センサ インスタンス化
QMP6988 qmp6988;      // 気圧センサ インスタンス化
float s_tmp   = 0.0;  // 温度測定データ格納用
float s_hum   = 0.0;  // 湿度測定データ格納用
float s_press = 0.0;  // 気圧測定データ格納用
// 変数宣言
char temp[20];   // 温度データ文字列格納用
char hum[20];    // 湿度データ文字列格納用
char press[20];  // 気圧データ文字列格納用
// 関数 *****************************************
// WiFi接続
void connect_wifi() {
  M5.Lcd.setTextFont(2);                  // テキストフォント指定
  M5.Lcd.println("WiFi Connecting to ");
  M5.Lcd.println(WIFI_SSID);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);   // Wi-Fi接続開始
  while (WiFi.status() != WL_CONNECTED) { // Wi-Fi接続完了するまでループ
    M5.Lcd.print(".");
    delay(2000);
  }
  M5.Lcd.println("Connected");
  M5.Lcd.printf("IP: %s\n\n", WiFi.localIP().toString().c_str()); // IPアドレス表示
}
// AWS IoTパブリッシュ(送信)関数
void publish() {
  dtostrf(s_tmp, 4, 1, temp);     // 温度データを文字列に変換(値, 文字桁数, 小数点桁数, 格納先文字配列)
  dtostrf(s_hum, 4, 1, hum);      // 湿度データを文字列に変換
  s_press = s_press / 100;        // PaをhPaに変換
  dtostrf(s_press, 6, 1, press);  // 気圧データを文字列に変換
  // JSONパブリッシュ処理
  char json_str[100];             // 送信するjsonデータ格納用文字列
  doc.clear();                    // JSONデータをクリア
  doc["temp"] = temp;             // 温度データをjsonデータのキーと値(バリュー)でセット
  doc["hum"] = hum;               // 温度データをjsonデータのキーと値(バリュー)でセット
  doc["press"] = press;           // 温度データをjsonデータのキーと値(バリュー)でセット
  serializeJson(doc, json_str);   // doc(StaticJsonDocument)をjsonデータに変換
  mqtt_client.publish("device/host", json_str); // jsonデータをトピック"device/01/local"に送信
  Serial.println(json_str);       // JSONデータを表示
  delay(500);
  digitalWrite(10, HIGH);         // 本体LED赤消灯
}
// AWS IoTサブスクライブ(受信)した時のJsonを処理して表示
void subscribe(char* topic, byte* payload, unsigned int length) {
  digitalWrite(10, LOW);                  // 本体LED赤点灯
  Serial.println(topic);                  // 受信したtopicを表示
  deserializeJson(doc, payload, length);  // 受信したjsonデータをdoc(StaticJsonDocument)に変換
  Serial.printf("%s\n", payload);         // 受信データを表示
  publish();  // publish処理関数呼び出し
}
// AWS IoT初期設定関数
void connect_aws(){
  https_client.setCACert(root_ca);                        // ルート証明書を設定
  https_client.setCertificate(certificate);               // クライアント証明書を設定
  https_client.setPrivateKey(private_key);                // プライベートキーを設定
  mqtt_client.setServer(AWS_IOT_ENDPOINT, AWS_IOT_PORT);  // エンドポイントとポート番号設定
  mqtt_client.setCallback(subscribe);                     // サブスクライブ(受信)した時に呼び出す関数設定

  M5.Lcd.println("MQTT Connection");
  while (!mqtt_client.connected())  {                     // MQTT接続完了までループ
    M5.Lcd.print(".");
    delay(1000);
    if (mqtt_client.connect(DEVICE_NAME)) {               // DEVICE_NAMEに接続完了なら
      M5.Lcd.println("Connected!!");
      mqtt_client.subscribe("device/01/local", QOS);      // サブスクライブ(受信)するトピックを設定
      // mqtt_client.subscribe("topic", QOS);             // トピックが複数ある場合は続けて設定
      delay(1000);                                        // 接続完了表示時間
    } else {                                              // 接続失敗なら
      M5.Lcd.printf("Failed!!, rc=%d\n", mqtt_client.state());
    }
  }
}
// 初期設定 *****************************************
void setup() {
  M5.begin();             // 本体初期化
  Serial.begin(9600);     // シリアル通信開始
  Wire.begin(32, 33);     // Wire init, adding the I2C bus.  Wire初始化, 加入i2c总线
  qmp6988.init();         // 気圧センサの初期化

  M5.Lcd.setRotation(3);  // 画面表示回転
  connect_wifi();         // Wi-Fi接続関数呼び出し
  connect_aws();          // AWS IoT初期設定関数呼び出し

  pinMode(10, OUTPUT);    //本体LED赤
  digitalWrite(10, HIGH); //本体LED初期値OFF(HIGH)

  //LCD表示
  M5.Lcd.fillScreen(BLACK);   // 背景色
  M5.Lcd.setTextFont(2);      // テキストフォント指定
  M5.Lcd.drawFastHLine(0, 0, 240, WHITE);           // 指定座標から平行線
  M5.Lcd.fillRect(0, 1, 240, 17, (uint16_t)0x098a); // タイトルエリア背景
  M5.Lcd.drawFastHLine(0, 19, 240, WHITE);          // 指定座標から平行線
  M5.Lcd.setTextColor(WHITE);                       // 文字色
  M5.Lcd.drawCentreString("AWS IoT MQTT LOCAL ENV3", 120, 2, 2);  // 指定座標を中心に文字表示(x, y, font)
}
// メイン *****************************************
void loop() {
  M5.update();        // ボタン状態更新
  mqtt_client.loop(); // サブスクライブ(受信)するために必要

  // 環境センサENVⅢ測定データ取得
  if (sht30.get() == 0) {   // 温湿度センサからデータ取得したら
    s_tmp = sht30.cTemp;    // 温度データを取得
    s_hum = sht30.humidity; // 湿度データを取得
  } else {                  // データが取得できなければ
    s_tmp = 0, s_hum = 0;   // 温湿度を0セット
  }
  s_press = qmp6988.calcPressure(); // 気圧データ取得

  // 本体ボタン処理
  if (M5.BtnA.wasPressed()) {
    digitalWrite(10, LOW);        // 本体LED赤点灯
    Serial.println("BTN_A ON!");  // 標準のシリアル出力
    publish();                    // publish処理関数呼び出し
  }
  // アナログ入出力電圧(アナログ入出力値)表示
  M5.Lcd.setTextFont(4);                        // フォント設定
  M5.Lcd.setCursor(10, 32); M5.Lcd.setTextColor(WHITE, BLACK);  // 座標指定(x, y) // 文字色、背景
  M5.Lcd.printf("Temp : %2.1f C'", s_tmp);      // 温度表示
  M5.Lcd.setCursor(10, 64);                     // 座標指定(x, y)
  M5.Lcd.printf("Hum : %2.1f %%", s_hum);       // 湿度表示
  M5.Lcd.setCursor(10, 96);                     // 座標指定(x, y)
  M5.Lcd.printf("Press : %2.1f hPa", s_press/100);  // 気圧表示
  delay(200); // 遅延時間
}

・動作確認

実際に動作確認してみましょう。
MQTTの通信データ確認には「AWS IoT」の「MQTTテストクライアント」を使用します。

「MQTTテストクライアント」は下画像のように「AWS IoT」のページからアクセスできます。

AWS IoT MQTTテストクライアントの使い方

「MQTTクライアント」の使用方法は以下のリンクで詳しく紹介しています。

AWS IoTの使い方① 初期設定、デバイス登録、MQTT接続テスト方法。
AWS IoTを使用したクラウド経由での遠隔操作とデータ監視の方法を数回に分けて紹介。今回はAWS IoTを使うための準備方法と動作確認方法を詳しく紹介します。

サンプルプログラムを書き込んだ2つのデバイスを並べて「AWS」のクラウドサーバー経由で動作確認を行います。
画像の左側が親機の「CORE2」、右側が子機の「M5StickC Plus」に環境センサ「ENVⅢ」を接続したものです。

ArduinoコマンドでAWS IoT 動作テスト

実際に動作確認をしたものは下画像のようになります。

ArduinoコマンドでAWS IoT 動作テスト

親機「CORE2」の電源を入れると、上画像のようにWi-Fi接続が開始されます。
Wi-Fi接続が完了するとAWSとのMQTT接続が開始され、接続完了で次の画面に切り換わります。

ArduinoコマンドでAWS IoT 動作テスト

子機「M5StickC Plus」も同様に電源を入れるとWi-Fi接続からMQTT接続が開始され、接続完了で次の画面に切り換わります。

ArduinoコマンドでAWS IoT 動作テスト

通信接続が完了すると上画像のようになり「M5StickC Plus」側には環境センサ「ENVⅢ」の測定データが表示されており「CORE2」にはまだデータは表示されていません。

ArduinoコマンドでAWS IoT 動作テスト

「CORE2」のボタンAを押すと、トピック[device/01/local]に空のJOSNデータ「{“”:””}」をパブリッシュ(送信)します。

ArduinoコマンドでAWS IoT 動作テスト

「CORE2」の画面が青色になり、画像ではわかりにくいですが「M5StickC」本体の赤色LEDが点灯します。

ArduinoコマンドでAWS IoT 動作テスト

「M5StickC」はトピック[device/01/local]へのサブスクライブ(受信)によって、センサのデータを「CORE2」用のトピック[device/host]にJSON形式でパブリッシュ(送信)します。
「CORE2」がデータを受信すると、画面にデータを表示し、画面が青から黒色に変わります。

ここまでの動作をAWS IoTの「MQTTテストクライアント」で確認すると下画像のようになります。
トピック[device/01/local]のサブスクライブを受けて[device/host]へ測定データがパブリッシュされているのが確認できます。(下画像のJSONデータの内容は上写真とは異なります。)

AWS IoT MQTTテストクライアント動作確認

ArduinoコマンドでAWS IoT 動作テスト

「M5StickC」のボタンAを押すと「CORE2」のトピック[device/host]へ測定データをJSON形式でパブリッシュ(送信)します。

ArduinoコマンドでAWS IoT 動作テスト

「CORE2」のトピック[device/host]へのサブスクライブ(受信)によって「CORE2」では受信したJSONデータを処理して画面に表示します。

ここまでの動作をAWS Iotの「MQTTテストクライアント」で確認すると下画像のようになります。
トピック[device/host]へ測定データがパブリッシュされているのが確認できます。
(下画像のJSONデータの内容は上写真とは異なります。)

AWS IoT MQTTテストクライアント動作確認
今回はそれぞれのボタンを操作することでデータの送受信を行なっています。
自動でデータの送受信をする場合はメイン処理の中で定期的にパブリッシュするようにプログラムしてください。
この時、送受信間隔を短くし過ぎると大量にデータを送信して課金に関わるカウントが増えることになるため、よく確認してから行ってください!
「AWS IoT」の「MQTTテストクライアント」でサブスクリプションに「#」を指定すると全ての受信データを表示することができます。
無駄な課金が発生しないように、サインアウトする前や、デバイスを使用していない時に、意図しないデータの送受信が行われていないかを必ず確認するようにしましょう。
デバイス使用時には送受信をしていなくても接続時間として課金のカウントがされています。課金額としては微小(初回12ヶ月は225万分/月まで無料)ですので数台程度なら大丈夫と思いますが、デバイス数が複数になるとカウントも増えますので、使用しないデバイスの通信接続は遮断しておきましょう。

5.まとめ

M5Stack社の「CORE2」と「M5StickC Plus」に「ENVⅢ」を接続して「C言語」ベースの「Arduinoコマンド」で「AWS IoT」を使用したクラウド経由の遠隔操作、データ監視を行う方法を紹介しました。

「AWS IoT」を使用して、デバイスの登録を行うことで、個別の「セキュリティ証明書」と「秘密鍵」を作成することができ、クラウド経由での安全なデータ通信を行うことができます。

証明書と秘密鍵は紛失や流出をしないよう大切に管理しましょう。
紛失や流出の可能性がある場合は、個別に無効化できるように証明書と秘密鍵は各デバイス個別に設定しておきましょう。

通信にはデータパケットの小さい「MQTT」通信で行い、設定した「トピック」宛に「パブリッシュ(送信)ー サブスクライブ(受信)」パターンを使用して、中継サーバーのブローカー(Broker)経由で行われます。

「C言語」ベースの「Arduinoコマンド」を使用することで、より自由度の高いプログラムが組めるため、MQTT通信を使用したクラウド経由の遠隔操作、データ監視環境を思い通りに構築することができます。

今回はシンプルな相互データ通信の方法を紹介しましたが、これができれば複雑な遠隔操作やデータ監視もできることになります。サーバーにデータを蓄積して管理、解析等行う方法は「AWS」で提供(制限付き無償期間あり)されているので、ここでも無償で利用できる範囲で色々紹介していければと思います。

今回は「C言語」ベースの「Arduino」コマンドを使用しましたが、シンプルな通信だけならより簡単に実現できる「ビジュアルプログラミング」を使用した方法も以下のリンクで詳しく紹介しています。
AWS IoTの使い方② M5Stack UiFlowで簡単MQTT通信 遠隔操作 監視の方法
M5Stack社のCORE2とM5StickC PlusにENVⅢを接続してAWS IoTを使用したクラウド経由の遠隔操作、データ監視を行う方法を詳しく紹介します。

「AWS」には「AWS IoT」以外にも様々なサービスがあります。
登録して1年間はサービスごとに無料枠が設定されており、その範囲内であれば無料で使用できますが、無料枠の範囲外になると課金が始まるものも出てくるため、ご自身でよく確認してご利用ください。

私はよく確認せずに外部サービスのチュートリアルを安易に利用したために失敗しました・・・
こうならないためにも、以下のリンクの失敗談の中で請求状況の確認方法等を紹介しています。私の環境での確認方法となりますが、ご参考までにご確認ください。
気軽にAWSでIoTやったら無知で痛い目見た話。課金の止め方。
クラウドでIoTやりたい!好奇心だけでAWS(アマゾンウェブサービス)の無料枠で手軽にできると思ってやったら無知すぎて無駄な課金で¥5,000以上捨ててしまった話。

コメント

  1. たじ より:

    参考にさせていただいています。

    一つ質問です。
    certs.cpp

    const char *root_ca
    const char *certificate
    const char *private_key

    には、それぞれ
    *-certificate.pem.crt
    *-private.pem.key
    *-public.pem.key

    のどの内容をコピーしたら良いのでしょうか?
    root_ca, certificateが
    —–BEGIN CERTIFICATE—–
    となっていて、certificate に*-certificate.pem.crt を入れたら良いと考えていますが、そうなるとroot_caには何をコピーしたら良いでしょうか?

    https://logikara.blog/aws-iot-setup/#mokuji_4
    に「ルート証明書は必要に応じて後からでもダウンロードできる」と記述されているこのルート証明書のことでしょうか?
    これはどこからダウンロードできますか?

    アドバイスいただけると助かります。

    • logikara より:

      こんにちは。
      const char *root_ca
      const char *certificate
      const char *private_key
      には、それぞれ
      root-CA.crt
      *-certificate.pem.crt
      *-private.pem.key
      の内容をコピーします。
      *-public.pem.key は使用しません。

      「root-CA.crt」を入手されていないように思いますので、以下の手順でダウンロードして設定してみてください。
      1.AWSコンソールにサインインします。
      2.AWS IoTのダッシュボードに移動します。
      3.左側のメニューから「セキュリティ」をクリックします。
      4.「証明書」をクリックします。
      5.[証明書を作成]ボタンをクリックして「証明書を作成」を選択します。
      6.「証明書のステータス」は「非アクティブ」で良いので、[作成]ボタンをクリックします。
      7.ダウンロードできる証明書の一覧のウインドウが表示されるので下の方の「ルート CA 証明書(AmazonルートCA 1)」をダウンロードします。
      8.ダウンロードしたファイルをメモ帳等で開いて「const char *root_ca」にコピペします。

      今作成した証明書は不要なので、チェックボックスにチェックを入れて「アクション」のドロップダウンリストから削除(アクティブだと削除できません)を選択して削除してください。
      「ルート証明書」は共通なのでデバイスが変わっても毎回同じものを設定すれば良いです。

      これでうまくいくと思いますのでお試しください♪

タイトルとURLをコピーしました