MakeCode for M5Core2 を作った

前回、M5Atom上で、MakeCode for micro:bit で作ったプログラムを動かしました。
[MakeCode for micro:bitで作ったプロジェクトをM5Atomで動かす](https://qiita.com/poruruba/items/8feed48b7dccb218f29b)

今度は、M5Core2上で動かしてみます。
具体的には、M5Core2のディスプレイ上に、Scratch3.0のネコを表示させ、M5Core2のタッチボタンのAとCボタンで左右に動かしたり、Bボタンでネコを回転させたりします。
それらの実装を、MakeCode for micro:bitでビジュアルプログラミングします。
やっていることは、MakeCodeで作ったものをJavaScriptでとりだして、ESP32で動くJavaScriptインタプリタQuickJSで動かしています。

細かいことは、ソースコードを見ていただくとして、ぜひ使ってみてください。
もろもろのソースコードを以下に置いています。

poruruba/M5Core2_MakeCode
　https://github.com/poruruba/M5Core2_MakeCode
poruruba/M5Core2_CustomBlock
　https://github.com/poruruba/M5Core2_CustomBlock

#用意するもの

以下を用意します。

* M5Core2 とUSB-Cケーブル
* SDカード(FAT32でフォーマット済)
* Visual Studio Code＋PlatformIO

#準備

まずは一式ダウンロードしてZIP展開。
https://github.com/poruruba/M5Core2_MakeCode

次に、M5Core2にSDカードを挿入しておきます。FAT32でフォーマットしておいて下さい。
Visual Studio Codeから、以下のフォルダを開きます。

　M5Core2_MakeCode-master\Arduino\M5Core2_MakeCode

そして、platformio.iniのポート番号を、実際のM5Core2が接続された仮想COMポートの番号に合わせる。

```ini:Arduino\M5Core2_MakeCode\platformio.ini
upload_port = COM8
monitor_port = COM8
```

WiFiアクセスポイントのSSIDとパスワードに変更。WiFiは、MakeCode for micro:bitで作ったJavascriptを登録するために使っています。

```cpp:Arduino\M5Core2_MakeCode\src\main.cpp
const char *wifi_ssid = "【WiFiアクセスポイントのSSID】";
const char *wifi_password = "【WiFiアクセスポイントのパスワード】";
```

最後に、M5Core2に書き込みます。

これで、Scratch3.0のネコが表示されるかと思います。
以下の画像が、M5Core2に表示されます。
 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/ce1af887-4915-2096-0462-2b6a716476cb.png)

M5Core2のタッチボタンを押してみてください。ネコが、左側のボタンを押すと左に、右側のボタンを押すと右に動くかと思います。真ん中のボタンを押すと、ネコが回転します。ついでに、LEDをチカチカさせていますし、ネコをタッチするとバイブレーションが動くようにしました。

#MakeCodeプロジェクトの作成

先ほど動かしたのは、以下のJavascriptです。

```js:Arduino\M5Core2_MakeCode\rom\main.js
let led2 = 0
custom.setInterval(500, function () {
    custom.led_onoff(led2)
    led2 = (led2 + 1) % 2
})
custom.onButtonPressed(ExButton.C, function () {
    custom.player_step(10, 0)
})
custom.onButtonPressed(ExButton.B, function () {
    custom.player_rotate(ExRotate.RIGHT, 10)
})
custom.onButtonPressed(ExButton.A, function () {
    custom.player_step(-10, 0)
})
custom.onPlayerTouched(function () {
    custom.shock_play(100)
})
```

これは、あらかじめMakeCode for micro:bitで作成したものを、以下のようにして埋め込んでおいたものです。

```ini:Arduino\M5Core2_MakeCode\platformio.ini
board_build.embed_txtfiles =
	rom/main.js
```

次は、これをもう一度MakeCodeで作成してみます。

MakeCode for micro:bitをブラウザから開きます。

MakeCode for micro:bit
　https://makecode.microbit.org/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/79d110da-8519-31a1-a25c-72ec0e15b48f.png)

新しいプロジェクトを選択します。
適当な名前を付けます。例えば、test

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/6dd30379-d551-3c8c-fed2-80f67c079c6f.png)

プロジェクトが作成されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/5b08378f-4a1a-8735-901b-d62953832074.png)
 
