マイコン ESP32 を使って AWS IoT Core と Pub/Sub 通信するまで

2024.02.08

ESP32 で AWS IoT Core を使ったブログはいくつかあるのですが、今回はデバイスや周辺機器の用意からプログラムの作成までの基本的な流れを確認してみました。

やりたいこと

今回は、デバイスや開発環境のセットアップから始めて、ESP32 と AWS IoT Core 間でシンプルな Publish / Subscribe によるやり取りまでをやってみます。

100-diagram

デバイスの準備

始めるにあたり、ESP32 や作業用 PC が必要になります。今回の検証で利用した環境を紹介します。

  • ESP32ESP32-DevKitC ESP-WROOM-32開発ボード | 秋月電子通商
  • ブレッドボード:これも手元にあったものを使っています。(どこで購入したものかは覚えていません…)
    • モノによってはピンの幅が合わない場合があるようです。自分のブレッドボードは一般的なものを購入したと思います。
    • もし用意したブレッドボードが合わないときは、 ESP32 とブレッドボードはジャンパーワイヤで繋いでしまうのもアリかと思います。
  • LED抵抗内蔵5mmLED 5V 赤色 640nm OSR6LU5B64A-5V | 秋月電子通商
    • Lチカ用に用意しました。部屋のどこかにある抵抗を探すの面倒だったので「抵抗内蔵」のものを用意しました。
  • ジャンパーワイヤブレッドボード・ジャンパーワイヤ 15cm赤 | 秋月電子通商
    • 手元にあるものを使っています。「オス - オス」のものを適宜用意してください。
  • USB ケーブル:作業用 PC と ESP32 を接続するために使います。
    • 私は M2 Mac を利用しているため、「TypeC - Micro USB Type-B(2.0)」のものを利用しています。ご利用のPC環境に合わせたものを用意してください。
    • 作業用 PC から ESP32 のプログラムを書き込むことになるので、USBケーブルは必ずデータ転送が可能なものを利用してください。 充電専用のケーブルもありますが見た目で判別できないので注意が必要です。

購入コスト

これら全てを用意する場合、購入にかかるコストは送料込みで恐らく 3,000 円程度で済むかと思います。
(購入先により価格はバラバラなので 3,000 円以上になる場合があります。金額は参考として捉えていただければ幸いです。)

構成

デバイス側の構成は単純です。抵抗内蔵の LED なのでジャンパワイヤでつなぐだけです。
(今回は Lチカに 25 番ピンを使っています)

00-breadboard

開発環境の用意

シリアルドライバのインストール

開発用PC(M2 Mac) と ESP32 が通信できるように Mac にシリアルドライバをインストールします。
ドライバは下記よりダウンロードします。

「ダウンロード」のタブを開いてMac 用のものをダウンロードします。Windows や Linux 用のものもあるようですが未確認です。

68-serial-driver

Arduino IDE のインストールと設定

開発に使う PC は M2 Mac を使っているので、本記事は Mac 前提の手順になります。
ESP32 の開発には Arduino IDE を使うので公式ページよりダウンロードします。

Apple Silicon 用のものが用意されているので、ダウンロードしてインストールします。

60-download-ide

インストールできたら Arduino IDE を起動して設定画面を開きます。
Mac の場合は Arduino IDE のウィンドウをアクティブにした状態で、画面上部のメニューから「Preferences...」をクリックします。

61-preference

設定画面の「Settings」タブを開いて、「Additional boards manager URLs:」に次の URL をセットします。

https://dl.espressif.com/dl/package_esp32_index.json

62-board-manager-url

次に、ボードマネージャーをインストールします。Arduino IDE の画面左側にあるメニューの上から 2 番目のアイコンをクリックしてボードマネージャーを開きます。

63-board-manager

検索窓に esp32 と入力して表示を絞り込みます。残ったものの中から「esp32 by Espressif Systems」をインストールします。
(少し時間がかかるかもしれません。私の場合は 5 分ほどかかったように思います)

64-install-esp32-board-manager

この状態で ESP32 のボード情報を設定できるようになっているので、「Tools」メニューから「ESP32 Dev Module」を選択します。

下記は選択済みの画面ショットなので、「Tools」以下のプルダウン表示が異なるかもしれません。その場合は Board: "xxxxx" となっている箇所を選んでみてください。

72-select-board

ライブラリのインストール

Arduino IDE で開発するときに各スケッチで利用するライブラリをインストールします。 実装したい内容に対応したライブラリをインストールすることになります。

