見出し画像

ベランダー向け多肉植物ネームプレート管理プロジェクト #2 -ESP32 開発環境-

ベランダー向け多肉植物ネームプレート管理プロジェクト
#1

この記事ではダイレクトに構想に対する成果物を目指すため、Lチカみたいに基礎的すぎて"で?何?"ということは取り扱いません
(あれはあれで後々パイロットランプ実装やHIGH/LOW制御に役立つため否定はしてません)

やりたいこと

ESP32 Devkit make環境セットアップ
bleブラウジングiOSアプリ セットアップ

前提とする開発環境

- MacBookPro 2020 11.0.1 Beta(20B5012d)
- atom editor
- ESP32 devkit ボード
      - 評価に32S, 32D何パターンか取り寄せてはみたけど、ble等の複数接続テストに使ってるのは 主にこれ (ESP32S)
- micropython
  本体
      - src tag v1.13使用
      - doc en ja 
- esp-idf   
  toolchain, ESPIDF_SUPHASH_V4 使用
- pip で先に入れておくもの
      - esptool
        arduinoIDEを使うならいらない?cliでbuild環境をソースからカスタマイズするなら必須
      - ampy
         ESP32 flashにファイル転送したり、usbからmac上のpythonコードを直接走らせるの転送しなくていいので便利、ボードのREPLを使っているらしい
         pipで入れる場合は pip install adafruit-ampy

作業に入る前に引っかかった前提

(いまメイン環境をたまたまBigsur評価にあててしまったので仕方なく。製品版なら気にしなくてたぶん大丈夫な話)
作業に入る前にBigSurとESP32 devkitをUSBでつないで

$ esptool.py --port /dev/cu.usbserial-0001 erase_flash

とかしても
"ValueError: dlsym(RTLD_DEFAULT, kIOMasterPortDefault): symbol not found"
とpyserialがエラー

Traceback (most recent call last):
 File "/usr/local/bin/esptool.py", line 57, in <module>
   import serial.tools.list_ports as list_ports
 File "/usr/local/lib/python3.7/site-packages/serial/tools/list_ports.py", line 29, in <module>
   from serial.tools.list_ports_posix import comports
 File "/usr/local/lib/python3.7/site-packages/serial/tools/list_ports_posix.py", line 31, in <module>
   from serial.tools.list_ports_osx import comports
 File "/usr/local/lib/python3.7/site-packages/serial/tools/list_ports_osx.py", line 32, in <module>
   kIOMasterPortDefault = ctypes.c_void_p.in_dll(iokit, "kIOMasterPortDefault")
ValueError: dlsym(RTLD_DEFAULT, kIOMasterPortDefault): symbol not found

を出して前に進められなかったので、pyserialの動向を調査。

$ pip install pyserial==v3.5b0

コードの差分を見ると、もう一段低いレイヤでfixするのがいいんじゃないかなぁと思いつつ、僕にどうこうできる仕事ではないので次へ...venvを利用する場合はactivateしてからpipでバージョンを合わせることを忘れずに
この時点で、(できなかないけど)内部で使うpythonごと抱え込んでいるarduinoIDEでは余計な仕事が増える(狂おしく面倒くさい)のでシリアルコンソール以外は開発環境から外した

micropython for ESP32 make環境セットアップ

(注) 原則的には
 https://github.com/micropython/micropython/blob/v1.13/README.md およびhttps://docs.espressif.com/projects/esp-idf/en/v4.0.1/get-started/index.html に書いてあることを3ヶ月後の僕が読んで同じところで躓きそうなところの補足や、情報が散らばっていたり長々説明しとるけどつまり言いたいことは何?をダイレクトに書き留める以上のものではありませんので、オリジナル読むよって方はオリジナルをあたることをお勧めします。

ゴール
1.のゴールはmicropythonをソースコードからmakeしてみることです

micropythonって何?
一般的にarduinoではCでコードを組み立てていくのが一般的だけど、micropythonではボード向け具象APIをライブラリに抱えたpython 3.4+3.5のサブセット(つまりPCなんかで3.4フルセットのつもりで書くといざって時にEnumが使えないなどで嵌る kwsk) がPython REPLとして起動すると立ち上がってきてファイル転送してあるコードとちょっとしたものであればシリアルポート、websock、ble UART経由でその場で実行できる。
pythonをcliのごとく使えるのであれば非常に便利

