背景
届いてから何ヶ月も開封していなかった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コードを書く上で必要な各パラメータを生成する。
- ProductKey
- deviceName
- deviceSecret
- clientId
- 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つのパラメータから合成する。
- Broker Address
- ClientID(MQTTプロトコル上の)
- UserName
- 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に変える。
#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
注意
これは私の趣味の世界です。所属する団体の考え方は全く反映していません。