Moddable SDKを使ってJavaScriptでIoT開発してみた
どうも。ほりひろ です。 dotstudioブログでは、初めましてですね。
JavaScript や IoT が好きな、でも実は半田付けも抵抗値の計算もろくにできないレベルの週末プログラマーです。 普段は某クラウドベンダーのサポートエンジニアをしています。 よろしくお願いします。
Twitterアカウントはこちらです。
年末に自分の中で話題になっていた、Moddable SDKというものをようやく触ってみました。
Moddable SDK & XS
Moddable SDK
Moddable SDK は、JavaScript コードを ESP32 や ESP8266 といったマイコン上で動作させるためのビルド環境やライブラリー群のこと、、、だと思います。
これは Moddable 社から提供されていますが、下記 GitHub リポジトリで公開されているので、無料で手に入れられます。
https://github.com/Moddable-OpenSource/moddable
XS
XS は、Moddable SDK で生成される JavaScript ランタイム環境 (ドキュメントには virtual machine と記載)で、なんと ES2018 に 99% 以上準拠しているらしいです。すごいですね!
※一部準拠していない部分は、注意事項としてこちらに記載されています。
これは公式ドキュメントにある画像です。「XS は一番小さい」って意味でしょうね。シャレてます。
ざっくりとした理解ですが、Moddable SDK のビルドツールで、自分が書いた JavaScript や C のソースコードと組み込みのクラスが含まれた XS を、一つのバイナリーにビルドし、マイコンに書き込んでいるようです。
これまで JavaScript でのマイコン制御というと、以前から ホスト PC とマイコンをシリアル接続し、ホストPC上の Node.js と Johnny-Five を使ってマイコンを制御する方法があり、最近では obniz の制御をネットワークを介して JavaScript などから行う方法がありますが、いずれもマイコンの外に JavaScript の実行環境を用意する必要があります。
一方で、Moddable SDK では、JavaScript 実行環境である XS がマイコン上で動作することができます。
この点は、これまでの実行環境とは大きく違うところですね!
開発環境の構築
基本的に、公式のリポジトリに記載された 構築手順通りに実施することで、ビルド ツールなどの環境が構築できます。
構築手順は macOS/Linux/Windows の各プラットフォーム向けにまとまっており、それぞれが、下記の 3 つのパートに分かれて記載されています。
mcconfig
やxsbug
などの開発ツールのビルド- ESP8266 向けの開発環境の構築
- ESP32 向けの開発環境の構築
いずれのプラットフォームでも 1. は必須ですが、2. と 3. は手持ちのボードに合わせて、どちらかを実施するだけでよいです。
私は Windows 用の環境構築をしましたが、Windows 向けの開発環境構築では、Windows ネイティブのコマンドを使用することをお勧めします。
WSL から git clone
などをすると、開発ツールがビルドできないようで、これに丸一日ハマりました。
あと、ビルドツールの実行は、必ず開発者コマンドプロンプト for VS2017
を起動し、そのコマンドプロンプトの中でしましょう。
サンプル コード
マイコンのサンプルと言えば Lチカですが、手元に LED がなかったので、とりあえず ESP32 上での非同期実行を試してみます。
ファイルの用意
プロジェクト ディレクトリに下記のような構造で、ファイルを作ります。
.
├── esp
│ ├── console.c
│ └── console.js
├── main.js
└── manifest.json
main.js
1 秒おきに 1
から 10
の数字を、1.5 秒おきに a
から z
の文字を、シリアル コンソールに出力するプログラムです。
import Timer from 'timer';
import console from "console";
const a = 'a';
const z = 'z';
let c = a;
let i = 0;
Timer.repeat(() => {
console.log(`${String(Date.now()).padStart(15)}:${i}`);
i = (i >= 10) ? 0 : i + 1;
}, 1000);
Timer.repeat(() => {
console.log(`${String(Date.now()).padStart(15)}:${c}`);
c = (c >= z) ? a : String.fromCharCode(c.charCodeAt(0) + 1);
}, 1500);
Web ブラウザーや Node.js なら、定期的な処理を書くなら setInterval
を使うところだと思いますが、Moddable SDK / XS では、グローバルに setInterval
が定義されていません。
代わりに、Timer
オブジェクトの repeat
メソッドを使って、同じ処理が似たような感じで書くことができます。
setInterval(() => {
:
}, 1000);
import Timer from 'timer';
Timer.repeat(() => {
:
}, 1000);
console.js /console.c
実は XS では console
オブジェクトもないので、とりあえず、下記の JS ファイルと C ファイルで、シリアルコンソールに 1 行出力できるメソッドを定義しておきます。
class Console @ "xs_console_destructor" {
constructor() {
}
static log() @ "xs_console_log"
}
Object.freeze(Console.prototype);
export default Console;
JS ファイルはタダのラッパーで、処理本体は C ファイルで定義しています。
JS ファイル内で @ ~
と書くと、C ファイルで宣言した関数とバインディングされます。
これは、XS 独自の実装のようです。
#include "xsAll.h"
#include "xs.h"
void xs_console_destructor(void)
{
}
void xs_console_log(xsMachine *the)
{
int argc = xsToInteger(xsArgc), i;
for (i = 0; i < argc; i++) {
char *str = xsToString(xsArg(i));
do {
uint8_t c = c_read8(str);
if (!c) {
ESP_putc('\n');
break;
}
ESP_putc(c);
str++;
} while (1);
}
}
manifest.json
最後は manifest ファイルです。 正直言うと、ここはあまり把握できていません :sweat:
include
で Moddable SDK で用意されている manifest を、ベースの manifest として読み込み、全プラットフォーム共通のモジュールとして main
(.js) を、esp32
向けには、./esp/console
(.js) をロードする設定を書いています。
{
"include": "$(MODDABLE)/examples/manifest_base.json",
"modules": {
"*": [
"./main",
]
},
"platforms": {
"esp32": {
"modules": {
"*": [
"./esp/console",
],
}
}
},
}
上の manifest.json
では Timer
クラスをロードしていませんが、main.js
では問題なく import することができます。
これは、Moddable SDK に含まれるベースの manifest でロード設定がされているためです。
{
:
(略)
:
"platforms": {
:
(略)
:
"esp32": {
"include": "$(BUILD)/devices/esp32/manifest.json"
},
}
:
(略)
:
}
{
:
(略)
:
"modules": {
"*": [
"$(MODULES)/base/time/*",
"$(MODULES)/base/time/esp/*",
"$(MODULES)/base/timer/*",
"$(MODULES)/base/timer/mc/*",
]
},
"preload": [
"time",
"timer",
],
:
(略)
:
}
ちなみに manifest についてツイートしたところ、公式アカウントからも返事がありました。
ドキュメントは定期的にメンテナンスされそうです。
The manifest documentation (https://t.co/HTYtMXR33N) is a good place to start, but we'll put a document with more details together soon. If you have any specific questions, we'd be happy to answer those as well.
— Moddable (@moddabletech) 2019年1月18日
実行してみる
manifest.json
があるディレクトリで、mcconfig
コマンドを実行します。
大抵は、XS のビルドから始まるので、書き込みが完了するまでだいぶ時間がかかると思います。
書き込みが完了すると、シリアルモニターに自動的に接続し、console.log
の出力内容が表示されます。
> mcconfig -m -p esp32
rm: cannot remove '/root/Projects/moddable/build/tmp/esp32/release/xsProj/sdkconfig': No such file or directory
# Running GENCONFIG...
fatal: Not a git repository (or any of the parent directories): .git
including /root/esp32/esp-idf/components/bootloader/Makefile.projbuild...
:
(略)
:
MONITOR
--- idf_monitor on /dev/ttyUSB0 115200 ---
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
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:636
load:0x40078000,len:6192
load:0x40080000,len:5152
0x40080000: _iram_start at /root/esp32/esp-idf/components/freertos/xtensa_vectors.S:1685
entry 0x40080264
0x40080264: _Level5Vector at ??:?
1010:1
1510:a
2010:2
3010:3
3011:b
4010:4
4510:c
5010:5
6010:6
:
(略)
:
指定した時間間隔で、コールバック関数が実行されています!
ES2017 で入った String.prototype.padStart
も正常に動作しているようです。
なお、マイコンには時計がないので、Date.now()
は起動時からの時間を返します。
デバッグ実行
先ほど実行したコマンド mcconfig -m -p esp32
にデバッグ オプション -d
を追加すると、デバッグ ビルドを実行し、デバッグ ツールである xsbug
が自動で起動します。
この xsbug
は JavaScript コードにブレークポイントの設定や、ステップ実行、変数の内容などを表示することが
でき、結構本格的なデバッグツールです。
今は独自の GUI ツールとして提供されているようですが、そのうちVSCode から拡張機能として利用できるようになると嬉しいですね。
まとめ
いかがだったでしょうか。 Moddable SDK を使うことで、JavaScript で書いたコードを、ESP8266/32 で動作させることができました。
Web フロントエンドや Node.js など、JavaScript を書くエンジニア人口は多いでしょうから、そういった方々も気軽にマイコン開発ができるようになりますね。
あとは、manifest ファイルに関するドキュメントの整備がすすんだり、もう少し簡単に環境構築ができれば、格段に開発しやすくなるのではないでしょうか。