はじめに
Mongoose OSは組み込み向けOSのひとつで、mJSというライブラリによってマイコン上でJavascriptのようなコードを動かすことが可能です。
参考: ESP32をWiFi経由でコントロールするアーキテクチャとライブラリをまとめた
ただ、センサー類のドライバをすべてJSで書くとなると大変なので、
C言語でドライバを書いて、それをJSから利用することが可能な仕組みが用意されています。
今回はC言語でDS18B20の制御コードを書き、それをJSから利用しようとした記録です。
環境
- Windows 10
- Mongoose OS v2.1.5
- ESP32-DEVKITC
結論
思いの外面倒だったので、これなら全部Cで書いたほうが良いと思いました。
Cで書いたドライバ
以下のリポジトリにアップしています。
https://github.com/uhey22e/mongoose-os-ds18b20
以下リポジトリをフォークさせていただきました。
https://github.com/maclema/mongoose-os-ds18b20
mJS用のコードを追加している他に、Initialize/Deinitializeの関数を分離したりなどしています。
mJSに関係ない部分は割愛します。
このライブラリは、Cであれば以下のように使うことが可能です。
enum mgos_app_init_result mgos_app_init(void) {
ds18b20_init(25, 10);
mgos_set_timer(1000 /* ms */, MGOS_TIMER_REPEAT, timer_cb, NULL);
return MGOS_APP_INIT_SUCCESS;
}
// 1秒おきにOSから呼ばれる
static void timer_cb(void *arg)
{
// 計測開始
ds18b20_read_all(temperatures_cb);
// 諸々の処理...
}
// 計測完了コールバック
static void temperatures_cb(ds18b20_result *results)
{
if ( results == NULL ) {
LOG(LL_INFO, ("Results is null."));
}
// 各センサの計測結果を出力
while ( results != NULL ) {
LOG(LL_INFO,
("ROM: %s, Temp: %f", results->mac, results->temp));
results = results->next;
}
}
結果が入る構造体は以下のようになっています。
typedef struct ds18b20_result {
uint8_t rom[8]; // センサのアドレス
char* mac; // センサのアドレスを文字列化したもの
float temp; // 計測結果
struct ds18b20_result* next; // 次の結果
} ds18b20_result;
DS18B20はひとつのバスに複数のセンサを接続可能です。
そのため、センサがいくつ繋がっていても対応出来るように、Linked Listの形を取っています。
mJSから使う
このドライバを、以下のようなコードで使えるといいなと思っていましたが、そんな簡単にはいきませんでした。
// 動かないコード
load('api_ds18b20.js');
DS18B20.init(25, 10);
DS18B20.readAll(function (results) {
results.forEach(function (result) {
print("Addr:", result.mac);
print("Temp:", result.temp);
});
}, null);
mJSではJavascriptと比べて色々と制約があります。
https://github.com/cesanta/mjs#Restrictions
最終的なコード
load('api_ds18b20.js');
DS18B20.init(25, 10);
// 2秒おきに計測
Timer.set(2000, Timer.REPEAT, function () {
Log.info("Timer Callback");
DS18B20.read_all();
// 計測完了まで100ms待つ
Timer.set(100, false, function () {
let index = 0;
// 各センサの計測結果を取得する
while (true) {
let result = DS18B20.getResultByIdx(index);
if (!result) {
break;
}
print(result.mac);
print(result.temp);
index++;
}
}, null);
}, null);
本当は、read_all
のコールバック内で計測結果を取得したかったのですが、
CのコードからmJSのfunctionを呼び出すと、Guru Mediation Error
が発生してしまい、これが解決できませんでした。
結果として、Timerで固定時間のwaitをいれてから、ひとつずつ取得する形式にしました。
mJSとCをつなげるコードは以下のように書きました。
let DS18B20 = {
init: ffi('void ds18b20_init(int, int)'),
deinit: ffi('void ds18b20_deinit(void)'),
read_all: ffi('void ds18b20_read_all_wrap(void)'),
_getResultByIdx: ffi('void * get_result_by_idx(int)'),
// Cの構造体をmJSのオブジェクトに変換
getResultByIdx: function (idx) {
let sd = ffi('void * get_ds18b20_result_descr()')();
let s = this._getResultByIdx(idx);
if (s !== null) {
return s2o(s, sd);
}
return null;
},
};
read_all_wrap
というmJS用にread_all
をラップした関数を作成しました。
他にも、Cの構造体とmJSのオブジェクトを結びつけるためのds18b20_result_descr
という定数も作っています。
LinkedListに使うnext
のフィールドは、mJSでうまく扱えないため落としています。
アドレスを示すrom
という配列も、mJSで配列がうまく扱えないため落としています。
// For mJS API
static const struct mjs_c_struct_member ds18b20_result_descr[] = {
{"mac", offsetof(ds18b20_result, mac), MJS_STRUCT_FIELD_TYPE_CHAR_PTR, NULL},
{"temp", offsetof(ds18b20_result, temp), MJS_STRUCT_FIELD_TYPE_FLOAT, NULL},
{NULL, 0, MJS_STRUCT_FIELD_TYPE_INVALID, NULL},
};
const struct mjs_c_struct_member* get_ds18b20_result_descr(void)
{
return ds18b20_result_descr;
}
void ds18b20_read_all_wrap(void)
{
ds18b20_read_all(NULL);
}
void * get_result_by_idx(int idx)
{
ds18b20_result* temp;
// Get result ptr by idx
temp = _list;
for (int i = 0; i < idx; i++) {
if (temp == NULL) {
return NULL;
}
temp = temp->next;
}
return temp;
}
まとめ
なんとか、mJSからDS18B20を使えるようにはなりましたが、あまり使い勝手の良いAPIとは言えない状態です。
とはいえ、Guru Mediation Error (LoadProhibited)
のデバッグがしんどく、これ以上手を加える気にもなれません。
Cのデータ構造をそのまま持って来られるわけでは無いのも、なかなか面倒でした。
各クラウドへの接続が簡単に出来て、RPCの仕組みも強力なMongoose OS、
メインの制御ロジックにmJSを使えばかなりの省力化が可能でとても良いのですが、
私は「やっぱりCでいいか…」となってしまいました。