スタックチャン製品化研修

Moddableではじめる!あなただけのスタックチャン !!

スタックチャン製品化研修

自己紹介

はじめまして、ズズくんと申します。株式会社アールティのエンジニア1年目です。アールティでの「スタックチャンキット化プロジェクト」でソフトウェア開発をしています。

↓ししかわさんのポスト

 

入社前からTwitterでスタックチャンの存在は知っていましたが、今年の7月に開催されたスタックチャンお誕生日会2023に足を運んで様々なスタックチャンを見て強く興味を持ちました。

スタックチャンお誕生日会2023

多くの人が参加しているスタックチャンコミニティに私も参加させていただきますので、よろしくお願いします!!もし今回の記事で質問等ありましたら、スタックチャンのDiscordコミュニティ(kurasawa_zuzukun)Twitter(@zuzukun1129)で気軽に声をかけてみてください!

あなただけのスタックチャン

スタックチャンはししかわさんが開発、公開している、手乗りサイズのスーパーカワイイコミュニケーションロボットです。

GitHub - stack-chan/stack-chan: A JavaScript-driven M5Stack-embedded super-kawaii robot.
A JavaScript-driven M5Stack-embedded super-kawaii robot. - stack-chan/stack-chan

スタックチャン
(引用:ししかわ版スタックチャンより)

私も自分だけのスタックチャンを作りたいところですが、ハードウェアの改造は敷居が高いです。そこで、まずはソフトウェアで変更可能な色の変更をしてみることにしてみます。これならソフトの変更だけで済みます。さらに「自分だけの世界にひとつのスタックチャン」感を出すために、M5Stackのデバイスごとに固有の色を作れないかと考えました。

ししかわさんとのディスカッションの結果「M5StackのMACアドレスをもとにデバイス固有の色を生成する」というアイディアに至りました。
MACアドレスはデバイスごとに固有に割り振られた値なので、これを活用しようというわけですね!

それでは、私だけの色のスタックチャンを実装していきます!

↓本記事のソースコード全体をGitHubで公開しておきます。

GitHub - KuraZuzu/stack-chan at feature/device-specific-color
A JavaScript-driven M5Stack-embedded super-kawaii robot. - GitHub - KuraZuzu/stack-chan at feature/device-specific-color

スタックチャンとModdable

スタックチャンはArduinoやMicroPythonなどの様々なプラットフォームで開発されています。ししかわ版スタックチャンではModdableが使われています。

Moddableとは組み込み開発のためのJavaScriptアプリケーション開発プラットフォームです。ESP32Raspberry Pi Picoなどのマイコンボード上でJavaScriptのコードを実行できます。

Moddable
Tools to create open IoT products using standard JavaScript on low cost microcontrollers.

詳しくはスタックチャン開発者であるししかわさんのブログにて記述されています。

Moddable版ファームウェアはArduino版に比べて利用者が少ないこともあり、今回はModdableの啓蒙を兼ねてスタックチャンで開発する流れを紹介します。

環境構築

本記事では環境構築の説明は省略します。ししかわ版スタックチャンの環境構築のページが簡潔に説明してくれていますので、そちらをご覧ください。

Moddableの環境構築は以前までは複雑な面がありましたが、xs-devというCLIツールにより敷居が下がりました。ししかわ版スタックチャンでも用いられています。

Modでスタックチャンの顔の色を変える

Moddableには、Modという仕組みがあります。スタックチャンを動かす基本のプログラムに付随して、私達が自由に動作をプログラムできます。

実際には、ModonRobotCreatedという関数が、アプリの起動時に呼ばれます。つまり、onRobotCreatedという関数をこちらで用意して、その中に処理を記述するだけでスタックチャンの動きや見た目をカスタマイズできちゃいます!

HostとMod
(引用:ししかわ版スタックチャンの”ホストとMOD“より)

顔の色を変えるのはrobot.setColorというメソッドを使います。
たとえばM5StackのCボタンを押したときに顔の色を白黒反転させるコードは次のようになります(サンプルアプリから抜粋)

