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

Scratch 2 オフラインエディタから Bluetooth LE で BBC micro:bit とやりとりする方法についてこちらの調査編で検討しました.

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

別のOSでもビルドできるかもしれませんが,ひとまずは Windows 10 用です.
動作例とシステム構成,データやり取りの実装部分をまとめます.

例:加速度センサを使った 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 を振ってます!

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.