その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

ESP32でZephyrを開発する環境を整える - macOS編

ZephyrはApache Lisence, Version 2.0で公開されているRTOSです.多分「ゼファー」と読むはず.
https://www.zephyrproject.org/

このZephyrをESP32のボードで開発する環境をmacOS上に整えるというのがこの記事の目的です.
なおESP32開発ボードは今回以下を使いました: http://akizukidenshi.com/catalog/g/gM-11819/

基本的にZephyrの以下のドキュメントに従うとできます:

http://docs.zephyrproject.org/getting_started/installation_mac.html
http://docs.zephyrproject.org/boards/xtensa/esp32/doc/esp32.html

事前準備
$ brew install direnv cmake ninja dfu-util doxygen qemu dtc python3 gperf

開発環境が問答無用でSystemのPython3を見てくるのでインストールする必要があります.direnvはあるとなにかと便利なので入れています.

(上記開発ボードを使っている場合) CP210x USB to UART Bridge VCP driversを入れる

https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers

もしもmacOS High Sierra以降を使っている場合は Security & Privacy でインストール時にBlockしているソフトウェアベンダーをAllowする必要があります (Silicon Laboratoriesとか).
これがないと開発ボードがシリアルデバイスとして認識されない.

Espressif Toolchainを入れる
$ mkdir -p ~/opt/esp
$ cd ~/opt/esp
$ curl -O https://dl.espressif.com/dl/xtensa-esp32-elf-osx-1.22.0-80-g6c4433a-5.2.0.tar.gz
$ tar zxf xtensa-esp32-elf-osx-1.22.0-80-g6c4433a-5.2.0.tar.gz

xtensa-esp32-elf-osxのバージョンについては実際どこを見れば最新なのかがわからなかった (ダウンロード可能なバージョン一覧が取れるページを見つけられなかった) ので,雑に調べて出てきたものを使っています.
確証はありませんが,
arduino-esp32/package_esp32_index.template.json at master · espressif/arduino-esp32 · GitHub
の中身に書いているやつを使っておけば良いような気がしています (espressifの公式リポジトリだし).

ESP32 IDFを入れる

要はRTOS SDKです.

$ git clone --recursive https://github.com/espressif/esp-idf.git
$ cd esp-idf
$ git checkout dc8c33892e0

espressifのリポジトリからESP32 IDFをcloneしてきます.
加えてZephyrのドキュメントにあったので,IDFのリビジョンを過去のものに固定しています.

Since ESP-IDF is an external project in constant development, it’s possible that files that Zephyr depends on will be moved, removed, or renamed. Those files are mostly header files containing hardware definitions, which are unlikely to change and require fixes from the vendor. In addition to setting the environment variables above, also check out an earlier version of ESP-IDF
http://docs.zephyrproject.org/boards/xtensa/esp32/doc/esp32.html

Zephyrをダウンロードして諸々の開発環境を整える
$ git clone git@github.com:zephyrproject-rtos/zephyr.git /path/to/zephyr
$ cd /path/to/zephyr
$ pip3 install --user -r scripts/requirements.txt
$ cat <<EOS >> .envrc

export PATH="$PATH:$HOME/opt/esp/xtensa-esp32-elf/bin
export ZEPHYR_TOOLCHAIN_VARIANT="espressif"
export ESP_IDF_PATH="$HOME/opt/esp/esp-idf"
export ESPRESSIF_TOOLCHAIN_PATH="$HOME/opt/esp/xtensa-esp32-elf/"

source ./zephyr-env.sh
export ESP_DEVICE="/dev/cu.blahblah" # ここは開発ボードによって変わるでしょう
export ESP_BAUD_RATE=115200 # ここは開発ボードによって変わるでしょう
export ESP_FLASH_SIZE="detect"
export ESP_FLASH_FREQ="40m"
export ESP_FLASH_MODE="dio"

EOS
$ direnv allow

ZephyrはGitHubで公開されているので普通にgit cloneしてくれば良いです.
次にpythonで書かれたツールを動かすための依存ライブラリをインストールします.このコマンドではSystem python以下に依存ライブラリが入ります (system pythonを使わずに済ます方法は無いものか……).
あとは諸々の環境変数を.envrcに書き込んでdirenvでそれらを有効にしてやる.

