ESP32 〜 Alibaba Cloud IoT Platform をMQTT接続(Arduino Core版)


背景

届いてから何ヶ月も開封していなかったESP32をいじって見よう、Arduino Core使えるから楽だよね、とりあえずAlibaba Cloud IoT PlatformへMQTT接続してpublishして遊ぼう、とやってみたら休日1日潰すレベルでハマったので忘備録。
IoT Platformを単純なMQTTのBrokerとしてみると結構クセありますね。原因はAlibaba CloudのIoT PlatformはMQTT brokerとしては30秒より短いKeep aliveを受け付けないんだけど、Arduinoの PubSubClientライブラリのデフォルトが15秒なので接続できない。なのでこの値を30秒以上にする必要がある。
Arduinoのエラーが2 : MQTT_CONNECT_BAD_CLIENT_ID - the server rejected the client identifier だった。普通に読むとClientIDが被疑箇所で、しかもClientIDの扱いが後述するけど、微妙に複雑なので、何度も何度も見直しててハマった。でも本当の原因はKEEP_ALIVEの値ですた。

必要な情報

Alibaba Cloud IoT Platform上のパラメータ

以下の情報をAlibaba Cloud Consoleから確認し控えておく。ここからESPがMQTT接続するArduinoコードを書く上で必要な各パラメータを生成する。
1. ProductKey
2. deviceName
3. deviceSecret
4. clientId
5. RegionId

1. ProductKey

2. deviceName / 3. deviceSecret

4. ClientId

ClientIdは任意。ここでは"arduino"。
MQTTプロトコル上のClientIDとは異なるので注意

5. RegionId

AlibabaCloud上のリージョンID。日本であれば"ap-northeast-1"

MQTTクライアントの接続に必要なパラメータ

MQTTプロトコルで接続するArduinoコードを書くための以下の4つの情報が必要になる。既述のAlibabaCloud IoT Platformの5つのパラメータから合成する。
1. Broker Address
2. ClientID(MQTTプロトコル上の)
3. UserName
4. Password

1. Broker Address

[ProductKey].iot-as-mqtt.[RegionId].aliyuncs.com
日本なら
[ProductKey].iot-as-mqtt.ap-northeast-1.aliyuncs.com

2. ClientID(MQTTプロトコル上)

Alibaba CloudのClientIdから合成。
TLS(sha1)を使う場合は
[ClientID]|securemode=2,signmethod=hmacsha1|
ここでは
arduino|securemode=2,signmethod=hmacsha1|
TLSを使わない場合は
[ClientID]|securemode=3,signmethod=hmacsha1|

3. UserName

[deviceName]&[ProductKey]
ここでは
testdev2&[ProductKey]

4. password

パスワード生成toolにproductKey、deviceName、deviceSecret、clientIdを入力して生成。
https://files.alicdn.com/tpsservice/471c155376d6a88a29c9ad66784e94f0.zip?spm=a21mg.p38356.a3.10.7b39255bbByw5k&file=471c155376d6a88a29c9ad66784e94f0.zip

ツールイメージ

またはこちらを参照して生成
https://jp.alibabacloud.com/help/doc-detail/86706.htm?spm=a21mg.p38356.b99.149.57d37b3frlA1t8

テスト

ここまで準備ができれば一般的なMQTT clientソフトウエアで接続できる。例えばLinuxのmosquittoならこんなコマンドでpublishできるはず。

# TLSなし、生MQTTの場合
mosquitto_pub -h [1. Broker Address] \
-i “[2. ClientID(MQTTプロトコル上)]” \
-u “[3. UserName]” \
-P “[4. password]” \
-t “[TOPIC]” \
-m '{id:”0001”,data:”helo world”}’

もしESP32ではなくてラズパイのようなlinux環境でやるならこのmosquittoでバッチ書くか、あるいはpaho-mqttあたりを使ってスクリプト書くのが王道なのかな?

ESP32

 機器調達

Alibaba CloudなのでAliexpressへ注文。usbで接続できるやつでも500円ぐらいから色々ある。技適マークがあり合法的に使えるやつを選ぼう。ほとんどがそうなはず。
同様に使えそうな一世代前のESP8266は安いけど技適マークつきのものが少ないので注意。

https://www.aliexpress.com/wholesale?catId=0&initiative_id=SB_20180916220315&SearchText=esp32

Aliexpressは安いけど1-4週間ほとかかる。そんな待てない、時間がもったいないという人はっちょっと高いけどAmazonから。
https://www.amazon.co.jp/s/ref=nb_sb_noss_2?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&url=search-alias%3Daps&field-keywords=esp32

ソースコード

#include <WiFiClientSecure.h>
#include <PubSubClient.h>

#define pin 2


const char* ssid     = "[WIFI_SSID]";
const char* password = "[WIFI_PASSWORD]";

const char* server = "[1. Broker Address]";
const int port = 1883;
const char* clientid = "[2. ClientID(MQTTプロトコル上の)]";
const char* mqttuser = "[3. UserName]";
const char* mqttpass = "[4. password]";
// TOPICはデフォルトのものを利用
const char* mqtttopic =  "/[ProductKey]/[deviceID]/update";
int cnt = 0;
char msg[50];

WiFiClientSecure client;
PubSubClient mqttClient(client);

void connAlibabaIoT() {
    while (!mqttClient.connected()) {
        if (mqttClient.connect(clientid, mqttuser, mqttpass)) {
            Serial.println("Connected.");
        } else {
            Serial.print("Failed. Error state=");
            Serial.println(mqttClient.state());
            Serial.println(clientid);
            // Wait 5 seconds before retrying
            delay(5000);
        }
    }
}

void setup() {
  pinMode(pin, OUTPUT);

  Serial.begin(115200);
  delay(10);
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  delay(1000);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  mqttClient.setServer(server, port); 
  connAlibabaIoT();
}

void loop() {
// publishするタイミングを確認するためにLチカ。不要。
  digitalWrite(pin, HIGH);   
  delay(1000);                       
  digitalWrite(pin, LOW);    voltage LOW
  delay(1000);                       

  if (!mqttClient.connected()) {
        connAlibabaIoT();
  }
  mqttClient.loop();
    ++cnt;
    snprintf (msg, 50, "{id:\"%05d\",data:\"hello world\"}", cnt);
    Serial.print("Publish message: ");
    Serial.println(msg);
    mqttClient.publish(mqtttopic, msg);
}

ヘッダーファイルのMQTT_KEEPALIVEの値をデフォルトの15から30に変える。

libraries\PubSubClient\src\PubSubClient.h
#ifndef MQTT_KEEPALIVE
#define MQTT_KEEPALIVE 30
#endif

結果

無事データがTable Storeへ格納されることを確認
*publishしたデータをTable Storeに格納するまでの方法はここでは割愛。

 その他

他のハマりどころ

Arduino IDEの書き込みスピード(serial portのUpLoad speed)が初期値で512000になってたけど115200ぐらいまで落とさないと書き込みに失敗する。私の環境だけかも。

参考文献

https://qiita.com/networkyohan69/items/4f028500d520dbf14be1
https://pubsubclient.knolleary.net/api.html

注意

これは私の趣味の世界です。所属する団体の考え方は全く反映していません。