ESP32 DeveloperでAWS IoTを動かそうとしてハマったメモ

ESP32 DeveloperでAWS IoTを動かそうとしてハマったメモを記しておきます。

経緯

ESP32ではAWSから提供されるAWS IoT SDKをそのままは使えないようなので、根性でなんとかするのかなというところです、以前、ESP8266で行ったPubSubClientによるMQTT接続の記憶をたどってやってみることにしました。

いろいろ調べたところ、WiFiClientSecureでセキュアな通信を成立させ、PubSubClientで使うのが良さそうと方向性が見えてきました。

このあたりは@MaripoGodaさんのESP32でAWS IoTに繋いでThing Shadowを弄る記事がとても助けになりました。

PubSubClient.hのMQTT_MAX_PACKET_SIZE変更

まずPubSubClient.hのMQTT_MAX_PACKET_SIZEが初期値が小さいので変更します。

image

ArduinoフォルダのlibrariesにPubSubClientをインストールしてMQTT_MAX_PACKET_SIZEを変更します。

image

srcフォルダにあります。

image

128だったので2048に変更します。

ソースコード

ほぼおなじコードなので恐縮ですが、私の場合は、AWS IoT Shadowでなくデータ送受信に使いたいので調整します。

#include <WiFiClient.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
 
char *ssid = "<YOUR_SSID>";
char *password = "<YOUR_WIFI_PASSWORD>";
 
const char *endpoint = "<AWS_IOT_ENDPOINT>";
// Example: xxxxxxxxxxxxxx.iot.ap-northeast-1.amazonaws.com
const int port = 8883;
char *pubTopic = "/sample123";
char *subTopic = "/sample123";
 
const char* rootCA = "-----BEGIN CERTIFICATE-----\n" \
"......" \
"-----END CERTIFICATE-----\n";
 
const char* certificate = "-----BEGIN CERTIFICATE-----\n" \
"......" \
"-----END CERTIFICATE-----\n";
 
const char* privateKey = "-----BEGIN RSA PRIVATE KEY-----\n" \
"......" \
"-----END RSA PRIVATE KEY-----\n";
 
WiFiClientSecure httpsClient;
PubSubClient mqttClient(httpsClient);
 
void setup() {
    Serial.begin(115200);
 
    // Start WiFi
    Serial.println("Connecting to ");
    Serial.print(ssid);
    WiFi.begin(ssid, password);
 
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println("\nConnected.");
 
    // Configure MQTT Client
    httpsClient.setCACert(rootCA);
    httpsClient.setCertificate(certificate);
    httpsClient.setPrivateKey(privateKey);
    mqttClient.setServer(endpoint, port);
    mqttClient.setCallback(mqttCallback);
 
    connectAWSIoT();
}
 
void connectAWSIoT() {
    while (!mqttClient.connected()) {
        if (mqttClient.connect("ESP32_device")) {
            Serial.println("Connected.");
            int qos = 0;
            mqttClient.subscribe(subTopic, qos);
            Serial.println("Subscribed.");
        } else {
            Serial.print("Failed. Error state=");
            Serial.print(mqttClient.state());
            // Wait 5 seconds before retrying
            delay(5000);
        }
    }
}
 
long messageSentAt = 0;
int dummyValue = 0;
char pubMessage[128];
 
void mqttCallback (char* topic, byte* payload, unsigned int length) {
    Serial.print("Received. topic=");
    Serial.println(topic);
    for (int i = 0; i < length; i++) {
        Serial.print((char)payload[i]);
    }
    Serial.print("\n");
}
 
void mqttLoop() {
    if (!mqttClient.connected()) {
        connectAWSIoT();
    }
    mqttClient.loop();
 
    long now = millis();
    if (now - messageSentAt > 5000) {
        messageSentAt = now;
        sprintf(pubMessage, "{\"message\": \"%d\"}", dummyValue++);
        Serial.print("Publishing message to topic ");
        Serial.println(pubTopic);
        Serial.println(pubMessage);
        mqttClient.publish(pubTopic, pubMessage);
        Serial.println("Published.");
    }
}
 
void loop() {
  mqttLoop();
}

rootCA、certificate、privateKey 変数の入れ方