次に、M5Core2用のカスタムブロックを取り込むため、拡張機能を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/7feb9ec3-24b6-5318-cefe-69a77264458a.png)

検索のところに、以下を入力します。

　https://github.com/poruruba/M5Core2_CustomBlock

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/b63aed1c-e932-0132-4285-58994ced1539.png)

M5Core2カスタムブロックを選択します。
そうすると、Customというのが増えているのがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/68b7529c-2f39-d8d3-ecfb-cdf497dee908.png)

M5Core2で便利なブロックを作っておきました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/b49eb4c1-c50b-172e-f04c-a2a1957bc125.png)

さきほどの拡張機能で取り込んできたファイル群の中にあります。
とはいっても、実は関数定義だけ。実体はArduino側に実装しています。

```ts:custom.ts
/**
 * このファイルを使って、独自の関数やブロックを定義してください。
 * 詳しくはこちらを参照してください：https://makecode.microbit.org/blocks/custom
 */

enum ExRotate{
    //% block="右回り"
    RIGHT,
    //% block="左回り"
    LEFT
}

enum ExDimension{
    //% block="X軸"
    X,
    //% block="Y軸"
    Y,
    //% block="Z軸"
    Z
}

enum ExDimension2d{
    //% block="X軸"
    X,
    //% block="Y軸"
    Y
}

enum ExRoll{
    //% block="ピッチ"
    PITCH,
    //% block="ロール"
    ROLL,
    //% block="ヨー"
    YAW
}

enum ExButton{
    //% block="A"
    A,
    //% block="B"
    B,
    //% block="C",
    C
}

/**
 * カスタムブロック
 */
//% weight=100 color=#0fbc11 icon="\uf0fe"
namespace custom {
    /**
     * タッチ
     */ 
    //% block block="このスプライトが押されたとき"
    export function onPlayerTouched(callback: () => void): void{
    }

    /**
     * ボタン押下
     */ 
    //% block block="%btn が押されたとき"
    export function onButtonPressed(btn: ExButton, callback: () => void): void{
    }

    /**
     * 繰り返し
     */ 
    //% block block="%msec msecごとに実行する"
    //% msec.defl=100
    export function setInterval(msec: number, callback: () => void): void{
    }

    /**
     * コスチューム
     */ 
    //% block block="コスチュームを %name にする"
    //% name.defl="costume1"
    export function costume_change(name: string): void{
    }

    /**
     * 背景
     */ 
    //% block block="背景を %name にする"
    //% name.defl="background1"
    export function background_change(name: string): void{
    }

    /**
     * 表示
     */ 
    //% block block="表示する"
    export function player_on(): void{
    }

    /**
     * 非表示
     */ 
    //% block block="隠す"
    export function player_off(): void{
    }

    /**
     * 歩く
     */ 
    //% block block="X座標を %x ずつ、Y座標を %y ずつ変える"
    export function player_step(x: number, y: number): void{
    }

    /**
     * 移動
     */ 
    //% block block="X座標を %x 、Y座標を %y にする"
    export function player_goto(x: number, y: number): void{
    }

    /**
     * 回転
     */ 
    //% block block="%direction に %degree 度回す"
    export function player_rotate(direction: ExRotate, degree: number): void{
    }

    /**
     * 角度
     */ 
    //% block block="%degree 度に向ける"
    export function player_angle(degree: number): void{
    }

    /**
     * 音再生
     */ 
    //% block block="%name の音を鳴らす"
    //% name.defl="cat"
    export function sound_play(name: string): void{
    }

    /**
     * バイブレーション
     */ 
    //% block block="%duration msec震わせる"
    export function shock_play(duration: number): void{
    }

    /**
     * 傾き
     */ 
    //% block block="%axis の傾き"
    export function direction(axis: ExDimension): number {
        return 0;
    }

    /**
     * 加速度
     */ 
    //% block block="%acc の加速度"
    export function acceleration(acc: ExDimension): number {
        return 0;
    }

    /**
     * 回転角
     */ 
    //% block block="%roll の回転角"
    export function rolling(roll: ExRoll): number {
        return 0;
    }

    /**
     * LED
     */ 
    //% block block="LED %onoff"
    export function led_onoff(onoff: number): void {
    }

    /**
     * コンソール
     */ 
    //% block block="%message をコンソール出力"
    export function console_log(message: string): void {
    }

    /**
     * 現在位置
     */ 
    //% block block="%dimension"
    export function player_position(dimension: ExDimension2d): number {
        return 0;
    }

    /**
     * 現在角度
     */ 
    //% block block="向き"
    export function player_direction(): number {
        return 0;
    }
}
```

カスタムブロックの仕様は以下が参考になります。
　https://makecode.com/defining-blocks

どんなカスタムブロックを作ったかというと、

* ネコの現在角度
* ネコの現在位置
* ネコの移動
* ネコの表示/非表示
* ネコのコスチュームの変更
* LEDのOnOff
* M5Core2の回転角
* M5Core2の加速度
* M5Core2の傾き
* バイブレーション
* 音声再生（メモリ不足中）
* 背景の変更
* XXXmsecごとに実行
* ボタンが押されたときに実行
* ネコがタップされたときに実行
* コンソール出力

それではさっそく作ってみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/ea54c7cb-4b16-8b9a-6c97-1f7f65c1fdfe.png)

ちょっと画像ではわかりにくいかもしれませんが、Javascriptを選択すると以下のように変わります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/d6fb00f1-a2da-e93d-f545-d65338fe6570.png)

このコードは以下に置いておきました。

　```scripts\cat_rolling.js```

実は、ブロックをマウスで作らなくても、このJavascriptをコピペしても作れます。

```js:scripts\cat_rolling.js
let x = 0
let y = 0
custom.setInterval(200, function () {
    x = custom.player_position(ExDimension2d.X) - custom.acceleration(ExDimension.X) / 2
    y = custom.player_position(ExDimension2d.Y) + custom.acceleration(ExDimension.Y) / 2
    if (x < 0) {
        x = 0
        custom.shock_play(100)
    }
    if (x >= 320) {
        x = 319
        custom.shock_play(100)
    }
    if (y < 0) {
        y = 0
        custom.shock_play(100)
    }
    if (y >= 240) {
        y = 239
        custom.shock_play(100)
    }
    custom.player_goto(x, y)
})
```

このデモは、M5Core2についているモーションセンサの値に従って、傾けている方向にネコを移動させるものです。もし画面の端にぶつかったら、M5Core2についているバイブレーションが動くようにしました。

#M5Core2に転送する。

実はM5Core2にWebSocketサーバを立ち上げておいたので、ブラウザからプログラムをいつでもアップロードできます。
アップロードするプログラムは、先ほど表示を切り替えたJavascriptです。

アップロードするためのWebページを以下に作っておきました。

　```html\upload\index.html```

これをブラウザで開きます。もしくは以下からもアクセスできるようにしておきました。

　https://poruruba.github.io/M5Core2_MakeCode/html/upload/

(ですが、よく考えたらM5Core2側はwssではなくwsなので、Chromeがエラーをはじくようです。その場合は、諦めてファイルからindex.htmlを開くか、[こちら](https://qiita.com/poruruba/items/d0f5ef3ac4d09cfad593)を参考にしてください。)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/4892d70a-6f6c-de09-ce81-f22227029305.png)

ws_urlに、M5Core2のURLを入力します。以下のような感じです。

　```ws://[M5Core2のIPアドレス]:81```

そして、Connectボタンを押下すると、M5Core2のWebSocketサーバと接続できます。
Javascriptをペーストして、Sendボタンを押下すると、JavascriptがM5Core2に送られ、Javascriptを実行するため、自身を再起動します。
内部的にはSDカードに書き込んでいます。

再起動すると、先ほどと同じように背景とネコが表示されます。
今度は、M5Core2を持ち上げて、上下左右に傾けてみてください。傾けた方向にネコが動くかと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/ce1af887-4915-2096-0462-2b6a716476cb.png)
 

#やり残したこと

・Javascriptエンジンに、QuickJSを使いました。どうしても、Javascriptの再読み込みができずに、自身を再起動させました。
　→ 解決しました。

・M5Core2のpsramInit()が動きませんでした。せっかく8MBもの広大なメモリがあるのに、使おうとすると、M5Core2がリブートしてしまいます。（どなたか助けてくれないでしょうか！！！）
メモリが増えれば、いろんなことができるのに。。。その影響で、サウンド再生が動けていないです。メモリが少ないため、再生しようとしたときにpsramInitを呼ぼうとしてリブート。。。

以上