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


背景

先日はArduino CoreにてESP32をAlibaba CloudのIoT platformへMQTT接続するところまでをやってみて"ESP32 〜 Alibaba Cloud IoT Platform をMQTT接続"に書きました。でもAlibaba CloudのドキュメントサイトをみるとESP-WROOMを作ってるEspressif Systemsさんがesp-aliyunなるvender SDKをgithubにdemo projectとしてあげているようです。

https://github.com/espressif/esp-aliyun/

githubへ行くとそれらしいのがありますが、READMEは中国語。Espressifさんも中国の会社だしおそらく中国アカウントでの利用を前提としてるのでしょうけど、同じ仕様でしょうから日本のアカウントでも使えないこともないでしょう。
開発環境はLinuxを前提としてるようですけど、OSXにもESPのtoolchainやSDKありますし、動かないこともないでしょう。
ということで、MACでMQTTのサンプルコードで日本リージョンのIoT Platformに接続するところまでチャレンジしてみました。

環境準備

必要なソフトウエアをインストール

pythonからserial portを使うためにpyserialと、makeが使用するgsedコマンドをMacへインストールします。

$ sudo easy_install pip
$ sudo pip install pyserial
$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
$ brew install gnu-sed

ESP toolchainとesp-idf, AliyunのSDKをインストール

ESP32 toolchainをダウンロードします。
https://dl.espressif.com/dl/xtensa-esp32-elf-osx-1.22.0-73-ge28a011-5.2.0.tar.gz

mkdir -p ~/esp
cd ~/esp
tar -xzf ~/Downloads/xtensa-esp32-elf-osx-1.22.0-73-ge28a011-5.2.0.tar.gz
cd

toolchainへのpathと、ついでなのでesp-idfへのpathも環境変数に書いておきます。

~/.bash_profile
export PATH=$PATH:$HOME/esp/xtensa-esp32-elf/bin
export IDF_PATH=$HOME/esp/esp-idf

ESP-IDFとAliyun SDKのインストール

cd ~/esp
$ sudo git clone --recursive https://github.com/espressif/esp-idf.git
$ sudo git clone --recursive https://github.com/espressif/esp-aliyun.git

なぜかarchiverへのパスが通らないので~/esp/esp-aliyun/components/aliyun/iotkit-embedded/build-rules/_rules-complib.mkファイルを開いて、$(AR)を直打ちのxtensa-esp32-elf-ar に変更します。もともと開発環境としてLinuxが想定されているところを無理やりOSXでやろうとしているからかな?

修正前

define Finalize_CompLib
(
    EXIST_OBJS="$$(ls $(1) 2>/dev/null)";

    if [ "$${EXIST_OBJS}" != "" ]; then 
        $(AR) -rcs $(2)/lib$(3).a $${EXIST_OBJS};
    fi
)
endef

修正後

define Finalize_CompLib
(
        EXIST_OBJS="$$(ls $(1) 2>/dev/null)";

        if [ "$${EXIST_OBJS}" != "" ]; then 
            xtensa-esp32-elf-ar -rcs $(2)/lib$(3).a $${EXIST_OBJS}; 
        fi 
)
endef

デフォルトでMQTT brokerが上海リージョンになるようドメイン名が記号定数にdefineされてしまっています。configurableにして欲しいのですが、とりあえず日本リージョンを使う場合は日本のドメイン名になるように~/esp/esp-aliyun/components/aliyun/iotkit-embedded.pkgs/iotkit-system/guider_internal.hを直接変更します。ドメイン名に含まれるcn-shanghaiが上海のリージョンIDでここを日本のリージョンIDであるap-northeast-1に書き換えます。

変更前
#define GUIDER_DIRECT_DOMAIN        "iot-as-mqtt.cn-shanghai.aliyuncs.com"
変更後
#define GUIDER_DIRECT_DOMAIN        "iot-as-mqtt.ap-northeast-1.aliyuncs.com"

