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が初期値が小さいので変更します。
ArduinoフォルダのlibrariesにPubSubClientをインストールしてMQTT_MAX_PACKET_SIZEを変更します。
srcフォルダにあります。
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 の改行をつける
- 複数行を継続するためにコードの各行の \ へつける。
といった、対応をします。
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.
これだけでだとフォーラムでもさっぱりわからないのでエラーを詳細化します。
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 とアクセスします。
main.ccpを開いて、
ループ部分の値を4096から8192に変更しました。
実際に動かしてみるとCPU haltedは出なくて、無事に起動しました!
PublishとSubscribeを同じトピックにしているので、AWS IoTに送るとESP32内ので反応しています。
また、AWS IoTで「/#」で全トピックを引っ掛けるようにしてみると無事受信していることも確認できました。
Mongoose OSでESPr Developer 32をAWS IoTにつなげるナレッジとともに、AWSクラウドの力が使えるときに、Arduino IDEでも使えると、Groveシリーズの力を引き出しやすいので、対応の幅が広がりますね。
それでは、よき Arduino & ESP32 & IoT Life を!