export function onRobotCreated() {
  // ...
  robot.button.c.onChanged = function () {
    if (this.read()) {
      trace('pressed B\n')
      if (flag) {
        robot.setColor('primary', 0xff, 0xff, 0xff)
        robot.setColor('secondary', 0x00, 0x00, 0x00)
      } else {
        robot.setColor('primary', 0x00, 0x00, 0x00)
        robot.setColor('secondary', 0xff, 0xff, 0xff)
      }
      flag = !flag
    }
  }
}

primaryが目や口の色、secondaryが背景=顔の色です。これに続いてR, G, Bの値を渡すと顔の色を自由に変更できます。

つまりデバイスに固有の顔色を設定したければ次のようにすればよいです。

const [r, g, b] = getDeviceSpecificColor() // ここになんらかのデバイスに固有な色を取得する関数
robot.setColor('secondary', r, g, b)

ここからはgetDeviceSpecificColorの中身を実装していきましょう。

ModdableでMACアドレスを取得する

ModdableにはデバイスのMACアドレスを取得する機能は提供されていません。
MACアドレスを得るにはベースとなるesp-idf側のAPIを使う必要があり、これにはC言語を使わないといけません。

そこで登場するのがXS in Cです!
C言語とJavaScriptを繋ぐModdableの仕組みです。

XS in C

特殊な記法を使ってJavaScriptからCのコードを呼び出します。
JavaScriptの関数として呼び出すために必要な要素はこれらです。

  • 呼び出されるC言語ファイル
  • 呼び出す側のJavaScriptファイル
  • ビルドのメタデータを記述したjsonファイル(マニフェストファイルと呼ぶ)

M5Stackの色情報を取得する機能のディレクトリ名をcolorと名付けました。 実際の構成は以下のようになります。

color
├── color.js
├── mac-adress.c
└── manifest_color.json

“XS in C” での依存関係

mac-adress.c
#include "xsPlatform.h"
#include "xs.h"
#include "mc.xs.h"
#include "esp_err.h"
#include "esp_system.h"

void mod_get_mac_address(xsMachine* the) {
    uint8_t ret[6];

    int32_t err = esp_efuse_mac_get_default(ret);
    if (err) xsUnknownError("failed to get mac address");

    xsResult = xsArrayBuffer(ret, 6);
}

mod_get_mac_address() という関数を呼ぶと、Macアドレスの文字列が返ってきます。XS in Cを使うと、JavaScriptの様々な文法と等価な処理をC言語で記述できます。xsResultへの代入は、JavaScriptのreturn文に相当します。

XS in Cでの値のやり取りは引数にあるxsMachine*という構造体のポインタを介して行います。xsRsultxsMachineのメンバですが、省略されておりthe->xsRsultと同義です。そのため、戻り値がvoidとなっています。

 

color.js
import { Digest } from 'crypt'
import { hslToRgb } from 'stackchan-util'

function getMacAddress() @ "mod_get_mac_address";

export function getDeviceSpecificColor() {
    let sha1 = new Digest('SHA1')
    sha1.write(getMacAddress())
    const arr = sha1.close()
    trace(arr)
    const dv = new DataView(arr, 0, 2)
    trace(dv.getUint8(0) + '\n')
    trace(dv.getUint8(1) + '\n')

    const color = hslToRgb(dv.getUint8(0) * 360 / 255, 1, 0.1 + (dv.getUint8(1) * 0.8 / 255))

    return color
}

いろいろ書いていますが重要なのは以下の部分です。

function getMacAddress() @ "mod_get_mac_address";

この「JavaScript関数の宣言の後、@に続けてCの関数を書く」という記法は、Moddable独自のもので、JavaScriptとCの関数をつなげる役割を持ちます。

getDeviceSpecificColor()関数内の実装はMACアドレスからハッシュ値を計算していますが、読み飛ばして構いません。使い方はModdableのドキュメントを参考にしました。

 