例えば flash以下でlsがしたい場合、EN押して起動後いきなりシリアルコンソールから"os.listdir()"で見たりできる

MicroPython v1.13-dirty on 2020-10-31; ESP32 module with ESP32
Type "help()" for more information.
>>> import os; os.listdir() 
['ble', 'boot.py', 'main.py']
>>> import os; os.listdir('/ble') 
['advertising.py', 'uart_peripheral.py', 'uart_repl.py']

esp-idfはプロジェクトの概念を持っていて、こちらでプロジェクトを作ればmain以降Cでも構築できるみたいだが、REPLが問題なく動いてパフォーマンスに問題がなければ今のところあまり意味を感じていない。

ディレクトリ構成
micropython をソースからmakeするためgit cloneする場所と、tag合わせのお話

必要なソースコードは2つ micropythonと別に前述esp-idf
|-micropython
|--- port
| ------esp32

|---esp-idf
 ...........
のような構成でも問題はなかったが、
|-micropython
|---port
| ------esp32
|-esp-idf
......
の方が管理はしやすい  

forkするかどうかはお好みで、

$ cd path/to/github/
$ git clone https://github.com/micropython/micropython.git
$ cd micropython
$ git checkout -b V1.13 refs/tags/v1.13
$ git submodule update --init --recursive
$ cd ..
$ git clone https://github.com/espressif/esp-idf.git
$ cd esp-idf
$ git checkout 4c81978a3e2220674a432a588292a4c860eef27b -b ESPIDF_SUPHASH_V4
$ git submodule update --init --recursive​

 をやっておく

チェックアウトしておくesp-idfのコミットハッシュはチェックアウトした"micropython/port/esp32/Makefile" に記述されているESPIDF_SUPHASH_[V3|V4]として定義されているものに合わせておいた方がいいだろう

ちなみにmicropython v1.3の場合は↓のようになっているので

# The git hash of the currently supported ESP IDF version.
# These correspond to v3.3.2 and v4.0.1.
ESPIDF_SUPHASH_V3 := 9e70825d1e1cbf7988cf36981774300066580ea7
ESPIDF_SUPHASH_V4 := 4c81978a3e2220674a432a588292a4c860eef27b

以下V4を選んだので4c81978でチェックアウトしておく

クロスコンパイラ
macOSからESP32へバイナリを流し込むので、当然crossのgccをセットアップする必要がある ArduinoIDEとかはこういうtoolchainが組み込まれてDLできるというものなので、ソースから行くなら必要
ここセットアップしていないと↓のようなエラーが出るのでこいつを目にしたらmpy-cross環境に不備がある

$ pwd
path/to/micropython/ports/esp32
$ make
Use make V=1 or set BUILD_VERBOSE in your environment to increase build verbosity.
Building with ESP IDF v4
** ERROR **
Cannot find C compiler xtensa-esp32-elf-gcc
Add the xtensa toolchain to your PATH. See README.md
Makefile:116: *** C compiler missing.  Stop.

結論から言うとここで(venv使うならここでactivateしておき)

$ cd path/to/esp-idf
$ ./install.sh
....
Collecting pyserial>=3.0
 Using cached pyserial-3.4-py2.py3-none-any.whl (193 kB)
# pyserialが正式最新に入れ直されてしまうので micropython make前に"pip install pyserial==3.50b" をしなおす 
All done! You can now run:

 . ./export.sh
$ source ./export.sh

ここでコンパイルが通って、export.shさえしておけば環境変数類はesspressifの設計通りいいようにやっておいてくれる(らしい)
別shellで環境変数をリセットしても、micropython makeはうまくいったので確度は高いかと。
これで幸せになれた人はmakeしてESP32へ流し込んでみるまで読み飛ばしてOK
どうもうまくいかない人は少し考察を書いたので以下もみてみてください

