MakeCodeによりmicro:bit ver2.21にBLEサービスを実装し、Windows PCのChromeブラウザで動作するWebBluetoothを使って、micro:bitの加速度センサー情報を表示し、ブラウザに表示されたボタンの操作により、micro:bitのLEDを点灯します。

micro:bitの仕様

micro:bitのサービスのUUIDは「micro:bit BlueToothプロファイル」に示します。

加速度センサーは下記図に示す通り、XYZ軸の加速度を-2G~+2Gの範囲で知ることができます。次に「micro:bit BlueToothプロファイル」から引用を示します。
Contains accelerometer measurements for X, Y and Z axes as 3 signed 16 bit values in that order and in little endian format. X, Y and Z values should be divided by 1000.

  • X軸:-2032~2048 右に傾けるとプラス、左に傾けるとマイナス
  • Y軸:-2032~2048 手前に傾けるとプラス、奥に傾けるとマイナス
  • Z軸:-2048~2032 上(LED表面)がプラス、下(裏面)がマイナス

LEDマトリックスについては、

Revised 5 byte representation of the LED Matrix:
Octet 0, LED Row 1: bit4 bit3 bit2 bit1 bit0
Octet 1, LED Row 2: bit4 bit3 bit2 bit1 bit0
Octet 2, LED Row 3: bit4 bit3 bit2 bit1 bit0
Octet 3, LED Row 4: bit4 bit3 bit2 bit1 bit0
Octet 4, LED Row 5: bit4 bit3 bit2 bit1 bit0

MakeCodeによるmicro:bitのBLEサービスの実装

micro:bitのMakeCodeには、Bluetoothのブロックが標準(の拡張機能)で用意され、これらのBLEサービス(ペリフェラル)を使ってプログラミングできます。

MakeCodeでは、独自の拡張機能を開発することが可能です。もちろん、標準(の拡張機能)にはないBLEサービスの開発と追加も可能です。詳細については「MakeCodeでmicro:bitのBLEサービスを実装したい」を参照します

  1. Microsoft MakeCode for micro:bit 」のエディタをブラウザから開きます。
  2. 「読み込み」ボタンとクリックして、ダイアログを表示します。
  3. 「ファイルを読み込む」選択します。
  4. microbit_test.hex」からダウンロードしたZipファイルを解凍し、解凍したファイルをドラッグアンドドロップすると、サンプルコードが展開されます。サンプルコードを整形し、各イベントごとにLED表示を次のように変更します。
  5. 歯車のアイコンから、「プロジェクトの設定」を選択します
  6. “No Pairing Required:Anyone can connect via Bluetooth”にチェックし、「保存」を選択します
  7. 「ダウンロード」ボタンをクリックし、作成したコードをmicro:bitに書き込みます

WebBluetoothを使ったmicrobitアプリの作成

作成したmicrobitアプリを次に示します。

index.html

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.0/font/bootstrap-icons.css">

    <title>micro:bit BLE Interface</title>
</head>

<body>
    <div class="container">

        <div class="row justify-content-center">
            <div class="col-10 pt-2 mb-2">
                <h3>micro:bit BLE Interface</h3>
                <div id="error">Error : </div>
            </div>
        </div>

        <div class="row justify-content-center bg-light">
            <div class="col-6  pt-2">
                <div id="device_name">Device Name : </div>
                <div id="connect_status">Status : Disconnect</div>

                <div class="mt-2">
                    <button type="button" class="btn btn-secondary scanBtn">Connect</button>
                    <button type="button" class="btn btn-secondary resetBtn">Disconnect</button>
                </div>
            </div>

            <div class="col-6 pt-2">
                <div id="read_data">加速度 :</div>
                <div class="col-12 pt-2">
                    <button type="button" class="btn btn-secondary notifyBtn">Notify Start</button>
                </div>

                <div class="col-12 pt-2 mb-2">
                    <div>
                        <button type="button" class="btn btn-secondary commandBtn1">LED0</button>
                        <button type="button" class="btn btn-secondary commandBtn2">LED1</button>
                        <button type="button" class="btn btn-secondary commandBtn3">LED2</button>
                    </div>
                </div>
            </div>

        </div>
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
            integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
            crossorigin="anonymous"></script>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

        <script type="module" src="./microbit.js"></script>

</body>

</html>

microbit.js

import microbit_ble from './microbit_ble.js';

let Device_ble = new microbit_ble();