manifest_color.json
{
    "modules": {
        "*": [
            "$(MODDABLE)/modules/crypt/digest/*",
            "$(MODDABLE)/modules/crypt/digest/kcl/*",workspace_signin
            "$(MODDABLE)/modules/crypt/arith/*",
            "$(MODDABLE)/modules/crypt/bin/*",
            "$(MODDABLE)/modules/data/base64/*",
            "./color",
            "./mac-adress"
        ]
    },
    "preload": [
        "color"
    ]
}

今回必要なモジュールを羅列しています。ここには、JavaScriptのコードだけではなく、呼び出されるC言語のファイルも記述する必要があります。ここに記述されたものがモジュールとして解釈されます。 "*": [モジュール名]によって、拡張子を除いたファイル名がモジュール名となります。

これでモジュールが完成しました!!

末尾に拡張性がついていませんが、ディレクトリ内の.c.jsファイルは省略可能です。なので、同名の.js.cであれば1行だけ書けば良いです。

M5Stack液晶の色変更

上記のcolorモジュールを使って、M5Stackの液晶の色を変更してみましょう。
後々、これをスタックチャン側で呼び出します。

M5Stack液晶の色変更の構成

それでは、今作ったモジュールをModdableのプロジェクトから使ってみましょう。

液晶の色を変更するには、以下のような構成になります。

color
├── color.js
├── mac-adress.c
├── manifest_color.json
├── main.js
└── manifest.json

先程の構成に加えて、main.jsmanifest.jsonが追加されています。
これらは、先程完成させたモジュールを呼び出して実行するJavaScriptファイルと、そのメタデータが記述されたファイルです。

M5Stack液晶変更での依存関係

manifest.json
{
    "include": [
        "$(MODDABLE)/examples/manifest_base.json",
        "$(MODDABLE)/examples/manifest_commodetto.json",
        "../../stackchan/utilities/manifest_utility.json",
        "./manifest_color.json"
    ],
    "modules": {
        "*": [
            "./main"
        ]
    }
}

先程作った、モジュールのメタデータmanifest_color.jsonincludeします。M5Stackの画面の色を変えるためにcommodettoというUIライブラリを使います(Moddableに同梱されています)。

main.js
import { getDeviceSpecificColor } from "color"
import Poco from 'commodetto/Poco'

const poco = new Poco(globalThis.screen)

const [r, g, b] = getDeviceSpecificColor()
const color = poco.makeColor(r, g, b)
poco.begin(0, 0, poco.width, poco.height)
poco.fillRectangle(color, 0, 0, poco.width, poco.height)
poco.end()

下記のように、C言語をラップしたモジュールcolorと、その中の関数getDeviceSpecificColorが呼び出せるようになっています。

import { getDeviceSpecificColor } from "color"

詳しい説明は省きますが、「getDeviceSpecificColorの結果を使って画面全体を塗りつぶす」処理をしています。

M5Stackに書き込み

それでは書き込んでみます。書き込みはマニフェストファイルが置いてある階層でコマンドを実行します。
今回は、color内です。

$ cd [colorのディレクトリ]
# M5Stack Basic/Gray/Fireの場合
$ mcconfig -d -m -p esp32/m5stack

# M5Stack Core2の場合
$ mcconfig -d -m -p esp32/m5stack_core2

# M5Stack CoreS3の場合
$ mcconfig -d -m -p esp32/m5stack_cores3

動作確認

2台のM5Stackに書き込んで動作確認してみました。

書き込んだプログラムは同じですが、getDeviceSpecificColorの結果がデバイスによって異なるため、それぞれ違う色が表示されています。

無事動作しました。嬉しいです!!

デバイス固有の色の液晶になったスタックチャン

無事にデバイスごとの色が反映されましたが、やはりスタックチャンには顔が無いと寂しいですね。
それでは、スタックチャンのプログラムとして取り込んでみましょう。

もう一息です。

スタックチャンの色に反映させる

今度はスタックチャンの色を変えます。