rootCA、certificate、privateKey 変数には、

  • rootCA
    • AWS IoTから提供されるルート証明書(root-CA.crtでダウンロードされるもの)
  • certificate
    • ~~~~.cert.pemでダウンロードした鍵ファイル
  • privateKey
    • ~~~~.private.keyでダウンロードした鍵ファイル

を入れるんですが、これは私の理解が追いついてなくて複数行の反映のさせ方で悩みました

例えば、こういう鍵ファイルがあるとしましょう。

-----BEGIN CERTIFICATE-----
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
-----END CERTIFICATE-----

本来はもっと行数が多い場合はありますが、あくまで例です。

これを、

const char* rootCA = "-----BEGIN CERTIFICATE-----\n" \
"......" \
"-----END CERTIFICATE-----\n";

に移すためにはこうします。

const char* rootCA = "-----BEGIN CERTIFICATE-----\n" \
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n" \
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\n" \
"CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n" \
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD\n" \
"EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n" \
"-----END CERTIFICATE-----\n";
  • 末尾に \n の改行をつける
  • 複数行を継続するためにコードの各行の \ へつける。

といった、対応をします。

image

WindowsでArduinoコードを書くとバックスラッシュが¥になるので構造がわかりやすいです。

このように、rootCA、certificate、privateKeyを割り当てます。

AWS IoTまわりでつまづきがちなポイントは、

ESP32でAWS IoTに繋いでThing Shadowを弄る(2) トラブルシューティング編 – コーヒーサーバは香炉である

で色々なケースが載っているのでぜひ参考下さい。

その他変更点

あとは、トピックをpublish、subscribeともに「/sample123」というテスト用のトピックにしました。

char *pubTopic = "/sample123";
char *subTopic = "/sample123";

また、データの送り方もstateでなく簡単なmessageとしました。

sprintf(pubMessage, "{\"message\": \"%d\"}", dummyValue++);

最大のハマリポイント:MQTT接続前後でCPU halted発生

こちらをESP32に書き込んでみたところ、MQTT接続前後でCPU halted発生してしまいました。

text
Connected to wifi
Guru Meditation Error: Core  1 panic'ed (Unhandled debug exception)
Debug exception reason: Stack canary watchpoint triggered 
Register dump:
PC      : 0x40083e4f  PS      : 0x00060a36  A0      : 0x800fc7ec  A1      : 0x3ffcd3c0  
A2      : 0x1cbeb2f2  A3      : 0x3ffcd600  A4      : 0x1cbeb2f2  A5      : 0xbc6c5d8b  
A6      : 0x00000048  A7      : 0xbe4bb048  A8      : 0xb3e094fa  A9      : 0x87ca897d  
A10     : 0x16debeaa  A11     : 0xe62ccd60  A12     : 0x401081e4  A13     : 0x3ffcdf80  
A14     : 0x00000020  A15     : 0x00000000  SAR     : 0x0000001c  EXCCAUSE: 0x00000001  
EXCVADDR: 0x00000000  LBEG    : 0x4000c46c  LEND    : 0x4000c477  LCOUNT  : 0xffffffff  

Backtrace: 0x40083e4f:0x3ffcd3c0 0x400fc7ec:0x3ffcd710 0x400ff989:0x3ffcd730 0x40109234:0x3ffcd750 0x401084c1:0x3ffcd770 0x4010819b:0x3ffcd790 0x401081f4:0x3ffcd7c0 0x401010d1:0x3ffcd7e0 0x40103dbc:0x3ffcdc00 0x40105434:0x3ffcdc50 0x401055ea:0x3ffcdcb0 0x40105a28:0x3ffcde20 0x40105c34:0x3ffcde40 0x40105c52:0x3ffcdec0 0x40103702:0x3ffcdee0 0x401038ee:0x3ffcdf70 0x40103a8b:0x3ffce0b0 0x400f4875:0x3ffce100 0x400f48ae:0x3ffce130 0x400f456e:0x3ffce210 0x4010d3c7:0x3ffce240 0x4010d4ef:0x3ffce2b0 0x400f84f4:0x3ffce2d0 0x400f8534:0x3ffce2f0 0x400d2a06:0x3ffce310 0x400d1aeb:0x3ffce460 0x400d2e3e:0x3ffce4a0 0x400d12c5:0x3ffce540 0x40136352:0x3ffce560

CPU halted.

これだけでだとフォーラムでもさっぱりわからないのでエラーを詳細化します。

image