menuconfigへ

$ cd esp-aliyun/examples/mqtt
$ make menuconfig

DemoConfigurationWifiのSSID/Password,それからAlibaba CloudのIoT platformのProduct keydevice namedevice secret を設定し、Serial Flasher configDefault Serial portでシリアルポートを設定する。/dev/tty.XXXXみたいなUSBでESP32を認識したデバイス名を記載する。
Alibaba Cloud IoT platformのパラメータの確認方法は"ESP32 〜 Alibaba Cloud IoT Platform をMQTT接続"を参照。

ビルド

$ make aliyun && make

Python関連のバージョンが低いと怒られたら下記を実行して満たしてあげます。

$ pip install --user -r ~/esp/esp-idf/requirements.txt
$ make clean
$ make aliyun && make

接続確認

ビルドできたらESP32に直接書き込んで接続テストしてみる。

make flash
make monitor

うまく設定されていればESP32がMQTTでpublishを始めるはずです。toolchainのバージョンが低いと怒られていることはとりあえず気にしない事に。

$ make monitor
WARNING: Toolchain version is not supported: 1.22.0-73-ge28a011
Expected to see version: 1.22.0-80-g6c4433a
Please check ESP-IDF setup instructions and update the toolchain, or proceed at your own risk.
Python requirements from /Users/makotaka/esp/esp-idf/requirements.txt are satisfied.
MONITOR
--- WARNING: Serial ports accessed as /dev/tty.* will hang gdb if launched.
--- Using /dev/cu.wchusbserial1410 instead...
--- idf_monitor on /dev/cu.wchusbserial1410 115200 ---
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
e?.Rչ  ??01??Ғ2:MSH?HH??i0x??POU??9_RE?U?I???i0x?&B?AI_FP*?e1M!?BO?U?Hh????gsi? 0 ?A%?P:???5
clkE???0x0?q_?.???0??E??:0x?,c.????:0??bB?_d???0,w}???:0x?j
틕?DIO,????k dZ??j
loᖂ?Mfff?18?+??4
?녑i0x???‚?0,???9392?!?+?:0??‚?00?+??712?C?????0x40?07Xj
I (28) boot: ESP-IDF v3.2-dev-1055-g3276a1316 2nd stage bootloader
I (28) boot: compile time 22:37:15
I (28) boot: Enabling RNG early entropy source...
I (34) boot: SPI Speed      : 40MHz
I (38) boot: SPI Mode       : DIO
I (42) boot: SPI Flash Size : 4MB
I (46) boot: Partition Table:
I (50) boot: ## Label            Usage          Type ST Offset   Length
I (57) boot:  0 nvs              WiFi data        01 02 00009000 00006000
I (65) boot:  1 phy_init         RF data          01 01 0000f000 00001000
I (72) boot:  2 factory          factory app      00 00 00010000 00100000
I (80) boot: End of partition table
I (84) esp_image: segment 0: paddr=0x00010020 vaddr=0x3f400020 size=0x1f534 (128308) map
I (138) esp_image: segment 1: paddr=0x0002f55c vaddr=0x3ffb0000 size=0x00ab4 (  2740) load
I (139) esp_image: segment 2: paddr=0x00030018 vaddr=0x400d0018 size=0x8c480 (574592) map
0x400d0018: _stext at ??:?

I (346) esp_image: segment 3: paddr=0x000bc4a0 vaddr=0x3ffb0ab4 size=0x02954 ( 10580) load
I (350) esp_image: segment 4: paddr=0x000bedfc vaddr=0x3ffb3408 size=0x00000 (     0) load
I (352) esp_image: segment 5: paddr=0x000bee04 vaddr=0x40080000 size=0x00400 (  1024) load
0x40080000: _WindowOverflow4 at /Users/makotaka/esp/esp-idf/components/freertos/xtensa_vectors.S:1685

