この記事は、2021年の obniz のアドベントカレンダー の 17日目の記事です。
今回の内容
この記事の内容は、obniz と複数台の toio を組み合わせるための準備をしていく過程での試行錯誤と、2台同時の制御を行ったという内容を記事にしています。
Web Bluetooth API を使った toio の制御は、以下の記事に書いた 6台同時というのをやったことがあったのですが、「obniz で複数台の制御をやろうとするとどうなるのかな?」と思って手をつけたのが今回の内容です。
●Web Bluetooth API で toio を 6台同時に制御する - Qiita
https://qiita.com/youtoy/items/2fae3f4365788810215d
途中の試行錯誤の情報が不要な場合
途中に試行錯誤した過程を、後々の自分用のメモも兼ねて色々と書いています。
結果の部分だけさっと確認したいとう方は、記事の最後のほうの「同時制御も試す」の「うまくいったやり方」という部分のあたりからお読みください。
obniz で toio を扱う
obniz と toio を BLE で接続して扱うためには、以下の SDK を使う方法があります。
●toio_corecube | JS Parts Library | obniz
https://obniz.com/ja/sdk/parts/toio_corecube/README.md
また、上記の SDK で実装されていない toio の機能を使う場合は、以下と toio の技術仕様の情報を使う方法があります。
●BLE - obniz Docs
https://obniz.com/ja/doc/reference/common/ble/
まずは、上記の 2つのやり方のそれぞれを使って、複数の toio をスキャンをするところからやってみようと思います。
実際に試していく 【その1】
スキャンのみを行う
toio のみをスキャン(SDK を利用しないもの)
まずは、SDK を使わずに toio をスキャンする処理を実装してみて、複数台を発見できるか試します。
その際、周りの関係ない機器をスキャンしないようにもしておきます。
具体的には、公式ページの「Central: スキャン」の「条件付き検索」に以下の記載された中の localNamePrefix
(前方一致でデバイス名を指定するもの)を使う形で、それについて toio
を指定します。
1つの HTMLファイルで作ってしまうような形で、以下を準備しました。
操作用のボタンなども用意したのですが、UI の見た目の部分は、CSSフレームワークの Bulma を使って少し整えています。
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.css" />
<script src="https://unpkg.com/obniz/obniz.js"></script>
</head>
<body>
<section class="section">
<div class="container">
<h1 class="title">obniz と toio</h1>
<div class="buttons" style="margin-top: 1.0rem">
<button class="button is-success is-light" type="button" onclick="onStartButtonClick()">
接続
</button>
</div>
</div>
<div id="obniz-debug" style="margin-top: 2.5rem"></div>
</section>
<script>
let obniz;
function onStartButtonClick() {
obniz = new Obniz("OBNIZ_ID_HERE");
obniz.onconnect = async function () {
await obniz.ble.initWait();
const target = {
localNamePrefix: "toio"
};
obniz.ble.scan.onfind = async function (peripheral) {
console.log(peripheral.localName)
};
obniz.ble.scan.onfinish = async function (peripherals, error) {
console.log("scan timeout!")
};
await obniz.ble.scan.startWait(target);
}
}
</script>
</body>
</html>
上記の使い方・処理の流れは、、接続ボタンを押下後に obniz との接続や BLE のスキャンが実行され、スキャン結果をログに出力する、というものになります。
2個の toio を電源ON にした状態で上記を実行したところ、2つの toio をスキャンできました。
ちなみに、 const target = { localNamePrefix: "toio" };
を指定せずにスキャンした場合は、以下のような出力になります。
toio のスキャン結果のみを出力(SDK を利用)
次は、SDK を使って、スキャン結果の中で toio の結果のみをログ出力するようにしてみます。
具体的には、以下のような処理にしてみました。
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.css" />
<script src="https://unpkg.com/obniz/obniz.js"></script>
</head>
<body>
<section class="section">
<div class="container">
<h1 class="title">obniz と toio</h1>
<div class="buttons" style="margin-top: 1.0rem">
<button class="button is-success is-light" type="button" onclick="onStartButtonClick()">
接続
</button>
</div>
</div>
<div id="obniz-debug" style="margin-top: 2.5rem"></div>
</section>
<script>
let obniz;
function onStartButtonClick() {
obniz = new Obniz("OBNIZ_ID_HERE");
obniz.onconnect = async function () {
await obniz.ble.initWait();
const Toio_CoreCube = Obniz.getPartsClass("toio_CoreCube");
obniz.ble.scan.onfind = async (peripheral) => {
if (Toio_CoreCube.isDevice(peripheral)) {
console.log("find");
console.log(peripheral.localName);
}
};
await obniz.ble.scan.startWait();
}
}
</script>
</body>
</html>
「スキャンした結果、toio だった場合にのみログ出力をする」という処理にしたつもりだったのですが、出力に toio 以外のものが出てきている感じがも...(自分の実装のやり方が違ってたりするのかな...)
2台の toio に接続する
ここからは、スキャンした toio に接続する部分を試していきます。
なお、この後は SDK を使わない方法のほうで試していきました。
接続処理をまずは足してみる
まずは、接続する処理を単純に追加してみます。
処理を追加した後の、JavaScript のプログラムの部分は以下となります。
let obniz;
function onStartButtonClick() {
obniz = new Obniz("OBNIZ_ID_HERE");
obniz.onconnect = async function () {
await obniz.ble.initWait();
var target = {
localNamePrefix: "toio"
};
obniz.ble.scan.onfind = async function (peripheral) {
console.log(peripheral.localName)
try {
await peripheral.connectWait();
console.log("connected");
} catch (e) {
console.error(e);
}
};
obniz.ble.scan.onfinish = async function (peripherals, error) {
console.log("scan timeout!")
};
await obniz.ble.scan.startWait(target);
}
}
動作させてみると、1台目が見つかったら接続をして、その際にスキャンは停止するという流れとなりました。
このあたりは、公式ドキュメントに書いてあるとおりの動作です。
接続とスキャンの同時実行はできないため、接続開始と同時にスキャンは自動停止されます。ただ、接続してしまったあとの両立はできますので再度スキャンの実行ができます。
接続後の再スキャンを行う
スキャン終了後に、再スキャンをする処理を足して、2台目にも接続できないか試します。
以下は、処理の追加をした部分の抜粋です。
obniz.ble.scan.onfinish = async function (peripherals, error) {
console.log("scan timeout!")
await obniz.ble.scan.startWait(target);
};
処理を実行してみると、2台とも接続した状態にはなっていそうですが、ログには何やら意図しない挙動だったような感じがする出力が...
接続に使う処理を変えてみる
再度、スキャンに関するドキュメントを見て、やり方を変えてみました。
具体的には、以下に書かれた「1つのみ検索」というのを使って、1台検索して接続してから 2台目を検索して接続する、というやり方にしてみます。
以下に、JavaScript の処理の部分だけ記載します。
function onStartButtonClick() {
obniz = new Obniz("OBNIZ_ID_HERE");
obniz.onconnect = async function () {
await obniz.ble.initWait();
const target = {
localNamePrefix: "toio"
};
for (let i = 0; i < 2; i++) {
const peripheral = await obniz.ble.scan.startOneWait(target);
console.log(peripheral.localName);
peripherals.push(peripheral);
try {
await peripherals[peripherals.length - 1].connectWait();
console.log("connected");
} catch (e) {
console.error(e);
}
}
}
}
もっと、やり方を変える必要があるのかな...
実際に試していく 【その2】
スキャン&接続
接続する toio を指定するやり方
上記で試したものとは別の方法を使ってみます。
スキャンしてアドレスを取得しておいて、その後にアドレス指定を使った接続、というのを試してみることにします。
処理の流れは以下の通りです。
- toio を検出したらアドレスを記録しておく
- 2台の toio のアドレスを取得できた時点でスキャンを終了させる
- 記録していた 2つのアドレス情報を使って、1台ずつ接続を試みる
JavaScript のプログラムは、以下のようにしてみました。
let obniz,
addressList = [];
function onStartButtonClick() {
obniz = new Obniz("OBNIZ_ID_HERE");
obniz.onconnect = async function () {
await obniz.ble.initWait();
const target = {
localNamePrefix: "toio"
};
obniz.ble.scan.onfind = async function (peripheral) {
addressList.push(peripheral.address);
if (addressList.length === 2) {
await obniz.ble.scan.endWait();
console.log(addressList);
for (const address of addressList) {
try {
await obniz.ble.directConnectWait(address, "random");
console.log("connected", address);
} catch (e) {
console.log("can't connect");
}
}
}
};
obniz.ble.scan.onfinish = async function (peripherals, error) {
console.log("scan timeout!");
};
await obniz.ble.scan.startWait(target);
}
}
上記を実行すると、こんどこそエラーが出ることなく、2つの toio に接続された状態になったようでした。
同時制御も試す
上記の接続後に、toio のモーター制御も実行してみます。
以下の obniz公式ドキュメントの書き込み処理の部分と、toio公式の通信仕様の情報を使って、モーター制御の部分も足してみます。
(toio の周りの処理は、「冒頭で掲載していた過去に書いた記事」で使った内容を流用しています)
失敗したやり方
当初、以下のように 2台の toio に接続する処理を行ったタイミングで peripheral の情報を配列に保持するやり方を試してみました。
そうすると、toio の挙動的には接続されたような動きであったものの、「can't connect」というメッセージがログに出力される状況でした。
try {
const peripheral = await obniz.ble.directConnectWait(address, "random");
peripheralList.pusu(peripheral);
console.log("connected", address);
} catch (e) {
console.log("can't connect");
}
うまくいったやり方
上記の失敗があったので、いったん 2台同時接続ができた際の処理と、以下の処理とを組み合わせることにしました。
●ObnizBLE > getconnectedperipherals
https://obniz.github.io/obniz/obnizjs/classes/obnizcore.components.ble.hci.obnizble.html#getconnectedperipherals
最終的に、以下のような内容となりました。
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.css" />
<script src="https://unpkg.com/obniz/obniz.js"></script>
</head>
<body>
<section class="section">
<div class="container">
<h1 class="title">obniz と toio</h1>
<div class="buttons" style="margin-top: 1.0rem">
<button class="button is-success is-light" type="button" onclick="connect()">
接続
</button>
</div>
</div>
<div id="obniz-debug" style="margin-top: 2.5rem"></div>
</section>
<script>
let obniz,
addressList = [];
const TOIO_SERVICE_UUID = "10b20100-5b3b-4571-9508-cf3efcd7bbae";
const MOTOR_CHARACTERISTIC_UUID = "10b20102-5b3b-4571-9508-cf3efcd7bbae";
const motorBuf = new Uint8Array([0x02, 0x01, 0x01, 0x32, 0x02, 0x02, 0x32, 0x78,]);
function connect() {
obniz = new Obniz("OBNIZ_ID_HERE");
obniz.onconnect = async function () {
await obniz.ble.initWait();
const target = {
localNamePrefix: "toio"
};
obniz.ble.scan.onfind = async function (peripheral) {
addressList.push(peripheral.address);
if (addressList.length === 2) {
await obniz.ble.scan.endWait();
console.log(addressList);
for (const address of addressList) {
try {
await obniz.ble.directConnectWait(address, "random");
console.log("connected", address);
} catch (e) {
console.log("can't connect");
}
}
}
};
obniz.ble.scan.onfinish = async function (peripherals, error) {
console.log("scan timeout!");
};
await obniz.ble.scan.startWait(target);
obniz.switch.onchange = async function (state) {
if (state === "push") {
console.log("pressed");
// console.log(obniz.ble.getConnectedPeripherals());
peripheralList = obniz.ble.getConnectedPeripherals();
if (peripheralList.length > 0) {
for (const peripheral of peripheralList) {
console.log(peripheral);
await peripheral.getService(TOIO_SERVICE_UUID).getCharacteristic(MOTOR_CHARACTERISTIC_UUID).writeWait(motorBuf);
}
}
}
}
}
}
</script>
</body>
</html>
上記のプログラムを動作させた際の様子がこちらです。
#obniz の BLE を使った、 #toio 2台の同時制御ができた! pic.twitter.com/bu4lAASMzk
— you (@youtoy) December 16, 2021
無事に、toio 2台を同時に動かすことができました。
(少し、動作開始のタイミングがずれるのだけど...)