Hello-Worldを動かしてみる
$ cd "$ZEPHYR_BASE/samples/hello_world"
$ mkdir build && cd build
$ cmake -DESP_IDF_PATH="$ESP_IDF_PATH" -GNinja -DBOARD=esp32 ..
$ west flash \
   --esp-device="$ESP_DEVICE" \
   --esp-baud-rate="$ESP_BAUD_RATE" \
   --esp-flash-size="$ESP_FLASH_SIZE" \
   --esp-flash-freq="$ESP_FLASH_FREQ" \
   --esp-flash-mode="$ESP_FLASH_MODE"
$ screen $ESP_DEVICE $ESP_BAUD_RATE # and push reset button

以上のようにコマンドを叩くとプログラムのビルドとボードへの書き込みを行うことができます.
公式ドキュメントでは ESP_DEVICEESP_BAUD_RATE といった環境変数を設定しておくと ninja flash コマンドはそれらをリスペクトするように書いてあるが,実際にそのような処理が行われている気配はない (ざっとコードを読んだ) ので, ninja コマンドの代わりに west を利用し,そのコマンドラインオプションで各種設定を与えるようにしています.
あとはscreenでTTYにつないでボードのリセットボタンを押すと "Hello World: esp32" が表示されるはずです!


以上です.これでmacOS上でESP32向けのZephyrプログラムが開発できます.やりましたね.

expectコマンドを使ってシリアル接続で非対話的にコマンドを流し込む

シリアル接続でコマンドを実行する際は cu コマンドや screen コマンドなどでシリアルコンソールに接続して対話的にコマンドを実行することになると思うんですが,しかし「一定周期ごとにシリアルでコマンドを実行して結果を取得したい」みたいなときに対話モードだとだるいわけです.
というわけで expect の出番です.以下のように expect コマンドを利用すると非対話的にシリアルにコマンドを流し込むことができます. expect コマンドについてはこちらを参照されたい:
expect(1) - Linux man page

expect -c "
  spawn cu -l ${TTY} -s 9600
  send \"AT+CGREG=2\\n\"
  expect \"OK\"
  send \"AT+CGREG?\\n\"
  expect \"+CGREG:\"
  exit 0
"

これは

soracom.zendesk.com

にあるLACとCIDを取得するATコマンドの例ですが,こうしてやることで非対話的にATコマンドを流し込むことができます.

while :
do
  RESULT="$(expect -c "
    spawn cu -l ${TTY} -s 9600
    send \"AT+CGREG=2\\n\"
    expect \"OK\"
    send \"AT+CGREG?\\n\"
    expect \"+CGREG:\"
    exit 0
  " | grep '^+CGREG:')"

  PCRE_LAC_CID_PATTERN='"(.+?)",\s*"(.+?)"'
  LAC="$(echo "$RESULT" | perl -ale "/${PCRE_LAC_CID_PATTERN}/; print \$1")"
  CID="$(echo "$RESULT" | perl -ale "/${PCRE_LAC_CID_PATTERN}/; print \$2")"

  echo "{\"LAC\":$LAC, \"CID\":$CID}"

  sleep 60
done

例えばこうしてやると1分ごとにcell locationが取れて便利.expect コマンド最高!!!! (しかし難しい)

C言語のジェネリクスサポート

全く知らなかったのだけれど,C11の新機能として_Genericという組み込み関数が提供されていた.
Generic selection - cppreference.com


jameshfisher.com

というのをこのブログを見て気づいたんですが,

#include <stdio.h>
int main() {
  char* x = "foo";
  printf("Type of x is: %s\n", _Generic(x, char*: "string", int: "int"));
  return 0;
}

これは完全に動くコード (最初よく文章を読んでなくて,大方擬似コードだろうと侮っていたら本当に動いて驚いた).
だいたい分かると思うんですが,上記のコードは char*の引数が_Genericに渡されるとstringという文字列を出力し,intの引数が与えられるとintという文字列が出力されるという挙動をします.