/*/////////////////////////////////
console.log("float test05");

function toFloat(v) {
    var a = new ArrayBuffer(4),
        b = new Uint32Array(a),
        f = new Float32Array(a);
    b[0] = v;
    return f[0];
}

console.log(toFloat(0x419A0000)); // -> 19.25
console.log(toFloat(0x41FC51EC)); // -> 31.54
console.log(toFloat(0x429DEB85)); // -> 78.96

////////////////////////////////*/

//Connectボタンの処理
const scanBtn = document.querySelector('.scanBtn');
scanBtn.addEventListener('click', function (clickEvent) {
    //this.EGRequest = 0; //暫定的に記載
    Device_ble.Blecan();
})

//コマンド書き込み
const onBtn1 = document.querySelector('.commandBtn1');
onBtn1.addEventListener('click', function (clickEvent) {
    //セットしたコマンドを送信
    Device_ble.Write(Device_ble.CustomLed_Characteristic_UUID, Device_ble.Command1);
})

const onBtn2 = document.querySelector('.commandBtn2');
onBtn2.addEventListener('click', function (clickEvent) {
    //セットしたコマンドを送信
    Device_ble.Write(Device_ble.CustomLed_Characteristic_UUID, Device_ble.Command2);
})

const onBtn3 = document.querySelector('.commandBtn3');
onBtn3.addEventListener('click', function (clickEvent) {
    //セットしたコマンドを送信
    Device_ble.Write(Device_ble.CustomLed_Characteristic_UUID, Device_ble.Command3);
})
//データ通知
const notifyBtn = document.querySelector('.notifyBtn');
notifyBtn.addEventListener('click', function (clickEvent) {
    Device_ble.StartNotify(Device_ble.CustomAcc_Characteristic_UUID);
})

//Disconnectボタンの処理
const resetBtn = document.querySelector('.resetBtn');
resetBtn.addEventListener('click', function (clickEvent) {
    Device_ble.Reset();
})

//HTMLに値を表示

Device_ble.onScan = function (deviceName) {
    document.getElementById('device_name').innerHTML = 'Device Name : ' + deviceName;
    document.getElementById('error').innerHTML = "Error : ";
}
Device_ble.onConnectGATT = function () {
    document.getElementById('connect_status').innerHTML = 'Status : Connected';
    document.getElementById('error').innerHTML = "Error : ";
};
Device_ble.onWrite = function () {
    document.getElementById('connect_status').innerHTML = 'Status : Sended command'
    document.getElementById('error').innerHTML = "Error : ";
}

Device_ble.onData = function (data) {
    document.getElementById('read_data').innerHTML = '加速度 : ' + data;
    document.getElementById('error').innerHTML = "Error : ";
}
Device_ble.onError = function (error) {
    document.getElementById('error').innerHTML = 'Error : ' + error;
}
  • 79-81行目でmicro:bitからの加速度データを変換しています。

microbit_ble.js

export default class {
    //constructor
    constructor() {
        this.device = null;
        this.server = null;
        this.serviceacc = null;
        this.serviceled = null;
        this.Commanduuid = null;
        this.Readuuid = null;
        this.Notifyuuid = null;

        this.ServiceAcc_UUID = "e95d0753-251d-470a-a062-fa1922dfa9a8";
        this.CustomAcc_Characteristic_UUID = "e95dca4b-251d-470a-a062-fa1922dfa9a8";

        this.ServiceLed_UUID = "e95dd91d-251d-470a-a062-fa1922dfa9a8";
        this.CustomLed_Characteristic_UUID = "e95d7b77-251d-470a-a062-fa1922dfa9a8";

        // micro:bit LED Data
        this.Command1 = [[0, 0, 0, 0, 0]];
        this.Command2 = [[0x8, 0, 0, 0, 0]];
        this.Command3 = [[0, 0, 0, 0, 0x8]];

        this.alpsoptions = {
            acceptAllDevices: true,
            optionalServices: [this.ServiceAcc_UUID, this.ServiceLed_UUID],
        };
    }

    async Blecan() {
        try {
            console.log('Requesting Bluetooth Device...');
            this.device = await navigator.bluetooth.requestDevice(this.alpsoptions);
            this.onScan(this.device.name);
            this.onConnectGATT();

            this.server = await this.device.gatt.connect();
            console.log('Getting GAP Service03...');
            this.serviceacc = await this.server.getPrimaryService(this.ServiceAcc_UUID);
            this.serviceled = await this.server.getPrimaryService(this.ServiceLed_UUID);
            console.log('Got Service');
        } catch (error) {
            this.onError(error);
            console.log('Argh! ' + error);
        }
    }