micropythonの指示に従い
https://github.com/micropython/micropython/blob/v1.13/README.md#the-micropython-cross-compiler-mpy-cross を信じて

$ cd path/to/micropython
$ cd mpy-cross
$ make

をやると確かに~/.esspressifというディレクトリができてxtensaのバイナリが配置されるが、esspressifが期待しているのはたぶんこれじゃない
この時できる生成物をみてみると、xtensa-esp32s2-elfやxtensa-esp32s3-elfはできるけどmicropython/port/esp32/MakefileのCROSS_COMPILEにある接頭辞xtensa-esp32-elf-ができてこない、シンボリックも張られないのでドキュメントが散っていてわかりにくいけど、micropythonではなく、esp-idf/install.sh の方を実行する

多分xtensaでも新しいやつあるいはチップにもっと寄り添ったコンパイラ?を使いたければ、Makefileを編集するか前もってexport CROSS_COMPILE=xtensa-esp32s3-elf-を指定してPATHに出来上がったtoolchainへのパスを追加しておけばいけるのかな?
結論は目指す最終ゴールとはあまり関係してそうにないしesspressifとmicropythonの関係など興味もないので具体的に困ったことが起きるまで塩漬けにする

makeしてESP32へ流し込んでみる
ここまでやっておけば、特段環境変数の調整とかはせずとも

$ cd path/to/micropython
$ make V=1 # バイナリ生成
......
Create build-GENERIC/firmware.bin
python3 makeimg.py build-GENERIC/bootloader.bin build-GENERIC/partitions.bin build-GENERIC/application.bin build-GENERIC/firmware.bin
bootloader     21520
partitions      3072
application  1372224
total        1437760

$ export PORT=/dev/cu.usbserial-0001 # ESP32が繋がっているシリアルポートを指定 Makefileを書き換えてもいいがそれもまためんどくさい
$ make deploy # 別にシリアルコンソール等/dev/cu.usbserial-0001 を占有しているアプリは全て閉じてから
Use make V=1 or set BUILD_VERBOSE in your environment to increase build verbosity.
Building with ESP IDF v4
Writing build-GENERIC/firmware.bin to the board
esptool.py v2.8
...
Wrote 1433664 bytes (921751 compressed) at 0x00001000 in 23.1 seconds (effective 496.2 kbit/s)...
Hash of data verified.

Leaving...
Hard resetting via RTS pin...
$

ここまでやっておくとENをおせばシリアルコンソールに↓が出てきてREPLが起動する

ets Jun  8 2016 00:22:57

rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0018,len:4
load:0x3fff001c,len:5084
load:0x40078000,len:12856
load:0x40080400,len:3480
entry 0x40080638
[0;32mI (538) cpu_start: Pro cpu up.[0m
[0;32mI (538) cpu_start: Application information:[0m
[0;32mI (538) cpu_start: Compile time:     Nov  1 2020 16:47:50[0m
[0;32mI (541) cpu_start: ELF file SHA256:  0000000000000000...[0m
[0;32mI (547) cpu_start: ESP-IDF:          v4.0.1[0m
[0;32mI (552) cpu_start: Starting app cpu, entry point is 0x40082888[0m
[0;32mI (0) cpu_start: App cpu up.[0m
[0;32mI (563) heap_init: Initializing. RAM available for dynamic allocation:[0m
[0;32mI (569) heap_init: At 3FFAFF10 len 000000F0 (0 KiB): DRAM[0m
[0;32mI (575) heap_init: At 3FFB6388 len 00001C78 (7 KiB): DRAM[0m
[0;32mI (581) heap_init: At 3FFB9A20 len 00004108 (16 KiB): DRAM[0m
[0;32mI (588) heap_init: At 3FFBDB5C len 00000004 (0 KiB): DRAM[0m
[0;32mI (594) heap_init: At 3FFCAAE8 len 00015518 (85 KiB): DRAM[0m
[0;32mI (600) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM[0m
[0;32mI (606) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM[0m
[0;32mI (613) heap_init: At 4009EA50 len 000015B0 (5 KiB): IRAM[0m
[0;32mI (619) cpu_start: Pro cpu start user code[0m
[0;32mI (637) spi_flash: detected chip: generic[0m
[0;32mI (638) spi_flash: flash io: dio[0m
[0;32mI (638) cpu_start: Starting scheduler on PRO CPU.[0m
[0;32mI (0) cpu_start: Starting scheduler on APP CPU.[0m
main has run
[0;32mI (410) BTDM_INIT: BT controller compile version [c1cbe45][0m
[0;32mI (1482) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE[0m
[0;32mI (1582) phy: phy_version: 4180, cb3948e, Sep 12 2019, 16:39:13, 0, 0[0m
GAP procedure initiated: advertise; disc_mode=2 adv_channel_map=7 own_addr_type=0 adv_filter_policy=0 adv_itvl_min=800 adv_itvl_max=800
power on or hard reset
MicroPython v1.13-dirty on 2020-11-01; ESP32 module with ESP32
Type "help()" for more information.

ちなみに"MicroPython v1.13-dirty" の部分は、micropython clone時に指定したtagが反映される。micropythonはリリース気にせずmasterでもここの印字内容が違うだけで普通に立ち上がった。

ちなみのちなみに、deploy以外にmakeで使ったのは
clean: いわゆるclean  開発環境側のbuild-GENERIC ごと消しちゃう
erase: 自分で作ったライブラリ等、ボード側flashに送ったファイルを全消ししてくれる。/boot.pyも消すので起動するなら要 make deploy
正体は

	esptool.py --chip esp32 --port $PORT erase_flash

なのでマニュアルでやってもいいくらい

flashへファイル転送 pythonで開発

ここまでセットアップできたらあとは半田付けとコードを書くのみなのだけど、flashにファイルを転送しないと再起動しても全てを忘れてREPLが立ち上がるだけで何もできない。


IoTでよくある、せいぜいLチカをやったくらいで、前提知識の枯渇と設計者、vendor意図の汲み取りができず何をググればわからないところからミッシングリンクにぶつかり、せっかく買ったボードやコードや部材が途端に酸っぱい葡萄になる現象が起こりやすいところだと思う
ちな、基本python3コードと同じノリでコンソールから入力すれば、普通に実行できてただただつまらないだけなのでこの記事でそう言うのは割愛

試しにble
ここからのやりたいこと設定は、
1. ESP32に必要なファイルを転送する
2. 転送したファイルからbleでのUART REPLサービスを立ち上げ
3. 再起動をかけても毎回サービスが立ち上がってくるようにしてみる
    3.1 iOSで今動かしているボードが拾えてるか確認

micropythonには編集するか、書かれている通りに回路を結線すればすぐに使えるサンプルがたくさん用意されていて、その存在に気づかなければ今まさにSSDの肥やしになりかけている
例えばここではmicropython/example/bluetoothを使ってみた

ファイル転送、実行に必要なツール
とりあえずmacに繋がっているシリアルコンソールから転送するのであればampyがわかりやすくて扱いやすいと思う
インストールはpython3で

$ pip3 install adafruit-ampy

とかしておくだけ。結局はREPLらしいのでpythonコードをサブコマンドに紐つけたaliasか?

正常にインストールされたらサブコマンド形式のプログラムでコマンドが
Commands:
get Retrieve a file from the board.
ls List contents of a directory on the board.
mkdir Create a directory on the board.
put Put a file or folder and its contents on the board.
reset Perform soft reset/reboot of the board.
rm Remove a file from the board.
rmdir Forcefully remove a folder and all its children from the board.
run Run a script and print its output.
とftpライクのコマンドが用意されている
例えば具体的に、 

$ ampy --port $PORT ls  # PORTは'make deploy'時に設定したもの
/boot.py
$ cd path/to/python/prj # example/bluetoothをコピった場所 idf.pyのようなprj.形式になっている必要はなし
$ ampy -p $PORT mkdir ble
​


UART立ち上げに必要なファイル
ディスカバリができてUARTでREPLサービスとして機能させるには
"ble_advertising.py", "ble_uart_peripheral.py", "ble_uart_repl.py"

ここではbleでまとめておきたいのでble/ 以下にまとめて、各ファイル相互importしているところを
"from ble_uart_peripheral import BLEUART" -> "from ble.uart_peripheral import BLEUART"とした以外は無編集で放り込む
平で / 以下に並べるなら本当に無編集でOKのはず

$ ampy -p $PORT put ./ble_advertising.py /ble/advertising.py
$ ampy -p $PORT put ./ble_uart_peripheral.py /ble/uart_peripheral.py
$ ampy -p $PORT put ./ble_uart_repl.py /ble/uart_repl.py
$ ampy -p $PORT ls
/ble
/boot.py
$ ampy -p $PORT ls /ble
/ble/advertising.py
/ble/uart_peripheral.py
/ble/uart_repl.py

ここまでできたら、次のコマンドをシリアルコンソールでREPLに入力する

exec(open('/ble/uart_repl.py').read())
>>> import ble.uart_repl as ble_uart
>>> ble.uart_repl.start()
[0;32mI (46160) BTDM_INIT: BT controller compile version [c1cbe45][0m
[0;32mI (66137) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE[0m
[0;33mW (66137) phy_init: failed to load RF calibration data (0x1102), falling back to full calibration[0m
[0;32mI (66287) phy: phy_version: 4180, cb3948e, Sep 12 2019, 16:39:13, 0, 2[0m
GAP procedure initiated: advertise; disc_mode=2 adv_channel_map=7 own_addr_type=0 adv_filter_policy=0 adv_itvl_min=800 adv_itvl_max=800
>>> 

てな感じで起動するが、このままではiOSのbluetooth設定にも出てこないので何も起きていないように見えるのでブラウジングできるアプリをXcode12で入れてみた https://github.com/adafruit/Bluefruit_LE_Connect_v2にあるので、各自入れてみて欲しい
それなりに完成度は高いのでAdafruitに足向けて寝れなくなる。

と、その前に、iOSアプリのexampleなんていらねーよXcodeなんて起動したこともねーよというヒトはAppStoreにも多分同じものがリリースされていたのでちょっとみるにはこれで十分かもしれないのでここは読み飛ばして"ble UARTのためのREPLアプリ設定"へ進んでOK。

cloneしてビルドを通すためにいじったところといえば

スクリーンショット 2020-11-01 23.41.17

と、Signing & Capabilitiesで自分のdeveloper アカウントに紐つけたくらいですんなり動いた
ESP32あるいはmpy-replという名前でボードが出てくる
advertise.pyの中身はまだよくみていないので、なぜそうなるかは知らない。

ファイル 2020-11-01 23 51 37

シミュレータでビルドしたらclangがシンボリックエラーを出したおしてbuild failure吐いていたのでアカウントの紐付けが必須かどうかはわからないがmacOSでもiPad版と見た目同じものが動いたのでなんとかなるのかもしれない Catalystの恩恵か?
テストするには便利そう

スクリーンショット 2020-11-02 0.03.43

スクリーンショット 2020-11-02 0.08.29

ble UARTのためのREPLアプリ設定
ただし、REPLを動かすなら入力行EoLを\nから\n\rに変更しないといけないのだけど、iPad版はiPhone版のような設定変更UIが見当たらず、今のところiPad版の方は使い物になっていない

ファイル 2020-11-02 0 18 12

これだけやっておけばシリアルコンソールと同じ感じで普通に使える

ファイル 2020-11-02 0 25 05

usbレスでble自動起動設定

要は上記でuart_repl.pyを読み込んだ後、

>>> import ble.uart_repl as ble_uart
>>> ble.uart_repl.start()

を実行しないとサービスは立ち上がってくれない仕様になっていると毎回usbに繋いでやらないと使い物にならないので、micropythonの仕様にあるmain.pyの仕組みを利用する

こんなファイルを用意して、これをflash直下におけば再起動した時勝手に実行しておいてくれる

$ ampy -p $PORT put ./main.py /main.py

これだけでバッテリだけ繋いで起動してもiPhoneから繋いでUARTでREPLに入力できるようになる

この記事が気に入ったらサポートをしてみませんか?