I (362) esp_image: segment 6: paddr=0x000bf20c vaddr=0x40080400 size=0x104b4 ( 66740) load
I (398) esp_image: segment 7: paddr=0x000cf6c8 vaddr=0x400c0000 size=0x00000 (     0) load
I (398) esp_image: segment 8: paddr=0x000cf6d0 vaddr=0x50000000 size=0x00000 (     0) load
I (415) boot: Loaded app from partition at offset 0x10000
I (415) boot: Disabling RNG early entropy source...
I (416) cpu_start: Pro cpu up.
I (420) cpu_start: Starting app cpu, entry point is 0x40080ff8
0x40080ff8: call_start_cpu1 at /Users/makotaka/esp/esp-idf/components/esp32/cpu_start.c:228

I (0) cpu_start: App cpu up.
I (430) heap_init: Initializing. RAM available for dynamic allocation:
I (437) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM
I (443) heap_init: At 3FFB9D68 len 00026298 (152 KiB): DRAM
I (450) heap_init: At 3FFE0440 len 00003BC0 (14 KiB): D/IRAM
I (456) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (462) heap_init: At 400908B4 len 0000F74C (61 KiB): IRAM
I (469) cpu_start: Pro cpu start user code
I (151) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
I (317) wifi: wifi driver task: 3ffc188c, prio:23, stack:3584, core=0
I (317) wifi: wifi firmware version: d8b211c
I (317) wifi: config NVS flash: enabled
I (317) wifi: config nano formating: disabled
I (327) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
I (337) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
I (367) wifi: Init dynamic tx buffer num: 32
I (367) wifi: Init data frame dynamic rx buffer num: 32
I (367) wifi: Init management frame dynamic rx buffer num: 32
I (367) wifi: Init static rx buffer size: 1600
I (377) wifi: Init static rx buffer num: 10
I (377) wifi: Init dynamic rx buffer num: 32
I (377) MQTT: Setting WiFi configuration SSID bluesky...
I (467) phy: phy_version: 4000, b6198fa, Sep  3 2018, 15:11:06, 0, 0
I (477) wifi: mode : sta (30:ae:a4:15:fe:f0)
I (477) MQTT: MQTT task started...
I (837) wifi: n:3 0, o:1 0, ap:255 255, sta:3 0, prof:1
I (1817) wifi: state: init -> auth (b0)
I (1827) wifi: state: auth -> assoc (0)
I (1837) wifi: state: assoc -> run (10)
I (2267) wifi: connected with bluesky, channel 3
I (2267) wifi: pm start, type: 1