しかしこれが「リッチなgenericsか」というとそうではなくて,_Genericの引数に延々と型とそれに対応するvalueを書いていかなければならないので,どちらかと言うとType Assertionに近いイメージ.JavagenericsC++のTemplateのようにコンパイル時に良い感じに自動解決してくれるような類のものではない.惜しいですね.とはいえ便利ではある.

問題としてはC11なんてモダンなものを使う環境ってあるの……? という部分であるが……

Code sampleとしては以下がわかりやすかった: http://www.robertgamble.net/2012/01/c11-generic-selections.html

CircleCIでgradleのテストを走らせているとプロセスが突然死するんですけどって時

CircleCI上でgradleでテストを走らせていると,プロセスが突然死してテストがコケるという事態に見舞われていました.
運が悪いと10連続でfailしたりするなどかなり厳しい状況だったのでCircleCI Supportに泣きついたところ,

  • 症状としてはOOMで死んでるっぽい (なぜかOOM debug logは出ていなかった)
  • gradleは環境変数 JVM_OPTS を尊重しない
  • 一方で _JAVA_OPTIONS 環境変数は優先するので,制限をかけたいときはこれを使うと良い

という術を教えてもらったので,それに従ったところテストが完全に安定しました.
diffとしてはたったのこれだけ!

@@ -7,7 +7,7 @@ 
     working_directory: ~/repo

     environment:
-      JVM_OPTS: "-Xms2048m -Xmx2048m"
+      _JAVA_OPTIONS: "-Xms2048m -Xmx2048m"
       TERM: dumb

     steps:

事象としてはgradleは JVM_OPTS を尊重しないため,JVM_OPTS にいくらヒープ容量の制限を書いてもそれが反映されず,結果的にgradleがメモリを無尽蔵のバカ食いして死んでしまったということのようです.
_JAVA_OPTIONSJVMに直接的にパラメータを食わせることができるので,それを用いるとgradleにもヒープ容量の制限を強く課すことができるようです.

本件についてはCircleCIのこのブログが詳しくて良いです: How to Handle Java OOM Errors - CircleCI
しかし効く環境変数と効かない環境変数があるのはなかなか難しいですね……

arnoldというJavaのbuilderライブラリを書いた

今時builderライブラリも無いだろという感じですが,書いたのです.

github.com

Motivationにも書いているのですが,普通に作ったJavaのbuilderは不完全なインスタンスを作成できる可能性を孕んでいます.注意深くコードを書けば問題ないかもしれませんが,えてして我々 (主語が大きい) はミスを犯しがちです.そしてNPEがやってきて……我々はしぬ.

というわけで今回作ったのがarnoldです.
端的にいうと「必要なすべての要素を取りこぼさないbuilder」であり,「そのジェネレータ (annotation processor)」でもあります.Basic usageにあるように,

import net.moznion.arnold.annotation.ArnoldBuilder;
import net.moznion.arnold.annotation.Required;

@ArnoldBuilder
public class Example {
    @Required
    private String foo;
    @Required
    private int bar;
    @Required
    private double buz;
}

というようにclassに @ArnoldBuilder を付与することでinstantiate対象のclassであることを示し,builderでbuildしたいフィールドについては @Required を付与することで必要なフィールドであることを示すことができます.そしてプロジェクトをビルド *1 するとAnnotation Processorが走り,builder classが自動生成されます.
自動生成されたbuilderはこのように使うことができます.

Example example = new ExampleBuilder().foo("foo")
                                      .bar(42)
                                      .buz(2.71828)
                                      .build();

このBuilderは分解して書いてみると

ExampleBuilder exampleBuilder = new ExampleBuilder();
ExampleBuilder1 exampleBuilder1 = exampleBuilder.foo("foo");
ExampleBuilder2 exampleBuilder2 = exampleBuilder1.bar(42);
ExampleBuilder3 exampleBuilder3 = exampleBuilder2.buz(2.71828);
Example example = exampleBuilder3.build();

というふうになっており,つまり

  • ExampleBuilderfoo を受け取って ExampleBuilder1 を返すsetterを提供する
  • ExampleBuilder1bar を受け取って ExampleBuilder2 を返すsetterを提供する
  • ExampleBuilder2buz を受け取って ExampleBuilder3 を返すsetterを提供する
  • ExampleBuilder2build() を提供する

