はじめに
ブラウザの Web Bluetooth API と toio を組み合わせたお試しについての記事です。
toio の通信仕様は公開されていて、それに従った BLE での処理を行うと、toio を様々なプログラミング言語で制御することができます。
今回の記事では、ブラウザ上で実行する JavaScript の処理で、以下の toio の時間指定付きモーター制御を扱います(その際に、ブラウザの API である「Web Bluetooth API」を使っています)。
●モーター | toio™コア キューブ 技術仕様 > 「時間指定付きモーター制御」
https://toio.github.io/toio-spec/docs/ble_motor/#%E6%99%82%E9%96%93%E6%8C%87%E5%AE%9A%E4%BB%98%E3%81%8D%E3%83%A2%E3%83%BC%E3%82%BF%E3%83%BC%E5%88%B6%E5%BE%A1
その際に、時間指定付きモーター制御の時間指定の値などを、処理の途中で書きかえつつ toio を動かすということも試しています。
直近で書いた記事について
直近では、Web Bluetooth API で以下のサウンド機能を扱う話を記事にしていました。その記事の内容と今回の内容は、基本的な流れは同じです。
●ブラウザの Web Bluetooth API を使って toio で使える効果音のいくつかをセットで鳴らす(環境は p5.js Web Editor を利用) - Qiita
https://qiita.com/youtoy/items/00abe2b06e23bf9e0f09
今回の内容を試した時の様子
実装内容の話に入る前に、先に今回の内容を試した時の様子(以下の動画)を紹介します。
こちらでは、「画面をクリックした際に toio のモーター制御を 3回実行する」ということをやっています。
また、モーター制御に関する少し変則的な処理や、p5.js のちょっとした描画処理を入れています。
それらについて、具体的には「3回目の動作を 1/2 の確率で異なる回転方向・回転時間となるようする」「一連のモーター制御を行っている間、動画の左の方にうつっている p5.js の描画用キャンバスの、背景色を青系の色に変化させる」というものです。
今回の実装
それでは、今回実装した内容の話に入っていきます。
まずは、プログラム全体を示します。ちょっと雑な部分があります・・・。
※ 開発・実行に p5.js Web Editor を用いており、その環境内の sketch.js のみ書きかえて使っています(HTML や CSS はそのまま)。
const TOIO_SERVICE_UUID = "10b20100-5b3b-4571-9508-cf3efcd7bbae";
const TOIO_MOTOR_CHARACTERISTIC_UUID = "10b20102-5b3b-4571-9508-cf3efcd7bbae";
const sleep = (msec) => new Promise((resolve) => setTimeout(resolve, msec));
let characteristic;
let isConnected = false;
async function controlMotor() {
try {
if (!isConnected) {
const device = await navigator.bluetooth.requestDevice({
filters: [{ services: [TOIO_SERVICE_UUID] }],
optionalServices: [TOIO_MOTOR_CHARACTERISTIC_UUID],
});
const server = await device.gatt.connect();
const service = await server.getPrimaryService(TOIO_SERVICE_UUID);
characteristic = await service.getCharacteristic(
TOIO_MOTOR_CHARACTERISTIC_UUID
);
isConnected = true;
console.log("接続しました");
} else {
background(100, 140, 190);
console.log("モーター制御を開始");
const data = new Uint8Array([
0x02,
0x01,
1,
0x64,
0x02,
2,
0x14,
10, // 0x0a を数値指定、10倍した値が時間(単位 ミリ秒)となる
]);
await characteristic.writeValueWithoutResponse(data);
await sleep(800);
toggleValues(data, [2, 5]);
data[7] = 20;
await characteristic.writeValueWithoutResponse(data);
await sleep(600);
let t;
if (random() < 0.5) {
toggleValues(data, [2, 5]);
t = 90;
} else {
t = 30;
}
data[7] = t;
await characteristic.writeValueWithoutResponse(data);
await sleep(t * 10);
console.log("モーター制御完了");
background(220);
}
} catch (error) {
console.error(`Error: ${error}`);
}
}
function toggleValues(data, indices) {
indices.forEach((index) => {
data[index] = data[index] === 1 ? 2 : 1;
});
}
function setup() {
createCanvas(550, 450);
background(220);
}
function mouseClicked() {
controlMotor();
}
実装内容の補足: 大まかな部分
上記で実装している内容は、おおまかには以下のとおりです。
- 画面をクリックすると controlMotor() を実行する
- controlMotor() の処理は以下のとおり
- toio との接続が行われていなかったら、toio との接続を行う:
if (!isConnected) {}
の中の処理 - 既に toio と接続されていたら効果音を鳴らす:
background(100, 140, 190)
〜background(220)
までの処理
- toio との接続が行われていなかったら、toio との接続を行う:
- controlMotor() の処理は以下のとおり
また、複数回のモーター制御を実行する際に、モーター制御の上書きを防ぐため、冒頭に書いている以下の処理を用いています(※ モーター制御が完了する前に、次の別のモーター制御を実行すると、完了していなかったモーター制御は途中までしか実行されません)。
const sleep = (msec) => new Promise((resolve) => setTimeout(resolve, msec));
なお、冒頭に書いている 2つの UUID は、toio の仕様のページに記載があるものを用いています。
実装内容の補足: モーター制御
モーター制御の処理は、writeValueWithoutResponse() を使っている部分です。その中で、Uint8Array() で toio に送るデータを作っています。
※ 前のサウンド機能を使った時は writeValueWithResponse() でしたが、今回は writeValueWithoutResponse() を使うことになります(違いは with か without か)
Uint8Array() の括弧の中の内容
Uint8Array() の括弧の中の内容は、以下となっています。
- [0x02, 0x01, 1, 0x64, 0x02, 2, 0x14, 10]
- [0x02, 0x01, 2, 0x64, 0x02, 1, 0x14, 20]
- 1/2 の確率で以下のどちらかになる
- [0x02, 0x01, 2, 0x64, 0x02, 1, 0x14, 30]
- [0x02, 0x01, 1, 0x64, 0x02, 2, 0x14, 30]
今回、const data = new Uint8Array()
で定義した値を、途中で書きかえる実験をしてみたかったため、background(100, 140, 190)
〜 background(220)
までの処理はごちゃごちゃした内容になっています。
なお、ここで指定している数値などの意味は、toio公式の仕様で書かれている以下の内容に従ったものです。
回転方向を反転させる処理
今回、3回の時間指定付きモーター制御を行う際に、回転方向が反転する部分があります。
その処理では、Uint8Array() の「3番目」「6番目」の両方の値を「1 ⇒ 2、または 2 ⇒ 1」に変化させています。それを実行するために以下の toggleValues() を用いています。
function toggleValues(data, indices) {
indices.forEach((index) => {
data[index] = data[index] === 1 ? 2 : 1;
});
}