冒頭で説明したとおり、Modを使えばスタックチャンの顔に反映できます。
Modから、先程作ったcolorモジュールのgetDeviceSpecificColor関数を使ってデバイス固有の色を取得します。

スタックチャンの色変更の構成

液晶の色を変更するには、以下のような構成になります。

stack-chan/firmware
├── mods
│   ├── device-specific-color
│   │   ├── manifest.json
│   │   └── mod.js
│   └── (その他のモジュール郡)
│
└── stackchan
    ├── color
    │   ├── color.js
    │   ├── mac-adress.c
    │   └── manifest_color.json
    │
    ├── main.ts
    └── manifest.json

ホスト側にmanifest_color.jsonモジュールを含めてビルドしたうえで、Modから利用します。
スタックチャンのホストを含むプログラムはstack-chan/firmware配下に置いてあります。

先程作成した、colorではMACアドレスによる液晶カラーの変更プログラムを呼び出すためのmain.jsmanifest.jsonは今回用いないので削除しています。(GitHubのコードでは残したままにしています)

スタックチャン色変更での依存関係

スタックチャンHost側の構成

先程の全体の構成から一部切り取ったものです

stack-chan/firmware/stackchan
├── main.ts
└── manifest.json

Moddableではセキュリティ等の都合上、Cのコードを含むモジュールはModではなくHost側のプログラムに含めるという決まりがあります。

ですので、モジュールディレクトリをstackchan配下に置き、そのメタデータであるマニフェストファイルもHOST側のmanifest.jsonを改造する必要もあるわけです。

stackchan/manifest.json
(一部抜粋)

{
    // ...(中略)

    "include": [
        "$(MODDABLE)/examples/manifest_base.json",
        "$(MODDABLE)/examples/manifest_typings.json",
        "$(MODULES)/base/modules/manifest.json",
        "$(MODULES)/data/crc/manifest.json",
        "./drivers/manifest_driver.json",
        "./ble/manifest_ble.json",
        "./dialogues/manifest_dialogue.json",
        "./renderers/manifest_renderer.json",
        "./services/manifest_service.json",
        "./speeches/manifest_speech.json",
        "./utilities/manifest_utility.json",
        "./manifest_typings.json",
        "./color/manifest_color.json"
    ],
    "modules": {
        "*": [
            "./touch",
            "./robot",
            "./main",
            "./color/color"
        ],
        "default-mods/*": "./default-mods/*"
    },
    "preload": [
        "robot",
        "touch",
        "color"
    ],

    // ...(中略)

}

includemodulesの最後の行にそれぞれcolorのマニフェストパスとモジュールパスを追加しました。これでC言語で記述されたメソッドを呼び出したモジュールの呼び出す準備は完了です。

stackchan/main.ts

main.tsの変更点はありませんが、MODの仕組みを理解するためにちょっと寄り道します。

(一部抜粋)

async function main() {
    await asyncWait(100)
  await checkAndConnectWiFi().catch((msg) => {
    trace(`WiFi connection failed: ${msg}`)
  })
  let { onRobotCreated, onLaunch } = defaultMod
  if (Modules.has('mod')) {
    const mod = Modules.importNow('mod') as StackchanMod
    onRobotCreated = mod.onRobotCreated ?? onRobotCreated
    onLaunch = mod.onLaunch ?? onLaunch
  }
  const shouldRobotCreate = await onLaunch?.()
  if (shouldRobotCreate !== false) {
    const robot = createRobot()
    await onRobotCreated?.(robot, globalThis.device)
  }
}

冒頭の説明でonRobotCreatedという関数内に処理を記述すると言いましたが、実際にmain.ts内のmain関数でonRobotCreatedを呼び出されていることが分かります。

ここでは拡張子が、.jsではなく、.tsになっています。これはスクリプトがTypeScriptで書かれているためです。ModdableではTypeScriptでも開発ができるのですが、本記事の趣旨から外れるためここでは気にしなくて大丈夫です。

Modの構成

それでは、Modを作っていきましょう。

stack-chan/firmware/mods配下に作成します。
そして、全体の構成からMod配下のものだけ切り取ったものがこちらです。

