Instantly share code, notes, and snippets.

Embed
What would you like to do?
Use all microbits built-in sensor and LED via web bluetooth

micro:bit内蔵のすべてのセンサーやLEDをweb bluetooth経由で使ってみる

micro:bitは、子供向けの教育用ボードコンピュータですが、Bluetooth搭載で、たくさんのセンサーや表示装置も載っています。コストパフォーマンスも入手性も良いと思いますので、Bluetoothを用いたIoTのプロトタイピングで、ワイヤレスのセンサーなどとして便利に活用できると思います。

Code on micro:bit

  • https://makecode.microbit.org/_gzF1MhfcFP9P
  • 癖があるjavascriptの環境ですが、CHIRIMENによるIoTの学習的には他の小型のボードコンピュータ上の開発言語よりは言語や開発環境の統一の面で良いと思います

WebApps

  • ためしてみる
  • ソースは添付htmlを参照してください
  • microBitBLE.jsがmicro:bitのセンサなどをWebBluetooth経由で使うためのドライバライブラリです。
  • CHIRIMEN for Raspberry Pi3環境で動作確認
  • Bluetoothインターフェースが載ったコンピュータでも動作すると思います。
    • ブラウザはChromeや Chromiumなど、Web Bluetoothサポートブラウザが必要
    • MacやWindows, Linux PC
    • Androidスマホ、タブレット
  • セキュリティサンドボックス面で、結構ハマりポイントがある
    • https必須
    • requestDevice()では、optionalServicesの列挙が必須(ソース参照)
    • requestDevice()は、人による操作(buttonなど)を引き金にして呼び出される必要がある(らしい)(一回の操作から二回呼び出すのもNG)
    • UUIDはハイフンをしっかり入れる必要がある。

ToDo, ISSUES

  • micro:bitのGPIOやI2Cを使いこなすコードは別途・・・ webGPIO, webI2C over webBluetoothにしたいですね。
  • chrome/windows10では、microBitBLE.get* APIが全部うまく動いてない?(値が変わらない・・) WebBluetooth/Chrome/Windows10の実装がおかしい? (CHIRIMEN RPi3(というよりraspbian(linux)上のchromium)ではうまく動いています)