今回は最終的に AWS IoT Core へ MQTT で JSON 形式のメッセージを送りたいので、以下の 2 つをインストールします。

ちなみに、同じような機能を提供する似た名前のライブラリが複数存在する場合がありますが、基本的にライブラリが違えばコードの書き方も変わるので、自分が使いたいものを間違えずにインストールしてください。

ライブラリの管理画面は Arduino IDE の画面右側の上から 3 つ目のアイコンで開けます。

50-launch-library-manager

  • ArduinoJsonのインストール
    検索フィールドに mqttと検索して「MQTT by Joel Gaehwiler...」と書かれているものをインストールします。

51-install-arduino-mqtt

  • arduino-mqtt のインストール
    同様にjson で検索して「Arduinojson」をインストールします。

52-install-ArduinoJson

Lチカで動作確認

ここまでできたら、簡単なスケッチを作って動作確認をしてみます。
最初に PC と ESP32 を USB ケーブルで接続しておきます。

次に、Arduino IDE の画面下部にあるエディタ領域に Lチカのコードを書きます。

65-esp32-led-test

0.5 秒単位で LED が点滅を繰り返すコードです。

void setup() {
  pinMode(25, OUTPUT);
}

void loop() {
  digitalWrite(25, HIGH);
  delay(500);
  digitalWrite(25, LOW);
  delay(500);
}

コードが書けたら「右矢印」のボタンを押します。これでコードのコンパイルと ESP32 へのアップロードが実行されます。

66-compile-upload

問題なくアップロードできたら「Output」の部分に下記のようなメッセージが表示されていると思います。また、LED も点滅を開始していれば成功です。

67-success-comile-and-upload

これで ESP32 の開発ができる状態になりました。次は AWS との接続を試してみます。

デバイス証明書の作成

今回は MQTT で AWS IoT Core と通信したいので、ESP32 に設置するデバイス証明書を AWS IoT Core 上で発行します。
AWS IoT Core のマネジメントコンソールを開いて、メニューから「モノ」の画面を開きます。「モノ」の画面が開けば「モノを作成」をクリックします。

00-create-thing

「モノ」の名前を設定します。この名前は AWS IoT 側で対象の ESP32 を識別するものになるので、デバイス側に付与する ID(ESP32 上のプログラム上で指定するMQTT のクライアントIDのこと) と合わせておくことをおすすめします。
(今回は esp32_test としています)

01-named-thing-name

デバイス証明書の設定では、デフォルトの「新しい証明書を自動生成」を選択しておきます。

02-device-cert

既存のポリシーがない場合は、新規に作成します。

03-make-iot-policy

先程の画面で「ポリシーを作成」をクリックするとブラウザの別タブが開くので、「ポリシーステートメント」の画面にある「ポリシードキュメント」に JSON 形式のポリシーをセットします。

ポリシーの中にある以下の項目は、利用環境に合わせて置き換えてください。

  • <YOUR_REGION>: 利用リージョン
  • <YOUR_AWS_ACCCOUNT_ID>: 利用する AWS アカウント ID

また、Action": "iot:Connect" のポリシー箇所で "Resource": "... client/esp32_test" としている部分は接続できるクライアント(クライアント ID) を指定する設定です。もしデバイス側で異なるクライアント ID を使っている場合は、適宜合わせて修正してください。

ただし、基本的に「クライアント ID」は、デバイス側でセットしたものとAWS 側で「モノ」として登録した名前と一致させることが望ましいので、本記事では、AWS IoT とデバイス側のクライアント ID はいずれも esp32_test を使うものとします。

04-cleate-policy-json

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "iot:Connect",
      "Resource": "arn:aws:iot:<YOUR_REGION>:<YOUR_AWS_ACCCOUNT_ID>:client/esp32_test"
    },
    {
      "Effect": "Allow",
      "Action": "iot:Subscribe",
      "Resource": "arn:aws:iot:<YOUR-REGION>:<YOUR_AWS_ACCCOUNT_ID>:topicfilter/esp32/sub"
    },
    {
      "Effect": "Allow",
      "Action": "iot:Receive",
      "Resource": "arn:aws:iot:<YOUR-REGION>:<YOUR_AWS_ACCCOUNT_ID>:topic/esp32/sub"
    },
    {
      "Effect": "Allow",
      "Action": "iot:Publish",
      "Resource": "arn:aws:iot:<YOUR-REGION>:<YOUR_AWS_ACCCOUNT_ID>:topic/esp32/pub"
    }
  ]
}

ポリシーは「ビルダー」を使って作成することもできます。

04-create-policy

ポリシーが作成できたら元の画面に戻って「更新ボタン」を押してください。ポリシー一覧に先程作成したポリシー(esp_test_policy)にチェックを入れて「モノを作成」をクリックします。
(検索フィールドにポリシー名の一部または全部を入れることで表示をフィルタリングできます)

05-attache-policy

最後に ESP32 にセットするデバイス証明書が作成されるので忘れずにダウンロードして下さい。

  • デバイス証明書(xxxxx.pem.crt
  • パブリックキーファイル(xxxxx-public.pem.key
  • プライベートキーファイル(xxxxx-private.pem.key
  • ルート CA 証明書
    • 「RSA 2048 ビットキー: Amazon ルート CA 1」のみで OK です。
    • ルート CA 証明書は後からダウンロード可能です。

06-download-certs-2

以上で、今回利用する ESP32 をモノ(esp32_test)として AWS に登録できました。
画面下部の「証明書」タブから証明書もアタッチされていることが分かりますね。

07-created-thing

AWS IoT Core と Pub/Sub するスケッチの作成

ここでは AWS の GitHub リポジトリで公開されているサンプルコードを使ってみます。
このリポジトリには M5Stack 用のサンプルもありますが、今回は aws-iot-esp32-arduino-examples/examples/basic-pubsub/ のものを使います。

先程は「Lチカ」するコードを作成しました。そのスケッチ内容を上書きしてもいいのですが今回は別スケッチとして新規に作成します。
Arduino IDE 画面左側にある「NEW SKETCH」をクリックします。

08-create-new-sketch

新しいスケッチが作成できたらスケッチ名を分かりやすいものに変更してきます。
画面右上にある 3 点リーダーをクリックして「Rename」をクリックします。

09-rename-new-sketch-2

適当な名前に変更します。(今回は sketch_esp32_aws_pubsub.inoとしました)

10-rename-aws-pubsub

次に、デバイス証明書の格納を行います。
単一のコード上に直接書いてもいいのですが、AWS のサンプルコードの通りヘッダーファイル(secrets.h)で管理することにします。

先程と同じように「New Tab」をクリックします。

11-create-new-tab

ファイル名は secrets.h とします。

12-named-secret-header

GitHub のサンプル(secrets.h)をコピペします。

/*
  Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
  Permission is hereby granted, free of charge, to any person obtaining a copy of this
  software and associated documentation files (the "Software"), to deal in the Software
  without restriction, including without limitation the rights to use, copy, modify,
  merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  permit persons to whom the Software is furnished to do so.
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#include <pgmspace.h>

#define SECRET
#define THINGNAME ""

const char WIFI_SSID[] = "";
const char WIFI_PASSWORD[] = "";
const char AWS_IOT_ENDPOINT[] = "xxxxx.amazonaws.com";

// Amazon Root CA 1
static const char AWS_CERT_CA[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
)EOF";

// Device Certificate
static const char AWS_CERT_CRT[] PROGMEM = R"KEY(
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
)KEY";

// Device Private Key
static const char AWS_CERT_PRIVATE[] PROGMEM = R"KEY(
-----BEGIN RSA PRIVATE KEY-----
-----END RSA PRIVATE KEY-----
)KEY";

この時、下記の項目を自分の環境に合わせて変更します。

  • 19行目:THINGNAME を AWS IoT Core に登録した「モノ」の名前に変更
    • MQTT 通信時における Client ID となるパラメータです
    • AWS IoT に登録した「esp32_test」以外を指定すると AWS 側で設定した IoT ポリシーの許可対象から外れるので接続できなくなります。
  • 21行目:利用する Wi-Fi の SSID
  • 22行目:利用する Wi-Fi のパスワード
  • 23行目:利用する AWS アカウントの AWS IoT エンドポイント

13-your-env-settings-2

AWS IoT のエンドポイントは AWS IoT Core のマネジメントコンソールから確認可能です。メニューの「設定」を開いて「デバイスデータエンドポイント」に書かれているものをコピペしてください。

14-check-endpoint

次は証明書(デバイス証明書とルート CA 証明書)と秘密鍵をセットします。

15-add-certs

各ファイルを 開発用 PC のエディタなどで開いてコピペします。このコードでは証明書のデータの扱いに「生文字列リテラル(Raw String Literals)」を利用しているので、そのままコピペして問題ありません。
もし証明書の記入で const char* rootCA = "-----BEGIN CERTIFICATE といった形式で書いている場合は、改行コードが各行末ごとに必要になります。

16-add-certs

証明書の格納ができたら、ようやく処理ロジック本体の実装です。先に開いていたタブsketch_esp32_aws_pubsub.inoにコードを書いていきます。

20-ino-code

コードを書くと言っても、下記のサンプルコード(basic-pubsub.ino)をコピペするだけです。(GitHub のものをコピペ)

/*
  Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
  Permission is hereby granted, free of charge, to any person obtaining a copy of this
  software and associated documentation files (the "Software"), to deal in the Software
  without restriction, including without limitation the rights to use, copy, modify,
  merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  permit persons to whom the Software is furnished to do so.
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/


#include "secrets.h"
#include <WiFiClientSecure.h>
#include <MQTTClient.h>
#include <ArduinoJson.h>
#include "WiFi.h"

// The MQTT topics that this device should publish/subscribe
#define AWS_IOT_PUBLISH_TOPIC   "esp32/pub"
#define AWS_IOT_SUBSCRIBE_TOPIC "esp32/sub"

WiFiClientSecure net = WiFiClientSecure();
MQTTClient client = MQTTClient(256);

void connectAWS()
{
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

  Serial.println("Connecting to Wi-Fi");

  while (WiFi.status() != WL_CONNECTED){
    delay(500);
    Serial.print(".");
  }

  // Configure WiFiClientSecure to use the AWS IoT device credentials
  net.setCACert(AWS_CERT_CA);
  net.setCertificate(AWS_CERT_CRT);
  net.setPrivateKey(AWS_CERT_PRIVATE);

  // Connect to the MQTT broker on the AWS endpoint we defined earlier
  client.begin(AWS_IOT_ENDPOINT, 8883, net);

  // Create a message handler
  client.onMessage(messageHandler);

  Serial.print("Connecting to AWS IOT");

  while (!client.connect(THINGNAME)) {
    Serial.print(".");
    delay(100);
  }

  if(!client.connected()){
    Serial.println("AWS IoT Timeout!");
    return;
  }

  // Subscribe to a topic
  client.subscribe(AWS_IOT_SUBSCRIBE_TOPIC);

  Serial.println("AWS IoT Connected!");
}

void publishMessage()
{
  StaticJsonDocument<200> doc;
  doc["time"] = millis();
  doc["sensor_a0"] = analogRead(0);
  char jsonBuffer[512];
  serializeJson(doc, jsonBuffer); // print to client

  client.publish(AWS_IOT_PUBLISH_TOPIC, jsonBuffer);
}

void messageHandler(String &topic, String &payload) {
  Serial.println("incoming: " + topic + " - " + payload);

//  StaticJsonDocument<200> doc;
//  deserializeJson(doc, payload);
//  const char* message = doc["message"];
}

void setup() {
  Serial.begin(9600);
  connectAWS();
}

void loop() {
  publishMessage();
  client.loop();
  delay(1000);
}

コードの記載が終われば、先程と同じようにコンパイルとアップロードを実行します。

21-verify-and-upload

AWS IoT Core と Pub/Sub 通信してみる

このコードでは、2つのデータ項目(timesensor_a0)をトピック esp32/pub に Publish しているので、1 秒単位でメッセージが AWS IoT Core に届いていることを確認します。

確認は、マネジメントコンソール上のテストクライアントを使いました。

22-esp32-pub-aws-client

1 秒間隔で ESP32 からメッセージを受信していることが確認できたら OK です。

53-aws-client-subscribe-2

最後に AWS IoT 側からメッセージを送って、ESP32 で受け取れることを確認します。
すでに ESP32 側では esp32/sub トピックを Subscribe しているので、このトピックにメッセージを送ります。

71-publish-from-aws

ESP32 で受け取ったメッセージは Arduino IDE のシリアルモニターで確認することができます。
シリアルモニターは IDE 画面の右上にある虫眼鏡のアイコンをクリックすると開きます。

69-serial-monitor

すでに AWS からメッセージを送っていれば、シリアルモニターに受信したメッセージが表示されていると思います。

70-message-from-aws

最後に

基本的な内容だったと思いますが、実は USB ケーブルの選択を誤っていて通信できないエラーに悩んでいました。

手元にあった ケーブルを 3 本用意しており「どれか 1 本くらいはデータ通信できるだろう」と思い込んでいたので、全て充電専用のケーブルだったことが原因であることに気づかず、ハマってしまいました。

この後、100 円ショップでケーブルラベルを買ってきて、データ通信できるものを人目で識別できるようにしました…

参考リンク