    async Write(uuid, moveCommand) {
        try {
            console.log('Execute01 : Write ' + uuid);
            this.Commanduuid = await this.serviceled.getCharacteristic(uuid);
            //this.Readuuid = await this.service.getCharacteristic(this.Custom1_Characteristic_UUID);

            for (let i = 0; i < moveCommand.length; i++) {
                //console.log(moveCommand[i]);
                let uint8array = new Uint8Array(moveCommand[i].length);
                for (let Offset = 0; Offset < moveCommand[i].length; Offset++) {
                    uint8array[Offset] = moveCommand[i][Offset];
                }
                //console.log(uint8array);
                await this.Commanduuid.writeValue(uint8array, true);
            }
            this.onWrite();

        }
        catch (error) {
            this.onError(error);
            console.log('Argh! ' + error);
        }
    }

    DataChanged(event) {
        console.log('Execute06 : DataChanged');

        try {
            let AcceleratorX = 0;
            let AcceleratorY = 0;
            let AcceleratorZ = 0;

            AcceleratorX = event.target.value.getInt16(0, true) / 1000.0;
            AcceleratorY = event.target.value.getInt16(2, true) / 1000.0;
            AcceleratorZ = event.target.value.getInt16(4, true) / 1000.0;

            console.log(AcceleratorX + ' ' + AcceleratorY + ' ' + AcceleratorZ);
            this.onData("x=" + AcceleratorX +
                " y=" + AcceleratorY +
                " z=" + AcceleratorZ
            );
        }
        catch (error) {
            this.onError(error);
            console.log('Argh! ' + error);
        }

    }

    async StartNotify(uuid) {
        try {
            console.log('Execute03 : StartNotify ' + uuid);
            this.Notifyuuid = await this.serviceacc.getCharacteristic(uuid);
            this.Notifyuuid.addEventListener('characteristicvaluechanged', this.DataChanged.bind(this));
            await this.Notifyuuid.startNotifications();
        }
        catch (error) {
            this.onError(error);
            console.log('Argh! ' + error);
        }
    }

    async StopNotify() {
        try {
            console.log('Execute : StopNotify');
            await this.Notifyuuid.stopNotifications();
        }
        catch (error) {
            this.onError(error);
            console.log('Argh! ' + error);
        }
    }

    async Disconnect() {
        if (!this.device) {
            var error = "No Bluetooth Device";
            console.log('Error : ' + error);
            this.onError(error);
            return;
        }

        if (this.device.gatt.connected) {
            console.log('Execute : disconnect');

            this.isNotify = false;
            this.device.gatt.disconnect();
        } else {
            var error = "Bluetooth Device is already disconnected";
            console.log('Error : ' + error);
            this.onError(error);
            return;
        }
    }

    Clear() {
        console.log('Excute : Clear Device and Characteristic');
        this.device = null;
    }

    //reset
    Reset() {
        console.log('Excute : reset');
        this.Disconnect(); //GNDisconnect() is not Promise Object
        this.Clear();
    }
}

WebBluetoothを使ったmicrobitアプリの実行

  1. ブラウザから「http://localhost/microbit/」でアクセスすると、作成したmicrobitアプリが実行され、次のように表示されます。
  2. 「Connect」ボタンをクリックすると次のようなダイアログが表示されます。
  3. 「BBC micro:bit」をクリックして、「ペア設定」ボタンを押すと、次のように表示されます

  4. micro:bitのLEDマトリックスが次のように表示されます。
  5. 「Notify Start」ボタンを押すと、「加速度:」の項目に次のように表示されます。
  6. 「LED1」ボタンを押すと、micro:bitのLEDマトリックスが次のように表示されます。
  7. Chromeの「デベロッパーツール」の「コンソール」には次のようにメッセージが表示されます。

エラーメッセージ:Add the service UUID to ‘optionalServices’ in requestDevice() options

micro:bitの加速度センサー情報取得とLEDマトリックスの点灯のために、今回作成したmicrobitアプリでは、2種類のサービスUUIDを使ってBLEを制御する必要があり、この時に上記のエラーメッセージが発生しました。
正常に動作させるためには、サービスUUIDを、上記「microbit_ble.js」ファイルの23-26行目のように定義し、32行目の「navigator.bluetooth.requestDevice」関数を呼び出す必要があります(詳細については「Web Bluetooth」を参照します)。
39行目で「server.getPrimaryService」関数により、加速度センサの他に、LEDのサービスUUIDのインスタンスを取得しています。