VerboseにDebug Levelを設定。ログが細かく出ます。

text
ets Jun  8 2016 00:22:57

rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
ets Jun  8 2016 00:22:57

rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0x00
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0008,len:8
load:0x3fff0010,len:160
load:0x40078000,len:10632
load:0x40080000,len:252
entry 0x40080034
Connecting to 
<Wi-Fi SSID>[D][WiFiGeneric.cpp:182] _eventCallback(): Event: 2 - STA_START
..[D][WiFiGeneric.cpp:182] _eventCallback(): Event: 4 - STA_CONNECTED
.[D][WiFiGeneric.cpp:182] _eventCallback(): Event: 7 - STA_GOT_IP
.
Connected.

mqttCallback
connectAWSIoT
.[I][ssl_client.cpp:45] start_ssl_client(): Free heap before TLS 164768
[I][ssl_client.cpp:47] start_ssl_client(): Starting socket
[I][ssl_client.cpp:75] start_ssl_client(): Seeding the random number generator
[I][ssl_client.cpp:84] start_ssl_client(): Setting up the SSL/TLS structure...
[I][ssl_client.cpp:97] start_ssl_client(): Loading CA cert
[I][ssl_client.cpp:115] start_ssl_client(): Loading CRT cert
[I][ssl_client.cpp:122] start_ssl_client(): Loading private key
[I][ssl_client.cpp:153] start_ssl_client(): Performing the SSL/TLS handshake...
Guru Meditation Error: Core  0 panic'ed (Interrupt wdt timeout on CPU1)
Register dump:
PC      : 0x400dbf70  PS      : 0x00060634  A0      : 0x8008543c  A1      : 0x3ffcba20  
A2      : 0x00000008  A3      : 0x00000001  A4      : 0x00060023  A5      : 0x3ffcc180  
A6      : 0x00000000  A7      : 0x00000001  A8      : 0x3ffc347c  A9      : 0x3ffcba00  
A10     : 0x00000000  A11     : 0x00060620  A12     : 0x80083f3c  A13     : 0x3ffcb920  
A14     : 0x00000000  A15     : 0x3ffcb5a0  SAR     : 0x00000000  EXCCAUSE: 0x00000006  
EXCVADDR: 0x00000000  LBEG    : 0x00000000  LEND    : 0x00000000  LCOUNT  : 0x00000000  

Backtrace: 0x400dbf70:0x3ffcba20 0x4008543c:0x3ffcba40

CPU halted.

どうも「[I][ssl_client.cpp:153] start_ssl_client(): Performing the SSL/TLS handshake…」あたりでしくじっている模様。いろいろ処理を消して調査しているとWiFiClientSecureがあやしい・・・。

すばらしい解決策を発見

こちらを片っ端から調べていきましたら@mgo_tecさんが遭遇されておりました。

Arduino – ESP32 WiFiClientSecure ライブラリのハングアップ問題がついに解決! | mgo-tec電子工作

まさにこの問題が起きています!!

Arduino メインループのスタックメモリを増やす対応で解決

私の問題は、連続して送信するエラーではなく、初動でコケているケースでしたので、ひとまずは「rduino メインループのスタックメモリを増やす」対応だけで解決しました。

先ほどの記事ではESP-IDF ( ESP32 開発環境 )で根本から修正していく対応も載っていましたが、これはかなり重い作業になるしArduino IDEから離れていくので避けられて良かったです。もしかすると、長期運用の際には、遭遇してしまうかもしれませんが、そのあたりは起きた時考えます。

まず、Arduinoのフォルダから、Arduino\hardware\espressif\esp32\cores\esp32 とアクセスします。

image

main.ccpを開いて、

image

ループ部分の値を4096から8192に変更しました。

実際に動かしてみるとCPU haltedは出なくて、無事に起動しました!

image

PublishとSubscribeを同じトピックにしているので、AWS IoTに送るとESP32内ので反応しています。

image

また、AWS IoTで「/#」で全トピックを引っ掛けるようにしてみると無事受信していることも確認できました。

Mongoose OSでESPr Developer 32をAWS IoTにつなげるナレッジとともに、AWSクラウドの力が使えるときに、Arduino IDEでも使えると、Groveシリーズの力を引き出しやすいので、対応の幅が広がりますね。

それでは、よき Arduino & ESP32 & IoT Life を!