stack-chan/firmware/mods
├── device-specific-color
│   ├── manifest.json
│   └── mod.js
└── (その他のモジュール郡)

行うことは3つです

  • onRobotCreated()という関数内に処理を記述する。
  • mod.jsというファイル内でonRobotCreatedを記述する。
  • メタデータとしてマニフェストファイルを作成する。

 

device-specific-color/mod.js
import { getDeviceSpecificColor } from 'color'

export function onRobotCreated(robot) {
    /**
     * Change color
     */
    const [r, g, b] = getDeviceSpecificColor()
    robot.setColor('secondary', r, g, b)
}

最初に想定したように、robot.setColorを呼ぶプログラムを記述しただけです。
モジュールcolorのメソッドgetDeviceSpecificColorimportしています。

device-specific-color/manifest.json
{
    "include": [
        "$(MODDABLE)/examples/manifest_mod.json"
    ],
    "modules": {
        "*": [
            "./mod"
        ]
    }
}

Modを作成するには$(MODDABLE)/examples/manifest_mod.jsonincludeし、自身のmod.jsをモジュールとして登録しています(mod.js以外にもJavaScriptファイルがあればそれも記述)。

colorモジュールはHost側に含まれているため、ここでは設定不要です。逆に書いてしまうと「C言語のコードはModに含められません」という旨のエラーが出てModのビルドができないので注意しましょう。私はここで引っかかりました。

スタックチャンとして書き込み

作成したプログラムは以下の方法で書き込めます。

# M5Stack Basic/Gray/Fireの場合
$ npm run debug                                            # Host側の書き込み
$ npm run mod ./mods/device-specific-color/manifest.json   # Mod側の書き込み

# M5Stack Core2の場合
$ npm run debug --target=esp32/m5stack_core2
$ npm run mod --target=esp32/m5stack_core2 ./mods/device-specific-color/manifest.json

# M5Stack CoreS3の場合
$ npm run debug --target=esp32/m5stack_cores3
$ npm run mod --target=esp32/m5stack_cores3 ./mods/device-specific-color/manifest.json

完成

私だけのスタックチャン

無事動作しました。
これで私だけのスタックチャンの色を拝むことができました。
カワイイ ヤッター !!

あなたも書き込んでみて自分のスタックチャンの色を確認して上げてください。

もしよろしければ、普段書き込んでいるプログラムの冒頭で今回の色変更プログラムを書き込んで動作させてくれると嬉しいです。

Moddableを使ってみた所感

スタックチャンキット開発プロジェクトで初めてModdableを触ってみました。以下、Moddableへの所感です。

Moddablでスタックチャンを開発する良さは、なんと言ってもJavaScriptで動かせることです。一般的な組み込みやロボットの記述言語として使われるC/C++に触れていなくても動かせるのは、広い業界の人に触ってもらえる可能性を秘めています。Moddableで開発して、あなたのスタックチャンも多くの人に触ってもらえるかもしれません。

今回使用したXS in Cもパフォーマンスを出したい場合や過去にC/C++で記述したコード資産を使いまわして開発したいという要望を実現するために有効でしょう。

一方デメリットとして、Moddableはまだまだ情報が少ないです。(開発中詰まったときもししかわさんに質問しながら進めていました)。また、モジュールの呼び出し時に構造や構文が間違っていた際、再起動を繰り返してエラーが出力されないために原因特定が難しかった場面もありました。

皆さんがスタックチャンを介してModdableを利用し、多くの情報を発信していただけると幸いです。

MACアドレスの例で説明している通り、Moddableではesp-idfの全ての機能をカバーできているわけではありません。今回紹介したXS in Cを使ってModdableの機能を拡張し、スタックチャンでできることを増やしていければスタックチャンコミュニティの発展にも繋がります。ぜひModdableでスタックチャンを開発してみてください。

ここまで読んでいただきありがとうございました。

それでは良いスタックチャンライフを!

メリークリスマス!!

タイトルとURLをコピーしました