/**
micro:bit BLE driver for Chromium / WebBluetooth
Programmed by Satoru Takagi
History:
2018/12/26 1st release
2019/01/16 make closure : microBitBLE.*
Based on:
https://qiita.com/yokmama/items/5522fabfb5b9623278e2
https://koichii.github.io/microbit-web-bluetooth/
https://lancaster-university.github.io/microbit-docs/ble/profile/
https://lancaster-university.github.io/microbit-docs/resources/bluetooth/bluetooth_profile.html
https://relativelayout.hatenablog.com/entry/2018/02/03/013251
https://github.com/edrosten/bluez/blob/master/monitor/uuid.c (Huge Dicts!)
TBD:
Support IO PIN Service based on webGPIO etc.
ISSUES:
Perhaps because the perfection of webBluetooth / chrome is low, there are various problems with each OS.
Chrome/windows10: Since readValue () is not decent, it seems that get* APIs does not work.
**/
( function ( window , undefined ) {
var document = window.document;
var navigator = window.navigator;
var location = window.location;
var microBitDevice,LEDtext,LEDmatrix,accelerometer,magnetometer,temperature,buttonA,buttonB;
var microBitBLE = ( function(){
var microBitUUIDs = {
//micro:bit BLE UUID
/* BBC micro:bit Bluetooth Profiles */
"MicroBit Accelerometer Service": "e95d0753-251d-470a-a062-fa1922dfa9a8",
"MicroBit Accelerometer Data": "e95dca4b-251d-470a-a062-fa1922dfa9a8",
"MicroBit Accelerometer Period": "e95dfb24-251d-470a-a062-fa1922dfa9a8",
"MicroBit Magnetometer Service": "e95df2d8-251d-470a-a062-fa1922dfa9a8",
"MicroBit Magnetometer Data": "e95dfb11-251d-470a-a062-fa1922dfa9a8",
"MicroBit Magnetometer Period": "e95d386c-251d-470a-a062-fa1922dfa9a8",
"MicroBit Magnetometer Bearing": "e95d9715-251d-470a-a062-fa1922dfa9a8",
"MicroBit Button Service": "e95d9882-251d-470a-a062-fa1922dfa9a8",
"MicroBit Button A State": "e95dda90-251d-470a-a062-fa1922dfa9a8",
"MicroBit Button B State": "e95dda91-251d-470a-a062-fa1922dfa9a8",
"MicroBit IO PIN Service": "e95d127b-251d-470a-a062-fa1922dfa9a8",
"MicroBit PIN Data": "e95d8d00-251d-470a-a062-fa1922dfa9a8",
"MicroBit PIN AD Configuration": "e95d5899-251d-470a-a062-fa1922dfa9a8",
"MicroBit PWM Control": "e95dd822-251d-470a-a062-fa1922dfa9a8",
"MicroBit LED Service": "e95dd91d-251d-470a-a062-fa1922dfa9a8",
"MicroBit LED Matrix state": "e95d7b77-251d-470a-a062-fa1922dfa9a8",
"MicroBit LED Text": "e95d93ee-251d-470a-a062-fa1922dfa9a8",
"MicroBit Scrolling Delay": "e95d0d2d-251d-470a-a062-fa1922dfa9a8",
"MicroBit Event Service": "e95d93af-251d-470a-a062-fa1922dfa9a8",
"MicroBit Requirements" : "e95db84c-251d-470a-a062-fa1922dfa9a8",
"MicroBit Event Data": "e95d9775-251d-470a-a062-fa1922dfa9a8",
"MicroBit Client Requirements": "e95d23c4-251d-470a-a062-fa1922dfa9a8",
"MicroBit Client Events": "e95d5404-251d-470a-a062-fa1922dfa9a8",
"MicroBit DFU Control Service": "e95d93b0-251d-470a-a062-fa1922dfa9a8",
"MicroBit DFU Control": "e95d93b1-251d-470a-a062-fa1922dfa9a8",
"MicroBit Temperature Service": "e95d6100-251d-470a-a062-fa1922dfa9a8",
"MicroBit Temperature Data": "e95d9250-251d-470a-a062-fa1922dfa9a8",
"MicroBit Temperature Period": "e95d1b25-251d-470a-a062-fa1922dfa9a8",
/* Nordic UART Port Emulation */
"Nordic UART Service": "6e400001-b5a3-f393-e0a9-e50e24dcca9e",
"Nordic UART TX": "6e400002-b5a3-f393-e0a9-e50e24dcca9e",
"Nordic UART RX": "6e400003-b5a3-f393-e0a9-e50e24dcca9e"
}
var accelerometerCBF = null;
var magnetometerCBF = null;
var temperatureCBF = null;
var buttonACBF = null;
var buttonBCBF = null;
var accelerometerGate = null;
var magnetometerGate = null;
var temperatureGate = null;
var buttonAGate = null;
var buttonBGate = null;
async function connect(accelerometerCB, magnetometerCB, temperatureCB, buttonACB, buttonBCB){
if ( accelerometerCB ){
accelerometerGate = onAccelerometerValueChanged;
accelerometerCBF = accelerometerCB;
}
if ( magnetometerCB ){
magnetometerGate = onMagnetometerValueChanged;
magnetometerCBF = magnetometerCB;
}
if ( temperatureCB ){
temperatureGate = onTempChanged;
temperatureCBF = temperatureCB;
}
if ( buttonACB ){
buttonAGate = onBtnAChanged;
buttonACBF = buttonACB;
}
if( buttonBCB ){
buttonBGate = onBtnBChanged;
buttonBCBF = buttonBCB;
}
await connectMicroBit();
return ( true );
}
async function connectMicroBit(){
// connect Accelerometer
var characteristicSet = [];
characteristicSet.push({
serviceUUID:microBitUUIDs["MicroBit Accelerometer Service"],
dataUUID:microBitUUIDs["MicroBit Accelerometer Data"],
callback:accelerometerGate
});
characteristicSet.push({
serviceUUID:microBitUUIDs["MicroBit Magnetometer Service"],
dataUUID:microBitUUIDs["MicroBit Magnetometer Data"],
callback:magnetometerGate
});
characteristicSet.push({
serviceUUID:microBitUUIDs["MicroBit Button Service"],
dataUUID:microBitUUIDs["MicroBit Button A State"],
callback:buttonAGate
});
characteristicSet.push({
serviceUUID:microBitUUIDs["MicroBit Button Service"],
dataUUID:microBitUUIDs["MicroBit Button B State"],
callback:buttonBGate
});
characteristicSet.push({
serviceUUID:microBitUUIDs["MicroBit Temperature Service"],
dataUUID:microBitUUIDs["MicroBit Temperature Data"],
callback:temperatureGate
});
// haracteristics[5]
characteristicSet.push({
serviceUUID:microBitUUIDs["MicroBit LED Service"],
dataUUID:microBitUUIDs["MicroBit LED Text"],
});
// haracteristics[6]
characteristicSet.push({
serviceUUID:microBitUUIDs["MicroBit LED Service"],
dataUUID:microBitUUIDs["MicroBit LED Matrix state"],
});
var microBotBLE = await connectBLE(characteristicSet);
accelerometer = microBotBLE.characteristics[0];
magnetometer = microBotBLE.characteristics[1];
buttonA = microBotBLE.characteristics[2];
buttonB = microBotBLE.characteristics[3];
temperature = microBotBLE.characteristics[4];
LEDtext = microBotBLE.characteristics[5];
LEDmatrix = microBotBLE.characteristics[6];
microBitDevice = microBotBLE.device;
// console.log("LED chara:",LEDtext, LEDmatrix);
}
async function connectBLE(characteristicSet ) {
// characteristicSet = [{serviceUUID:serviceUUID, dataUUID:dataUUID, callBack:callBackFunc},...]
// というデータ構造を作って、投入する。
// 生成された.deviceおよび、上記に対応するcharacteristicが入った.characteristics配列が、返却される
// If requestDevice() is called without HM interaction (input button event etc) then throw exception
// If you already get device then set device option , for bypassing requestDevice()
var optionalServicesArray = [];
for ( var i = 0 ; i < characteristicSet.length ; i++ ){
optionalServicesArray.push(characteristicSet[i].serviceUUID);
}
try{
var device = await navigator.bluetooth.requestDevice({
filters: [{
namePrefix: 'BBC micro:bit',
}],
optionalServices: optionalServicesArray // Trap!!!: This option is mandatory
});
console.log("device", device);
var server = await (async function(device){
return device.gatt.connect();
})(device);
console.log("server", server)
var characteristics = [];
for ( var i = 0 ; i < characteristicSet.length ; i++ ){
var service = await (async function(server){
return server.getPrimaryService(characteristicSet[i].serviceUUID);
})(server);
console.log("service", service)
var chara = await (async function(service){
return service.getCharacteristic(characteristicSet[i].dataUUID);
})(service);
console.log("chara:", chara)
await (async function(chara){
// alert("BLE接続が完了しました。");
console.log("callBack?:",characteristicSet[i].callback, characteristicSet[i]);
if ( characteristicSet[i].callback ){
console.log("SET CALLVACK : : : ",characteristicSet[i].callback);
chara.startNotifications();
chara.addEventListener('characteristicvaluechanged',characteristicSet[i].callback);
} else {
console.log("NO CALLBACK, no NOTIFICATION");
// chara.addEventListener('characteristicvaluechanged',function(ev){console.log("no NOTIF:",ev)});
}
})(chara);
characteristics.push(chara);
}
return ( {device:device,characteristics:characteristics});
} catch(error){
alert("Connection Failed : ",error);
console.log(error);
}
}
function onAccelerometerValueChanged(event) {
var AcceleratorX = event.target.value.getInt16(0,true)/1000.0;
var AcceleratorY = event.target.value.getInt16(2,true)/1000.0;
var AcceleratorZ = event.target.value.getInt16(4,true)/1000.0;
accelerometerCBF({x:AcceleratorX, y:AcceleratorY, z:AcceleratorZ});
}
function onMagnetometerValueChanged(event) {
var MagnetometerX = event.target.value.getInt16(0,true)/1000.0;
var MagnetometerY = event.target.value.getInt16(2,true)/1000.0;
var MagnetometerZ = event.target.value.getInt16(4,true)/1000.0;
magnetometerCBF({x:MagnetometerX, y:MagnetometerY, z:MagnetometerZ});
}
function onBtnAChanged(event){
var bA = event.target.value.getInt8();
buttonACBF( bA );
}
function onBtnBChanged(event){
var bB = event.target.value.getInt8();
buttonBCBF( bB );
}
function onTempChanged(event){
var temperature = event.target.value.getInt8();
temperatureCBF( temperature );
}
function disconnect() {
if (microBitDevice && microBitDevice.gatt.connected){
microBitDevice.gatt.disconnect();
}
// alert("Connection closed");
}
function sleep(ms) {
return new Promise(function(resolve) {
setTimeout(resolve, ms);
});
}
function string_to_buffer(src) {
return (new Uint8Array([].map.call(src, function(c) {
return c.charCodeAt(0)
}))).buffer;
}
function setLEDtext(ptext){
console.log(ptext);
var msg = string_to_buffer(ptext);
LEDtext.writeValue(msg);
}
function setLEDmatrix( matrixData ){
LEDmatrix.writeValue(matrixData);
}
async function getAccelerometer(){
var val = await accelerometer.readValue();
console.log("called getAccelerometer:",val, accelerometer.value);
return {
x: val.getInt16(0,true)/1000.0,
y: val.getInt16(2,true)/1000.0,
z: val.getInt16(4,true)/1000.0,
}
}
async function getMagnetometer(){
var val = await magnetometer.readValue();
return {
x: val.getInt16(0,true)/1000.0,
y: val.getInt16(2,true)/1000.0,
z: val.getInt16(4,true)/1000.0,
}
}
async function getButtonA(){
var val = await buttonA.readValue();
return ( val.getInt8() );
}
async function getButtonB(){
var val = await buttonB.readValue();
return ( val.getInt8() );
}
async function getTemperature(){
var val = await temperature.readValue();
return ( val.getInt8() );
}
// GPIO: https://lancaster-university.github.io/microbit-docs/ble/iopin-service/
return { // Exposed APIs for microBitBLE.*
connect: connect,
disconnect : disconnect,
setLEDtext : setLEDtext,
setLEDmatrix : setLEDmatrix,
getAccelerometer : getAccelerometer,
getMagnetometer : getMagnetometer,
getButtonA : getButtonA,
getButtonB : getButtonB,
getTemperature : getTemperature,
sleep: sleep
}
})();
window.microBitBLE = microBitBLE;
})( window );
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1.0,maximum-scale=1.0" />
<title></title>
</head>
<script type="text/javascript" src="microBitBLE.js"></script>
<script>
async function connectMicroBit(){
// Notes on microBitBLE.connect()
// 使用にあたってまずこの関数を呼び出す。ただし、
// WebBluetoothの制約により、<input> buttonなど、Human-Interactionを起点にして呼び出される必要がある(らしい)
// 引数は、それぞれのセンサーのデータ更新時に呼び出されるコールバック関数(必要なければNull)
// 第1引数: 加速度
// 第2引数: 磁気
// 第3引数: 温度
// 第4引数: ボタンA
// 第5引数: ボタンB
// コールバック関数は一つの引数を持つ必要があり、この引数にセンサーデータが返される
// (磁気・加速度は引数.x,.y,.zに各軸の値が返される。以下参照)
await microBitBLE.connect(accelerometerCBF, magnetometerCBF, temperatureCBF, buttonACBF, buttonBCBF);
// await microBitBLE.connect(null,null,null,null,null); // 随時更新を使わない場合はこんな感じです
disc.disabled="";
}
function disconnectMicroBit(){
microBitBLE.disconnect();
disc.disabled="true";
}
function accelerometerCBF(val){
ax.innerHTML = val.x;
ay.innerHTML = val.y;
az.innerHTML = val.z;
}
function magnetometerCBF(val){
mx.innerHTML = val.x;
my.innerHTML = val.y;
mz.innerHTML = val.z;
}
function temperatureCBF(val){
tempera.innerHTML = val;
}
function buttonACBF(val){
btnA.innerHTML = ( val == 0 ? "OFF" : "ON");
}
function buttonBCBF(val){
btnB.innerHTML = ( val == 0 ? "OFF" : "ON");
}
function showText(){
// Notes on microBitBLE.setLEDtext()
// 引数には、表示したい文字列を入れる。言うまでもないですがアスキー文字のみ
var ptext = txt.value;
microBitBLE.setLEDtext(ptext);
}
function showMatrix( event ){
// Notes on microBitBLE.setLEDmatrix()
// 引数には、表示したいドットパターンをUint8Array(5)の形で入れる
// 1row(桁)を一つのUint8ビット列で
// それを5個~行に対応
console.log("called setLEDmatrix ", event);
matrix = [];
checkBoxes = event.target.parentElement.getElementsByTagName("input");
for ( var i = 0 ; i < checkBoxes.length ; i++ ){
matrix[i]=checkBoxes[i].checked;
}
console.log("LEDmat:",matrix);
var i = 0;
var matrixData = new Uint8Array(5);
for ( var row = 0 ; row < 5 ; row++ ){
var bit = 16;
for ( var col = 0 ; col < 5 ; col++ ){
if ( matrix[i] ){
matrixData[row] += bit;
}
bit /=2;
++i;
}
console.log("row:",row," : ",matrixData[row].toString(2));
}
console.log(matrixData);
microBitBLE.setLEDmatrix(matrixData);
}
async function getSensorData(){
// Notes on microBitBLE.get*()
// 返却される値は各センサーの値(磁気・加速度は.x,.y,.zに各軸の値)
// .getAccelerometer、.getMagnetometer、.getTemperature、.getButtonA、.getButtonBがある
var acc = await microBitBLE.getAccelerometer();
var mag = await microBitBLE.getMagnetometer();
var temp = await microBitBLE.getTemperature();
var btnA = await microBitBLE.getButtonA();
var btnB = await microBitBLE.getButtonB();
console.log("Acc:",acc," Mag:",mag," Tmp:",temp," bA:",btnA," bB:",btnB);
sdata.innerHTML = "Acc:"+acc.x+","+acc.y+","+acc.z+" Mag:"+mag.x+","+mag.y+","+mag.z+" Tmp:"+temp+" bA:"+btnA+" bB:"+btnB;
}
</script>
<body>
<form name="js">
<input type="button" value="Connect" onclick="connectMicroBit();"/>
<input type="button" value="Disconnect" onclick="disconnectMicroBit();" id="disc" disabled="true"/>
<table>
<tr><td colspan="2">Accelerometer</td></tr>
<tr><td>x:</td><td id="ax"></td></tr>
<tr><td>y:</td><td id="ay"></td></tr>
<tr><td>z:</td><td id="az"></td></tr>
<tr><td colspan="2">Magnetometer</td></tr>
<tr><td>x:</td><td id="mx"></td></tr>
<tr><td>y:</td><td id="my"></td></tr>
<tr><td>z:</td><td id="mz"></td></tr>
<tr><td colspan="2">Buttons</td></tr>
<tr><td>A:</td><td id="btnA">OFF</td></tr>
<tr><td>B:</td><td id="btnB">OFF</td></tr>
<tr><td colspan="1">Temperature</td><td id="tempera"></td></tr>
<tr><td colspan="2">LED</td></tr>
<tr><td ><input id="txt" type="text" value="Hello!" ></td><td><input type="button" value="Print" onclick="showText();"/></td></tr>
<tr><td onclick="showMatrix(event);">
<INPUT type="checkbox"><INPUT type="checkbox"><INPUT type="checkbox"><INPUT type="checkbox"><INPUT type="checkbox"><br>
<INPUT type="checkbox"><INPUT type="checkbox"><INPUT type="checkbox"><INPUT type="checkbox"><INPUT type="checkbox"><br>
<INPUT type="checkbox"><INPUT type="checkbox"><INPUT type="checkbox"><INPUT type="checkbox"><INPUT type="checkbox"><br>
<INPUT type="checkbox"><INPUT type="checkbox"><INPUT type="checkbox"><INPUT type="checkbox"><INPUT type="checkbox"><br>
<INPUT type="checkbox"><INPUT type="checkbox"><INPUT type="checkbox"><INPUT type="checkbox"><INPUT type="checkbox">
</td></tr>
<tr><td><input type="button" value="getSensorData" onclick="getSensorData();"/></td><td id="sdata">NULL</td></tr>
</table>
</body>
</html>
@satakagi

This comment has been minimized.

Copy link
Owner

satakagi commented Dec 27, 2018

回路図というほどではないですが、一応構成図
micro_bit

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment