micro:bit を Scratch 2 + Bluetooth で使う（まとめ編）

# Scratch 2 と micro:bit を Bluetooth LE でつなぐ

Scratch (スクラッチ) 2 オフラインエディタから Bluetooth LE で BBC micro:bit (マイクロビット) とやりとりする方法について「Scratch 3 までは待てない！」ということで、[<調査編>](https://qiita.com/memakura/items/6b74e01fba310ebf6381)で検討しました。

これをもとに、[「スマイビー (Smi:be)」<img width="48" height="48" alt="s2microbit-ble.png" src="https://qiita-image-store.s3.amazonaws.com/0/55780/5d80e550-43b3-417a-2a4c-cda8bf197e64.png">](https://memakura.github.io/s2microbit-ble/)という Electron アプリケーションを作成してみました。
（正式名称は s2microbit-ble ですが、読みにくいようなので短くしました。）

スマイビーを立ち上げておくと、Scratchから micro:bit の加速度・磁気・温度センサを読んだり、LEDマトリクスを操作したり、入出力ピンを使うことができます。ブルートゥースなのでPCからmicro:bitを遠隔操作したり、Scratchのコントローラーとして使うこともできます。[（ブロックの詳しい説明）](https://github.com/memakura/s2microbit-ble/wiki/)[（デモプロジェクト）](https://memakura.github.io/s2microbit-ble/00scratch/)

Electronなので、Windows/Mac/(おそらくLinuxも)など別のOSでもビルドできます。

この記事では、システム構成、データやり取りの実装についてまとめます。

## 例：加速度センサを使った Scratch 2 用コントローラ

<blockquote class="twitter-tweet" data-lang="ja"><p lang="und" dir="ltr"><a href="https://t.co/OPKCfPFAcp">https://t.co/OPKCfPFAcp</a></p>&mdash; memakura (@memakura) <a href="https://twitter.com/memakura/status/983902805801906178?ref_src=twsrc%5Etfw">2018年4月11日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


# micro:bit と Scratch 2 をつなぐための構成

ざっくり図で書くと以下のような構成になっています。ヘルパーはこの中心の灰色の四角です。[Electron](https://electronjs.org/) のアプリケーションなので、メインプロセスとレンダラプロセスから成ります。今回はメインプロセスの中身について、データ受け渡し関係を中心に

1. micro:bit の加速度センサ値を Scratch 2 のレポーターブロック <img width="100" alt="「加速度センサ X」" src="https://qiita-image-store.s3.amazonaws.com/0/55780/b17fbec0-de72-46a5-d189-788d86cc9297.png"> で受け取る
1. Scratch 2 のコマンドブロック <img width="110" alt="「文字列を表示: Hello!」" src="https://qiita-image-store.s3.amazonaws.com/0/55780/a6d1ba4a-905b-56ef-5aa5-831bed813730.png"> で指定した文字列を micro:bit へ送る

についてまとめます。

![system-configuration.png](https://qiita-image-store.s3.amazonaws.com/0/55780/d3e2316e-5ee3-a8c4-fb55-c78aa36b1e84.png)


s2microbit-ble のインストール方法やブロックの使用方法、Electron を使ったインストーラのビルド方法については以下で解説しています。

- [s2microbit-ble のインストールや使用方法](https://memakura.github.io/s2microbit-ble/)
- [micro:bit を Scratch 2 + Bluetooth で使う（ビルド編1：noble-uwp）](https://qiita.com/memakura/items/11a0426f9060da1ded7e)
- [micro:bit を Scratch 2 + Bluetooth で使う（ビルド編2：Electron + noble-uwp）]
(https://qiita.com/memakura/items/dc5cf2ff39d24ceb53ff)

なお、以下で出てくるコードは、[s2microbit-ble のメインプロセスのコード](https://github.com/memakura/s2microbit-ble/blob/master/app/s2microbit-ble.js)を一部抜粋・編集したものです。


## micro:bit との Bluetooth 通信用 API

[node-bbc-microbit](https://github.com/sandeepmistry/node-bbc-microbit) を使っています。作者は [sandeepmistry](https://github.com/sandeepmistry) で、Node.js でBluetooth Low Energy を扱うためのモジュールとしてよく知られている [noble](https://github.com/noble/noble) のメインコントリビューターです。Web Bluetooth API もありますが、今回はWindows PCで動くHTTPサーバを作成する必要があるため、noble を使うことにしました。

node-bbc-microbit の APIの詳しい説明は[こちら](https://github.com/sandeepmistry/node-bbc-microbit/blob/master/API.md)にあります。使うときは以下のように require しておきます。

```main.js
const BBCMicrobit = require('bbc-microbit');
``` 

## Scratch 2 との通信用HTTPサーバ

Scratch 2 オフライン版では、ブロック拡張では HTTP のリクエストを使っています。Scratch 2 からは GET してくるだけなので、ヘルパー側ではそれに対する応答を追加していく形です。今回は Express を使うことにします。詳しくは[こちらの解説](https://qiita.com/memakura/items/ca1fff4d7dd33164bb7e)を参考にしてください。ポートを 50209 にしているのは、[MrYsLab の s2m](https://mryslab.github.io/s2m/) との互換性を残すためです。

```main.js
const express = require('express');
let exapp = express();
let exserver = null;

function startHTTPServer(){
  exserver = exapp.listen(50209, function(){
    console.log("Server started... listening port " + exserver.address().port);
  });
}
// 適当な場所で
if (exserver === null) {
  startHTTPServer();
}
```

## Electron のメイン・レンダラプロセス間通信

エラーなどはせめて DevTools の Console に表示してほしいです。メインプロセスとレンダラプロセス間の通信を使うために `ipcMain` を require しておきます。

```main.js
// Module to control application life.
// Module to communicate with renderer process
// Module to create native browser window.
const { app, ipcMain, BrowserWindow } = require('electron');
const path = require('path');
const url = require('url');

let mainWindow;

function createWindow () {
  mainWindow = new BrowserWindow({width: 800, height: 600});

  mainWindow.loadURL(url.format({
    pathname: path.join(__dirname, 'index.html'),
    protocol: 'file:',
    slashes: true
  }));
  // 'closed' などその他の処理も追加しておく
  // 後で説明する micro:bit の discover をここでやってもよい
});

// こんな感じでコマンドプロンプトとDevToolsの両コンソールに出すようにしておきます
function logBothConsole (msg) {
  console.log(msg);
  mainWindow.webContents.send('mainmsg', msg);
}
```

レンダラ側では Console にメッセージを出してもらうために、index.html に以下のコードを追加しておきます。

```index.html
  <body>
    ...略...
    <script>
      const { ipcRenderer } = require('electron');
      ipcRenderer.on('mainmsg', function (event, msg) {
        console.log("[main] " + msg);
      });
    </script>
  </body>
```


# 1. micro:bit の加速度センサ値を Scratch 2 のレポーターブロックで受け取る 

センサ値を受け取るような Scratch のレポーターブロック（<img width="100" alt="「加速度センサ X」" src="https://qiita-image-store.s3.amazonaws.com/0/55780/b17fbec0-de72-46a5-d189-788d86cc9297.png"> など）を作る流れをまとめます。センサ値を if 文で処理して、たとえば <img width="110" alt="「右にかたむいている」" src="https://qiita-image-store.s3.amazonaws.com/0/55780/5ce0775c-667f-5492-85a9-50eec7a620b8.png"> のような、true/false の二値を返すようなレポーターブロック (boolean block) とすることもできます。

<img width="600" alt="system-configuration_m2s.png" src="https://qiita-image-store.s3.amazonaws.com/0/55780/d0f1e7e7-79b0-b24a-8d71-ebd869acace1.png">

(1) [node-bbc-microbit の examples/accelerometer-listener.js](https://github.com/sandeepmistry/node-bbc-microbit/blob/master/examples/accelerometer-listener.js) を参考にします。はじめに discover しておく必要があります。[APIの説明にあるように](https://github.com/sandeepmistry/node-bbc-microbit/blob/master/API.md#discovery)、周辺のすべての micro:bit を見つける関数や、ID を指定する方法などがあります。今回ははじめに見つかった micro:bit をに接続することにします。

見つかれば `connectAndSetUp` を呼んで接続し、続いて `writeAccelerometerPeriod`でセンサ値の取得周期を設定します。160の単位はミリ秒です。さらに、`subscribeAccelerometer`で加速度の値が変化した際にイベント`'accelerometerChange'`が emit されるようにしておきます。他のセンサや I/O Pin の設定なども同様に行えます。

```main.js
BBCMicrobit.discover( function(microbit) { // 周辺の micro:bit を見つけて
  // 接続する
  microbit.connectAndSetUp(function() {
    logBothConsole('microbit: connected');
    // 加速度センサ値の取得周期を160ミリ秒に設定し、イベントの emit を有効化
    microbit.writeAccelerometerPeriod(160, function() {
      microbit.subscribeAccelerometer(function(error) {
        logBothConsole('microbit: subscribed to accelerometer');
      });
    });
  });
});   
```

(2) bbc-microbit から emit されたイベントに対するコールバックを作成し、センサ値を保存しておきます。図の Variables となっている部分です。ここでは `accelerometer` という名前の連想配列を使っています。なお、Scratch 2 で使う際には適度な精度でよいので、`toFixed`を使って小数点以下二桁にします。

```main.js
BBCMicrobit.discover( function(microbit) {
  microbit.on('accelerometerChange', function(x, y, z) {
    x = x.toFixed(2);
    y = y.toFixed(2);
    z = z.toFixed(2);
    accelerometer = { 'x': x, 'y': y, 'z': z };
  });
  // 以下は(1)で作成したもの
  microbit.connectAndSetUp(function() {
  ...
});
```


(3) Scratch 2 の GET /poll リクエストに対して、保存された値を返します。[ポーリングは毎秒30回ぐらいらしい](https://wiki.scratch.mit.edu/w/images/ExtensionsDoc.HTTP-9-11.pdf)ですが、ほかの処理が重いともう少し遅くなっているかもしれません。

```main.js
exapp.get('/poll', function(req, res) {
  var reply = '';
  reply += 'acc_x ' + accelerometer['x'] + '\n';
  reply += 'acc_y ' + accelerometer['y'] + '\n';
  reply += 'acc_z ' + accelerometer['z'] + '\n';
  res.send(reply);
});
```

`acc_x` などの文字列は、Scratch 2 のエディタで読み込む、ブロック拡張用のファイル (.s2e) で定義しておきます（詳しくは[こちら](https://qiita.com/memakura/items/ca1fff4d7dd33164bb7e)）。



# 2. Scratch 2 のコマンドブロックで指定した文字列を micro:bit へ送る

Scratch側から micro:bit（もしくは中間のヘルパー）に指示を出すにはコマンドブロック（<img width="120" alt="「文字列を表示: Hello!」" src="https://qiita-image-store.s3.amazonaws.com/0/55780/a6d1ba4a-905b-56ef-5aa5-831bed813730.png"> など）を使います。

<img width="600" alt="system-configuration_s2m.png" src="https://qiita-image-store.s3.amazonaws.com/0/55780/e3f95687-7e1a-0b69-f13a-4577220b36f9.png">

コマンドブロックの作成は非常に簡単で、Scratch 2 からの GET リクエストが来た時に処理を書いておくだけです。以下の例では、bbc-microbit API の `writeLedText` を使って、micro:bit の LED にスクロールテキストを表示します。20文字までというのは [API の仕様です](https://github.com/sandeepmistry/node-bbc-microbit/blob/master/API.md)。

```main.js
let device = null;
// 1 (1) に出てきた microbit.connectAndSetUp のコールバックの中で
//    device = microbit;
// されていると仮定

exapp.get('/scroll/:text', function(req, res) {
  if (device) {
    // text is a string that must be 20 characters or less
    var txt = req.params.text.substring(0, 20);
    device.writeLedText(txt, function(error) {
      logBothConsole('microbit: display ' + txt);
    });
  }
  res.send('OK');
});
```

# まとめ

Scratch 2 のブロックの拡張は思ったよりも簡単です。[MrYsLab の s2m](https://mryslab.github.io/s2m/) を参考に[いろいろ追加してみました](https://github.com/memakura/s2microbit-ble/wiki)。一方、今回は子供のPCにすぐにインストールできるように Electron + electron-builder を使いましたが、[ネイティブモジュールが絡む Electron のビルド](https://qiita.com/memakura/items/dc5cf2ff39d24ceb53ff)は、慣れるまでがややこしかったです。

Scratch 3 はもう少し先に考えるとして、Scratch 2 ではひとまずこれで様子を見ます。子供も早速、なにやら手裏剣？対戦ゲーム（一人はキーボード、一人はmicro:bit）を加速度センサで作っていました。USBケーブルから解放されて、ズバッと micro:bit を振ってます！