Help us understand the problem. What is going on with this article?

Mongoose OSでDS18B20のmJSライブラリを作ったが…

はじめに

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であれば以下のように使うことが可能です。

main.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;
    }
}

結果が入る構造体は以下のようになっています。

ds18b20.h
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

最終的なコード

init.js
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をつなげるコードは以下のように書きました。

api_ds18b20.js
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で配列がうまく扱えないため落としています。

ds18b20.c
// 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でいいか…」となってしまいました。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away