というようなコードをAnnotation Processorが一式生成するという挙動となっています.なかなか富豪的ですね.

このように,1つのbuilder (もはやbuilderではない気もしますが) が高々1つの要素を受け取り次のbuilderを返却し,終端のbuilderが build() して所望のclassのインスタンスをinstanciateする,という仕組みにすることで

  • 「build途中の不完全な状態のインスタンス」は何らかのbuilder classになるので,build対象のインスタンスをinstantiateすることは出来ない
  • 必須なフィールドがすべて揃ったらその時点でbuilderはbuild()を提供し,ようやくbuild対象のインスタンスをinstantiate出来るようになる

という安全な挙動を実現しています.


今後の課題としてはprivate classのinstantiateができないというものがあり,これはなんとかしたい *2


それはそうと今回Javaのコード生成には square/javapoet を使ってみたんですが,使いやすく,なおかつパワフルで良いですね.
現場からは以上です.ぜひご利用下さい.

*1:紛らわしいですね

*2:これなんとかなるんですかね?

YAPC::Okinawa 2018 Onnasonに行きつつ喋りました

yapcjapan.org

行ってきて,そして喋りました.
スライドは以下にあります.

speakerdeck.com

Perlコードに別の言語のコードを埋め込んで動かしてしまう技術であるところのInlineモジュールの話です.今回のYAPCのテーマは「万国津梁」とのことだったので「じゃあPerlと他の言語をつなげるInlineモジュールの話でもすればよかろう」と短絡的に題材を選択してしまったわけですが,そのままではなかなか「引っ掛かり」が無い話になってしまったため (出オチみたいな感じになった),色々風呂敷を広げてみました.それはそうとしてnumpyマジで速いですね.それだけ覚えて帰って下さい.
「グルー言語」の部分については碌々調べずにスライドのあるようなことを喋ったわけですが,恐らくこのような点はあるのだろうなと思っています.
かつて言語側で頑張らなければならなかったコンポーネント間の「糊付け」の責務が,HTTPやTCPのような「プロトコル」のレイヤに寄ってきている,あるいは別の見方をするとprotobuf等のIDLやJSONなどのデータ交換のための表現が発達したことによって「言語」のレイヤから更にメタな「表現」のレイヤにコンポーネントを協調させる責務が寄ってきた (寄ってきたというよりも,「表現」がその責務を負えるだけの進化を遂げたと言う方が適切かもしれません) というのがあるのかなと思います.これはいずれか片方にだけ寄っているという意味ではなく,複合的な要因によるものでしょう.もちろんスライドにも書きましたがLLVMのようなコードを変換するような潮流も,パラダイムを変えていった一因だと考えています.
とは言え「グルー言語」というエッセンスが無価値なものになったかと言うとそうではなく,むしろ選択肢が広がったという見方をするのが自然だと思っているところです.
懇親会ではid:nkgt_chkonkさんが,「例えばプリで計算した結果とかをどこかストレージに入れておいて,それをアプリ側で引いてくるっていうシチュエーションでは,ストレージもグルーのひとつだよね」とおっしゃっていて,たしかにそのとおりだなあと思いました.グルーはあまねく遍在している.これ,僕が言ったことになりませんかね?


それはさておき,今回のYAPCも興味深い話がたくさんあり,特に新屋さんのトークid:akiymさんのトークは大変おもしろく聞かせて頂きました.
新屋さんのトークは平易な言葉を用いて構文解析における曖昧性の解説をされていて大変勉強になりました.4年前に知りたかった…… *1.語り口としてもちゃんと笑わせてくる部分を作って確実に聴衆を引き込んでいくさまは流石ですね.
id:akiymさんのトークperlの新しくて便利なモジュール紹介100連発という感じで,常に新しい情報を追うというそのアンテナの高さと,積極的にそれを取り込んでいく姿勢には学ぶべきものが多いと思いました.ところで懇親会で酔っ払ってて訊けなかったんですけど,おもにどこで情報集めてるんですか?


というわけで,今回のYAPCはそんな感じでした.運営の皆さん,お疲れ様でした.
次回は東京に戻ってくるということで楽しみですね!

*1:色々なことが当時あった