Node.js
bluetooth
Scratch
Electron
microbit

micro:bit を Scratch 2 + Bluetooth で使う(まとめ編)

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

Scratch 2 オフラインエディタから Bluetooth LE で BBC micro:bit とやりとりする方法について、Scratch 3 までは待てない!ということで、<調査編>で検討しました。

これをもとに、s2microbit-ble s2microbit-ble.pngというヘルパーを Electron で作成してみました。

Windows/Macなど別のOSでもビルドできます。
動作例とシステム構成、データやり取りの実装部分をまとめます。

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

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

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

  1. micro:bit の加速度センサ値を Scratch 2 のレポーターブロック 「加速度センサ X」 で受け取る
  2. Scratch 2 のコマンドブロック 「文字列を表示: Hello!」 で指定した文字列を micro:bit へ送る

についてまとめます。

system-configuration.png

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

なお、以下で出てくるコードは、s2microbit-ble のメインプロセスのコードを一部抜粋・編集したものです。

micro:bit との Bluetooth 通信用 API

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

node-bbc-microbit の APIの詳しい説明はこちらにあります。使うときは以下のように require しておきます。

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

Scratch 2 との通信用HTTPサーバ

Scratch 2 オフライン版では、ブロック拡張では HTTP のリクエストを使っています。Scratch 2 からは GET してくるだけなので、ヘルパー側ではそれに対する応答を追加していく形です。今回は Express を使うことにします。詳しくはこちらの解説を参考にしてください。ポートを 50209 にしているのは、MrYsLab の 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 のレポーターブロック(「加速度センサ X」 など)を作る流れをまとめます。センサ値を if 文で処理して、たとえば 「右にかたむいている」 のような、true/false の二値を返すようなレポーターブロック (boolean block) とすることもできます。

system-configuration_m2s.png

(1) node-bbc-microbit の examples/accelerometer-listener.js を参考にします。はじめに discover しておく必要があります。APIの説明にあるように、周辺のすべての 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回ぐらいらしいですが、ほかの処理が重いともう少し遅くなっているかもしれません。

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) で定義しておきます(詳しくはこちら)。

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

Scratch側から micro:bit(もしくは中間のヘルパー)に指示を出すにはコマンドブロック(「文字列を表示: Hello!」 など)を使います。

system-configuration_s2m.png

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

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 を参考にいろいろ追加してみました。一方、今回は子供のPCにすぐにインストールできるように Electron + electron-builder を使いましたが、ネイティブモジュールが絡む Electron のビルドは、慣れるまでがややこしかったです。

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