Scratch 2 と micro:bit を Bluetooth LE でつなぐ
Scratch (スクラッチ) 2 オフラインエディタから Bluetooth LE で BBC micro:bit (マイクロビット) とやりとりする方法について「Scratch 3 までは待てない!」ということで、<調査編>で検討しました。
これをもとに、「スマイビー (Smi:be)」という Electron アプリケーションを作成してみました。
(正式名称は s2microbit-ble ですが、読みにくいようなので短くしました。)
スマイビーを立ち上げておくと、Scratchから micro:bit の加速度・磁気・温度センサを読んだり、LEDマトリクスを操作したり、入出力ピンを使うことができます。ブルートゥースなのでPCからmicro:bitを遠隔操作したり、Scratchのコントローラーとして使うこともできます。(ブロックの詳しい説明)(デモプロジェクト)
Electronなので、Windows/Mac/(おそらくLinuxも)など別のOSでもビルドできます。
この記事では、システム構成、データやり取りの実装についてまとめます。
例:加速度センサを使った Scratch 2 用コントローラ
— memakura (@memakura) 2018年4月11日
micro:bit と Scratch 2 をつなぐための構成
ざっくり図で書くと以下のような構成になっています。ヘルパーはこの中心の灰色の四角です。Electron のアプリケーションなので、メインプロセスとレンダラプロセスから成ります。今回はメインプロセスの中身について、データ受け渡し関係を中心に
についてまとめます。
s2microbit-ble のインストール方法やブロックの使用方法、Electron を使ったインストーラのビルド方法については以下で解説しています。
- s2microbit-ble のインストールや使用方法
- micro:bit を Scratch 2 + Bluetooth で使う(ビルド編1:noble-uwp)
- [micro:bit を Scratch 2 + Bluetooth で使う(ビルド編2:Electron + noble-uwp)]
(https://qiita.com/memakura/items/dc5cf2ff39d24ceb53ff)
なお、以下で出てくるコードは、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 しておきます。
const BBCMicrobit = require('bbc-microbit');
Scratch 2 との通信用HTTPサーバ
Scratch 2 オフライン版では、ブロック拡張では HTTP のリクエストを使っています。Scratch 2 からは GET してくるだけなので、ヘルパー側ではそれに対する応答を追加していく形です。今回は Express を使うことにします。詳しくはこちらの解説を参考にしてください。ポートを 50209 にしているのは、MrYsLab の s2m との互換性を残すためです。
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 しておきます。
// 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 に以下のコードを追加しておきます。
<body>
...略...
<script>
const { ipcRenderer } = require('electron');
ipcRenderer.on('mainmsg', function (event, msg) {
console.log("[main] " + msg);
});
</script>
</body>
1. micro:bit の加速度センサ値を Scratch 2 のレポーターブロックで受け取る
センサ値を受け取るような Scratch のレポーターブロック( など)を作る流れをまとめます。センサ値を if 文で処理して、たとえば のような、true/false の二値を返すようなレポーターブロック (boolean block) とすることもできます。
(1) node-bbc-microbit の examples/accelerometer-listener.js を参考にします。はじめに discover しておく必要があります。APIの説明にあるように、周辺のすべての micro:bit を見つける関数や、ID を指定する方法などがあります。今回ははじめに見つかった micro:bit をに接続することにします。
見つかれば connectAndSetUp
を呼んで接続し、続いて writeAccelerometerPeriod
でセンサ値の取得周期を設定します。160の単位はミリ秒です。さらに、subscribeAccelerometer
で加速度の値が変化した際にイベント'accelerometerChange'
が emit されるようにしておきます。他のセンサや I/O Pin の設定なども同様に行えます。
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
を使って小数点以下二桁にします。
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回ぐらいらしいですが、ほかの処理が重いともう少し遅くなっているかもしれません。
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(もしくは中間のヘルパー)に指示を出すにはコマンドブロック( など)を使います。
コマンドブロックの作成は非常に簡単で、Scratch 2 からの GET リクエストが来た時に処理を書いておくだけです。以下の例では、bbc-microbit API の writeLedText
を使って、micro:bit の LED にスクロールテキストを表示します。20文字までというのは API の仕様です。
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 を振ってます!