Fukuoka RubyistKaigi 03 2023.02.18 Sat. 13:30-13:55 https://regional.rubykaigi.org/fukuoka03/index.html
2023-02-18(Sat) 岡嵜雄平mruby on IoT devices.
View Slide
自己紹介
岡嵜雄平 @Y_uuu株式会社Fusic IoTクラウドエンジニアフィヨルドブートキャンプ メンター
本日のお話• mrubyとIoT• mruby-esp32• 課題1: ESP-IDFの最新バージョン(v5.0)に追従できていない• 課題2: MQTT通信クライアントがない• まとめ4
mrubyとIoT
mruby• 組込みシステムをターゲットに開発されたRubyの処理系• 現在もMatzを中心に開発が続けられている• mrubyをさらに軽量化したmruby/cというOSSも存在する6
• IoT開発(特にPoCフェーズ)で頻発するチューニングに柔軟に対応しやすい• Web/クラウドと同じRubyで開発ができるIoT開発におけるmrubyの可能性7クラウドデバイスセンサー収集 加工 分析 可視化インターネットセンサーの値を送信7別のセンサーを使いたい送信データの形を変えたい送信周期を変えたい機能を拡張したい
mruby-esp32
ESP32• Espressif Systems社によって開発されたマイクロコントローラ(マイコン)• 低消費電力かつWi-Fi・Bluetoothも内蔵しているためIoTとの親和性が高い• M5Stackに搭載されているマイコンもESP329https://ja.wikipedia.org/wiki/ESP32
ESP32-DevKitC• ESP32を搭載した評価基盤• 1600円で買える(送料・手数料は別)• mrubyを動かすために十分なスペック(RAM: 512KB, ROM: 4MB)10
mruby-esp32• ESP32上でmrubyを動かすためのOSSプロジェクト• ESP-IDFというフレームワークを使ってmrubyをコンパイル・リンクしている11ESP-IDFProjectComponentMainComponentmrubyToolchainmruby-esp32.binコンパイル・リンク
mruby-esp32• binファイルをROMに書き込むことで起動• FreeRTOS(組込み向けのリアルタイムOS)上でmrubyが動作12ESP-IDFProjectComponentMainComponentmrubyToolchainmruby-esp32.binESP32-DevKitCFreeRTOSアプリケーションmrubyMrubyTaskコンパイル・リンクアップロード(ROMに書き込み)Library
使い方1. ESP-IDFをインストール• インストール手順はESP-IDF Programming Guide > Get Startedを参照13https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html
使い方2. mruby-esp32をgit clone• mrubyがSubmoduleとなっているので注意14https://github.com/mruby-esp32/mruby-esp32
使い方3. Rubyのプログラムを書く• main/spiffs/main.rb に任意のプログラムを記述します15
使い方4. ビルド• Idf.py build コマンドでビルドします(初回は1分ほどかかります)16
使い方5. デバイスとPCを接続• /dev/tty.usbserial-xxx のようなデバイスファイル(PORT)を確認17
使い方6. 書き込み・起動• idf.py -p (PORT) flash monitor コマンドで書き込み・起動します18LEDと反対側のボタンを押して、モードを切り替える実機での出力をシリアル通信で確認
応用編: AWS IoT CoreにMQTTSでPublish• わずか10行のコードでAWSにデータを送信できます19
簡単にデバイス上でmrubyを動かせる🎉• 「簡単に」というイメージを、今日はぜひ持ち帰ってください• ここから先は諸々の課題が解決する前(2ヶ月前)に時を戻します…20
IoTでの活用を想定したとき、2つの課題に直面1. mruby-esp32がESP-IDFの最新バージョン(v5.0)に追従できていない• デバイスのライフタイムを考えると、最新バージョンに対応したい2. mruby-esp32で使用できるMQTT通信クライアントがない• IoTシステムにおいてよく使われるプロトコルであり、必要不可欠21
2つの課題をどう解決したか?• これからお話していきます22
課題1: ESP-IDFの最新バージョン(v5.0)に追従できていない
プロジェクトの構成を理解する• 2つのコンポーネント、それぞれにcomponent.mk (Makefile)が配置• Mrubyのコンポーネントの中にmrubyのソース一式がある24mruby_componentmain_componentmrubyのソースコード一式(サブモジュール)
ESP-IDF v5.0を使ってビルドしてみる• いろいろな問題が起こる💣25
• project.mkがない、というエラーが出力される問題1: ビルドが始まらない26
• ビルドシステムがMake→cmakeに変わっている• v4.4のドキュメントでは存在していたMakeへの言及がv5.0では消滅問題1: ビルドが始まらない27https://docs.espressif.com/projects/esp-idf/en/v4.4.4/esp32/api-guides/build-system.html https://docs.espressif.com/projects/esp-idf/en/v5.0/esp32/api-guides/build-system.htmlv5.0のドキュメントにはMakeへの言及がない
問題1: ビルドが始まらない• ESP-IDFの他OSSやドキュメントを参考にCMakeLists.txtを記述• Mrubyのビルドはカスタムコマンドとしてrakeを実行28https://github.com/mruby-esp32/mruby-esp32/pull/27カスタムコマンドとしてmrubyのrakeを実行
問題2: Legacy event loopでコンパイルエラー• mruby-esp32-wifiでコンパイルエラーが発生29
問題2: Legacy event loopでコンパイルエラー• Wi-Fi接続やIP取得といったイベントを検知するために使用• v5.0ではLegacy event loopが消滅している30V5.0にLegacy event loopのドキュメントは存在しないhttps://docs.espressif.com/projects/esp-idf/en/release-v4.4/esp32/api-reference/system/esp_event_legacy.html
問題2: Legacy event loopでコンパイルエラー• 後継のEvent Loop Libraryを使って処理を置き換える31https://github.com/mruby-esp32/mruby-esp32-wifi/pull/3
問題3: mrubyのコンパイルでたくさんエラーが起こる• mrbgemでincludeしている、ESP-IDFが提供するヘッダファイルでエラー32
問題3: mrubyのコンパイルでたくさんエラーが起こる• ESP-IDFの通常のプロジェクトをコンパイルするときとの オプションの違いを見比べる• 通常のプロジェクトでは-std=gnu17というオプションが付いている33ProjectComponentMainComponentmrubyToolchainESP-IDFでビルドC17RakeでビルドC99Toolchain内のソースコードがC17を前提としているesp_macros.h
問題3: mrubyのコンパイルでたくさんエラーが起こる• mrubyもC17でビルドするよう、オプションを付けることで解決34https://github.com/mruby-esp32/mruby-esp32/pull/27オプションを追加
問題4: Error: app partition is too small for 〜 が出る• ビルドの終盤でエラーが発生35
問題4: Error: app partition is too small for 〜 が出る• パーティション=ROMの構成情報36ブートローダーパーティションテーブルnvs: 24KBphy_init: 4KBfactory: 1MB空き0x000000000x000010000x000090000x0000f0000x000100000x00110000
問題4: Error: app partition is too small for 〜 が出る• デフォルトのパーティションテーブルではapp領域は1MBしかない37ブートローダーパーティションテーブルnvs: 24KBphy_init: 4KBfactory: 1MB空きここがapp領域0x000000000x000010000x000090000x0000f0000x000100000x00110000
問題4: Error: app partition is too small for 〜 が出る• パーティションテーブルをカスタマイズして1.5MBまで拡げて対策38ブートローダーパーティションテーブルnvs: 24KBphy_init: 4KBfactory: 1.5MB空き0x000000000x000010000x000090000x0000f0000x000100000x00180000ここには後々 ファイルシステムを構築予定一部のESP32はROMが2MBしか ないため、全体を2MB以内としたいhttps://github.com/mruby-esp32/mruby-esp32/pull/30
ESP-IDF v5.0でビルド→起動までこぎ着けた🎉• 実際には1つの問題に数日を要することもあり、それなりに大変だった39
ポーティングのポイント• 組込みソフトウェアがどういった仕組みで動作するか前提知識をつけること• MCU/ROM/RAMといったH/W構成要素を理解する• リアルタイムOS(ex: FreeRTOS)について知る• エラーメッセージから何が起こっているのか正しく理解すること• Webとは違い、H/Wが関連するエラーもしばしば起こるので注意40
ポーティングのポイント• 開発環境(ex: ESP-IDF)のドキュメントをよく読むこと• バージョンごとの変遷を追う• カスタマイズできること・できないことを把握する• 情報が集まるコミュニティ(ex: ESP-IDFのissues)を見つける• Webの世界に比べて、情報は少ない• 類似する問題を探す・質問する41
課題2: MQTT通信クライアントがない
MQTT• TCP/IPによるPub/Sub型データ配信を行う、軽量データ配信プロトコル• ヘッダーサイズが最小2byte、プロトコルシーケンスがシンプル• 1つのクライアントがPublisherとSubscriberを兼ねることもできる43BrokerPublisherPublisherSubscriberSubscriber
ESP-MQTT• ESP32上で動作するMQTTクライアント• これをラッピングしたmrbgemを作れば良い44https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/protocols/mqtt.html
参考: mrbgem• RubyでいうところのRubygemにあたる、mrubyの拡張ライブラリ• RubyまたはC言語を用いて実装できる• 今回はESP-IDFが提供するAPIを利用したいのでC言語で実装45アプリケーションmrubyMrubyTask wifi mqtt.c・・・FreeRTOSLibrary.c mrbgems
クラス設計(mruby-mqttを参考)• 当初、mruby-mqttを元に検討した• 全体的に非同期的であり、コールバックを多用する仕様に懸念46https://github.com/hiroeorz/mruby-mqtt接続時や送受信時にyieldされる
• on_connect等によるコールバックが別タスク(別コンテキスト)となる• mruby VMはシングルタスクでの動作が前提のため、避けたいクラス設計(mruby-mqttを参考)47Rubyアプリケーションconnectmruby-esp32-mqtton_connectESP-MQTTmruby VMconnectMain TaskMQTT Taskblockevent handler別のタスク(別コンテキスト)
クラス設計(ruby-mqttを参考)• ruby-mqttの設計を参考にした• いずれのメソッドも同期的であり、処理が完了してから次の処理へ進む48https://github.com/njh/ruby-mqttconnect, getどちらも同期的
• シングルタスクでmruby VMを動かす• 別タスクからのイベントを受信後、元のメソッドをreturnクラス設計(ruby-mqttを参考)49Rubyアプリケーションconnectmruby-esp32-mqttESP-MQTTqueueconnectMain TaskMQTT Taskevent handlerwait_for_eventイベントの情報をエンキュー同期的にreturn
mrb_mruby_esp32_mqtt_gem_init50• mrbgemのお作法に則って実装https://github.com/mruby-esp32/mruby-esp32-mqtt/blob/master/src/mrb_esp32_mqtt.cMQTTクラスを定義各種メソッドを定義
mrb_mqtt_client_connect• 接続後に mqtt_wait_for_event で接続完了を待っている51https://github.com/mruby-esp32/mruby-esp32-mqtt/blob/master/src/mrb_esp32_mqtt.cQueueにイベントが来るまで待つMQTT接続開始
Rubyアプリケーション• わずか10行足らずで、MQTT Publish可能に🎉52https://github.com/mruby-esp32/mruby-esp32/blob/master/main/examples/mqtt_publish.rb
mruby-esp32/mruby-esp32-mqtt として公開中• mruby-esp32のOrganization配下で公開 🎉53https://github.com/mruby-esp32/mruby-esp32-mqtt
まとめ
所感• デバイス上でmrubyを動かすのは一苦労• 一度動いてしまえば使い慣れた言語でデバイスを制御できる• 比較的コントリビュートしやすい• UART通信, SPI通信, タイマー, RTCといったAPIをmrbgem化• ドキュメントの整備55
おわりに• 今日の話を聞いて「デバイス難しそう…」と感じた人がいるかもしれません• 実際、Webシステムの開発とは別のハードルがたくさんあります56
おわりに• 少なくとも、今日話した内容は全て解決済みなのでご安心ください• ぜひお手元にESP32-DevKitCをご用意の上、mrubyを動かしてみてください57
ご清聴ありがとうございました
Appendix
参考: IoTシステム• モノのインターネット• よくある事例: センサーの値をクラウドに送信して可視化・分析60クラウドデバイスセンサー収集 加工 分析 可視化インターネットセンサーの値を送信
参考: リアルタイム性とmruby• IoTシステムで求められるリアルタイム性がそこまで高くない• 組込みシステムの中でも、mrubyが敬遠される要素が少ない61時間の流れタスクA タスクBイベントが発生レイテンシタスクAイベントが発生レイテンシタスクBハードリアルタイム例: 自動車のブレーキ、緊急停止装置ソフトリアルタイム例: GUI、環境測定、データ配信mrubyはソフトリアルタイム処理期限
• FreeRTOSにおけるタスク間でのデータ送受信や同期を行う仕組みの一つ• 別タスクに対してデータを送信できる• 送受信が完了するまで、タスクを待ち状態にできる参考: FreeRTOS Queue API62queue sender_taskreveiver_taskQueueにメッセージが来るまで待ち状態に
参考: MQTT Publishの動作確認• mosquittoというツールでSubscribeし、Publishできていることを確認63