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


# 背景
届いてから何ヶ月も開封していなかったESP32をいじって見よう、Arduino Core使えるから楽だよね、とりあえずAlibaba Cloud IoT PlatformへMQTT接続してpublishして遊ぼう、とやってみたら休日１日潰すレベルでハマったので忘備録。
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 
![image.png](https://qiita-image-store.s3.amazonaws.com/0/132165/ad45dd0a-0371-56e4-c725-d35b12ac0f33.png)


### 2. deviceName / 3. deviceSecret
![image.png](https://qiita-image-store.s3.amazonaws.com/0/132165/c58e6903-9e87-f888-15e1-60d4e0fb20f0.png)


### 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

**ツールイメージ**
![image.png](https://qiita-image-store.s3.amazonaws.com/0/132165/85e39e23-7a7d-340b-faab-0b72520d4a79.png)

またはこちらを参照して生成
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
![image.png](https://qiita-image-store.s3.amazonaws.com/0/132165/f5bd890e-b5ad-c5ce-ca93-60ee10b8670f.png)

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
![image.png](https://qiita-image-store.s3.amazonaws.com/0/132165/39ae9ca8-312e-c0fd-9705-f7a421879462.png)

### ソースコード
```c++
#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に変える。

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

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

![image.png](https://qiita-image-store.s3.amazonaws.com/0/132165/541bbec0-eb75-0874-d02c-2da389a720f7.png)

#　その他
## 他のハマりどころ
Arduino IDEの書き込みスピード(serial portのUpLoad speed)が初期値で512000になってたけど115200ぐらいまで落とさないと書き込みに失敗する。私の環境だけかも。
## 参考文献
https://qiita.com/networkyohan69/items/4f028500d520dbf14be1
https://pubsubclient.knolleary.net/api.html
## 注意
これは私の趣味の世界です。所属する団体の考え方は全く反映していません。