I (4317) event: sta ip: 172.16.81.142, mask: 255.255.192.0, gw: 172.16.64.1
I (4317) MQTT: MQTT client example begin, free heap size:229244
[inf] iotx_device_info_init(40): device_info created successfully!
[dbg] iotx_device_info_set(50): start to set device info!
[dbg] iotx_device_info_set(64): device_info set successfully!
[dbg] guider_print_dev_guider_info(256): ....................................................
[dbg] guider_print_dev_guider_info(257):           ProductKey : XXXXXXXXXXX
[dbg] guider_print_dev_guider_info(258):           DeviceName : testdev2
[dbg] guider_print_dev_guider_info(259):             DeviceID : XXXXXXXXXXX.testdev2
[dbg] guider_print_dev_guider_info(261): ....................................................
[dbg] guider_print_dev_guider_info(262):        PartnerID Buf : ,partner_id=espressif
[dbg] guider_print_dev_guider_info(263):         ModuleID Buf : ,module_id=wroom-32
[dbg] guider_print_dev_guider_info(264):           Guider URL : 
[dbg] guider_print_dev_guider_info(266):       Guider SecMode : 2 (TLS + Direct)
[dbg] guider_print_dev_guider_info(268):     Guider Timestamp : 2524608000000
[dbg] guider_print_dev_guider_info(269): ....................................................
[dbg] guider_print_dev_guider_info(275): ....................................................
[dbg] guider_print_conn_info(233): -----------------------------------------
[dbg] guider_print_conn_info(234):             Host : XXXXXXXXXX.iot-as-mqtt.ap-northeast-1.aliyuncs.com
[dbg] guider_print_conn_info(235):             Port : 1883
[dbg] guider_print_conn_info(238):         ClientID : XXXXXXXXXXX.testdev2|securemode=2,timestamp=2524608000000,signmethod=hmacsha1,gw=0,ext=0,partner_id=espressif,module_id=wroom-32|
[dbg] guider_print_conn_info(240):       TLS PubKey : 0x3f402758 ('-----BEGIN CERTI ...')
[dbg] guider_print_conn_info(243): -----------------------------------------
[inf] iotx_mc_init(1703): MQTT init success!
[inf] _ssl_client_init(175): Loading the CA root certificate ...
[inf] _ssl_parse_crt(142): crt content:2
[inf] _ssl_client_init(183):  ok (0 skipped)
[inf] _TLSConnectNetwork(345): Connecting to /XXXXXXXXXX.iot-as-mqtt.ap-northeast-1.aliyuncs.com/1883...
[inf] _TLSConnectNetwork(358):  ok
[inf] _TLSConnectNetwork(363):   . Setting up the SSL/TLS structure...
[inf] _TLSConnectNetwork(373):  ok
[inf] _TLSConnectNetwork(410): Performing the SSL/TLS handshake...
[inf] _TLSConnectNetwork(418):  ok
[inf] _TLSConnectNetwork(422):   . Verifying peer X.509 certificate..
[inf] _real_confirm(81): certificate verification result: 0x00
[inf] iotx_mc_connect(2035): mqtt connect success!
[dbg] iotx_mc_report_mid(2259): MID Report: started in MQTT
[dbg] iotx_mc_report_mid(2276): MID Report: json data = '{"id":"XXXXXXXXXX_testdev2_mid","params":{"_sys_device_mid":"wroom-32","_sys_device_pid":"espressif"}}'
[dbg] iotx_mc_report_mid(2292): MID Report: topic name = '/sys/XXXXXXXXXX/testdev2/thing/status/update'
[dbg] iotx_mc_report_mid(2309): MID Report: finished, IOT_MQTT_Publish() = 0
mqtt_client|245 :: 
 publish message: 
 topic: /XXXXXXXXX/testdev2/update
 payload: update: hello! start!
 rc = 1
[inf] iotx_mc_subscribe(1388): mqtt subscribe success,topic = /XXXXXXXXXX/testdev2/data!
mqtt_client|267 :: 
 publish message: 
 topic: /XXXXXXXXXXXX/testdev2/data
 payload: data: hello! start!
 rc = 3

Alibaba Cloud側の設定は省略するけど、きちんとpublishを受けるには少なくとも/[ProductKey]/[DeviceName]/dataというtopicが必要です。

参考文献

https://jp.alibabacloud.com/help/doc-detail/42648.htm?spm=a21mg.p38356.b99.59.590f435awKQQRh
https://qiita.com/tyone/items/de413db7763826d4fc02
https://github.com/espressif/esp-aliyun/issues/65

まとめ

まず、当方素人です。間違ってたらツッコミ下さい。
それから、「Arduino CoreではESP32のマルチコアを使いこなせない」と勘違いしてFreeRTOSやろう、一旦準公式のコードをチェックしてみよう、というモチベーションで始めてみました。実務で使うならやっぱりEspressifさん推奨のOpenRTOSベースのSDK使うべきなんでしょね。
でもArduinoでも実はマルチコア使えるっぽいでエスし、ちょっと遊ぶにはやっぱりArduinoがお手軽で好きかな〜。microPythonとかも今度やってみよう。

EspressifさんのESP32をAliexpressで購入してAlibabaCloudへ。中国一色ですねw

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