自己紹介
はじめまして、ズズくんと申します。株式会社アールティのエンジニア1年目です。アールティでの「スタックチャンキット化プロジェクト」でソフトウェア開発をしています。
↓ししかわさんのポスト
スタックチャンキットの量産を開始します!
翻訳機能の都合で誤解を与えてしまいましたが「法人化」ではありません。アールティがM5Stackに量産を委託する形になります。 https://t.co/DUeNJNtIvw— スタックチャン/Stack-chan (@stack_chan) November 13, 2023
入社前からTwitterでスタックチャンの存在は知っていましたが、今年の7月に開催されたスタックチャンお誕生日会2023に足を運んで様々なスタックチャンを見て強く興味を持ちました。
多くの人が参加しているスタックチャンコミニティに私も参加させていただきますので、よろしくお願いします!!もし今回の記事で質問等ありましたら、スタックチャンのDiscordコミュニティ(kurasawa_zuzukun)やTwitter(@zuzukun1129)で気軽に声をかけてみてください!
あなただけのスタックチャン
スタックチャンはししかわさんが開発、公開している、手乗りサイズのスーパーカワイイコミュニケーションロボットです。
私も自分だけのスタックチャンを作りたいところですが、ハードウェアの改造は敷居が高いです。そこで、まずはソフトウェアで変更可能な色の変更をしてみることにしてみます。これならソフトの変更だけで済みます。さらに「自分だけの世界にひとつのスタックチャン」感を出すために、M5Stackのデバイスごとに固有の色を作れないかと考えました。
ししかわさんとのディスカッションの結果「M5StackのMACアドレスをもとにデバイス固有の色を生成する」というアイディアに至りました。
MACアドレスはデバイスごとに固有に割り振られた値なので、これを活用しようというわけですね!
それでは、私だけの色のスタックチャンを実装していきます!
↓本記事のソースコード全体をGitHubで公開しておきます。
スタックチャンとModdable
スタックチャンはArduinoやMicroPythonなどの様々なプラットフォームで開発されています。ししかわ版スタックチャンではModdableが使われています。
Moddable
とは組み込み開発のためのJavaScriptアプリケーション開発プラットフォームです。ESP32
やRaspberry Pi Pico
などのマイコンボード上でJavaScriptのコードを実行できます。
詳しくはスタックチャン開発者であるししかわさんのブログにて記述されています。
Moddable
版ファームウェアはArduino版に比べて利用者が少ないこともあり、今回はModdable
の啓蒙を兼ねてスタックチャンで開発する流れを紹介します。
環境構築
本記事では環境構築の説明は省略します。ししかわ版スタックチャンの環境構築のページが簡潔に説明してくれていますので、そちらをご覧ください。
Modでスタックチャンの顔の色を変える
Moddable
には、Mod
という仕組みがあります。スタックチャンを動かす基本のプログラムに付随して、私達が自由に動作をプログラムできます。
実際には、Mod
のonRobotCreated
という関数が、アプリの起動時に呼ばれます。つまり、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
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*
という構造体のポインタを介して行います。xsRsult
はxsMachine
のメンバですが、省略されており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の関数をつなげる役割を持ちます。
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.js
とmanifest.json
が追加されています。
これらは、先程完成させたモジュールを呼び出して実行するJavaScriptファイルと、そのメタデータが記述されたファイルです。
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.json
をinclude
します。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
配下に置いてあります。
スタックチャン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"
],
// ...(中略)
}
include
とmodules
の最後の行にそれぞれ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
を呼び出されていることが分かります。
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
のメソッドgetDeviceSpecificColor
をimport
しています。
device-specific-color/manifest.json
{
"include": [
"$(MODDABLE)/examples/manifest_mod.json"
],
"modules": {
"*": [
"./mod"
]
}
}
Mod
を作成するには$(MODDABLE)/examples/manifest_mod.json
をinclude
し、自身のmod.js
をモジュールとして登録しています(mod.js
以外にもJavaScriptファイルがあればそれも記述)。
スタックチャンとして書き込み
作成したプログラムは以下の方法で書き込めます。
# 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でスタックチャンを開発してみてください。
ここまで読んでいただきありがとうございました。
それでは良いスタックチャンライフを!
メリークリスマス!!