デモ動画
部品
部品名 | 個数 |
---|---|
obniz Board 1Y | 1 |
LSM303DLH | 1 |
設計図
「モーションセンサーで動く3Dオブジェクト」は、obniz Board 1Yに接続されたモーションセンサー「LSM303DLH」の動きに応じて、ブラウザに表示された3Dオブジェクトが動きます。また、3Dオブジェクトの動きの開始及び終了は、Web Speech APIを使用した音声認識により行い、音声合成により状況を通知します。3Dオブジェクトはjavascriptライブラリ「Three.js」を用いて描画します。
使用方法
「モーションセンサーで動く3Dオブジェクト」の使用方法を次に示します。
- obniz Board 1Yに電源を入れて、作成したブラウザソースコードをアクセスすると、ブラウザに3Dオブジェクトが表示されます。
- ブラウザ画面に表示されている「音声合成」ボタンを押して、マイクに「開始して」を発話すると、音声合成により「運動を開始します」が発話されます。
- obniz Board 1Yに接続されているモーションセンサー「LSM303DLH」を傾けると、その傾きに従って表示されている3Dオブジェクトが傾きます。
- マイクに「終了して」を発話すると、音声合成により「運動を終了します」が発話され、以降モーションセンサー「LSM303DLH」を傾けても、表示されている3Dオブジェクトは傾きません。
回路図2
obniz Board 1Yとモーションセンサー「LSM303DLH」を次のようにI2Cにより接続します。
ハードウェア
ハードウェアの接続画像を次に示します。
ブラウザ表示
ブラウザソースコードにアクセスするとブラウザに次のように3Dオブジェクトが表示されます。
ソースコード
「ブラウザソースコード」にアクセスすることにより、「モーションセンサーで動く3Dオブジェクト」が開始されます。javascriptライブラリ「Three.js」は「ブラウザソースコード」から呼ばれて3Dオブジェクトを描画し、「LSM303DLHクラスライブラリ」は「ブラウザソースコード」から呼ばれて加速度データや方位角を取得します。
ブラウザソースコード
ブラウザソースコードは次の処理を行います。
- 「音声合成」ボタンを表示します。このボタンを押すことにより音声合成を許可します。
- Obnizクラスをインスタンス化してobniz Board 1Yに接続します。
- 接続が完了すると、LSM303DLHクラス「 LSM303」をインスタンス化してモーションセンサー「LSM303DLH」を初期化します。
- 音声認識のコールバック関数「recognition.onresult」を登録し、発話すると音声認識が行われて結果がコールバック関数「recognition.onresult」に渡され、音声合成を用いて音声認識に対応する音声を発話します。
- 300msごとに運動の開始が要求されているかを調べ、運動の開始が要求されているとaccelreadAxesメソッドで加速度データを取得し、readHeadingメソッドで方位角を取得します。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>obniz IoTコンテスト</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
<script src="https://unpkg.com/obniz@3.14.0/obniz.js" crossorigin="anonymous"></script>
<script src="./lsm303.js"></script>
</head>
<body>
<div id="info">
<button id="exportASCII">音声合成</button>
</div>
<script>
var obniz = new Obniz("OBNIZ_ID_HERE");
var lsm303;
var lsm303dx = 0;
var lsm303dy = 0;
var lsm303dz = 0;
var uttrstart = new SpeechSynthesisUtterance("運動を開始します");
var uttrstop = new SpeechSynthesisUtterance("運動を終了します");
var startflg = 0;
obniz.onconnect = async function (obniz) {
lsm303 = new LSM303();
lsm303.params = {vcc: 0, scl: 1, sda: 2, gnd: 3};
lsm303.wired(obniz);
lsm303.enabl();
const recognition = new webkitSpeechRecognition();
recognition.lang = "ja-JP";
recognition.continuous = true;
recognition.onresult = function (event) {
// omit
console.log("onresult2! " + event.results[event.resultIndex][0]
.transcript);
if (event.results[event.resultIndex][0].transcript.includes(
'開始')) {
startflg = 1;
speechSynthesis.speak(uttrstart);
} else if (event.results[event.resultIndex][0].transcript.includes(
'終了')) {
startflg = 0;
speechSynthesis.speak(uttrstop);
}
};
recognition.onspeechend = function (event) {
// omit
console.log("onspeechend! " + event);
};
recognition.onend = function (event) {
console.log("onend! " + event);
recognition.start();
};
recognition.start();
};
// called while online.
obniz.onloop = async function () {
obniz.wait(300);
if (startflg === 1) {
let axes = await lsm303.accelreadAxes();
lsm303dx = axes.x / 8;
lsm303dy = axes.y / 8;
lsm303dz = axes.z / 8;
console.log(axes);
let heading = await lsm303.readHeading();
console.log(heading);
} else {
var lsm303x = lsm303y = lsm303z = 0;
}
// JavaScript Examples
};
// called on offline
obniz.onclose = async function () {
};
</script>
<script type="module" src="3dobject.js">
</script>
</body>
</html>
LSM303DLHクラスライブラリ
obniz標準のパーツライブラリでは モーションセンサー「LSM303DLH」がサポートされていないため、LSM303DLHクラスライブラリ「lsm303.js」を作成して単なるクラスとして使用しました。LSM303DLHクラスライブラリでは、accelreadAxesメソッドで加速度センサーからの加速度データを取得し、readHeadingメソッドで磁気コンパスからデータを取得して方位角を計算します。
lsm303.js
//======================================
// LSM303 : デジタルコンパス・加速度センサ
//============================== ========
class LSM303 {
constructor() {
this.requiredKeys = [];
this.keys = ["vcc", "gnd", "sda", "scl", "i2c"];
this.acceladdress = 0x18;
this.magaddress = 0x1e;
}
static info() {
return {
name: "LSM303"
};
}
wired(obniz) {
this.obniz = obniz;
this.obniz.setVccGnd(this.params.vcc, this.params.gnd, "5v");
this.params.clock = 400000;
this.params.pull = "5v";
this.params.mode = "master";
this.i2c = obniz.getI2CWithConfig(this.params);
this.x_offset = 0;
this.y_offset = 0;
this.z_offset = 0;
this.obniz.wait(100);
}
async enabl() {
this.i2c.write(this.acceladdress, [0x20, 0x27]); // enabling Accelerometer
this.i2c.write(this.magaddress, [0x02, 0x00]); // enabling Magnetometer
}
async accelreadAxes() {
this.i2c.write(this.acceladdress, [0x28 | 0x80]);
let res = await this.i2c.readWait(this.acceladdress, 6);
return this.buffToXYZAccel(res);
}
async readHeading() {
this.i2c.write(this.magaddress, [0x03]);
let res = await this.i2c.readWait(this.magaddress, 6);
return this.buffToHeadMag(res);
}
buffToXYZAccel(buffer) {
var pos;
pos = {
x: this.twoscomp(((buffer[1] << 8) | buffer[0]) >> 4, 12),
y: this.twoscomp(((buffer[3] << 8) | buffer[2]) >> 4, 12),
z: this.twoscomp(((buffer[5] << 8) | buffer[4]) >> 4, 12)
};
return pos;
}
buffToHeadMag(buffer) {
var pos;
pos = {
x: (this.twoscomp((buffer[0] << 8) | buffer[1], 16) - this.x_offset),
z: (this.twoscomp((buffer[2] << 8) | buffer[3], 16) - this.z_offset),
y: (this.twoscomp((buffer[4] << 8) | buffer[5], 16) - this.y_offset)
};
return this.toPolar(pos.x, pos.y);
}
twoscomp(value, no_of_bits) {
var upper = Math.pow(2, no_of_bits);
if (value > upper / 2) {
return value - upper;
} else {
return value;
}
}
toPolar(x, y) { // returns polar coordinates as an object (radians)
var polarCoords = {};
polarCoords.r = Math.sqrt(x * x + y * y);
polarCoords.theta = Math.PI / 2 - Math.atan2(y, x);
if (polarCoords.theta < 0) {
polarCoords.theta += 2 * Math.PI;
}
polarCoords.theta = 2 * Math.PI - polarCoords.theta;
polarCoords.theta = (180 / Math.PI * polarCoords.theta);
return ((polarCoords.theta !== 360) ? polarCoords.theta : 0);
}
}
javascriptライブラリ「Three.js」
javascriptライブラリ「Three.js」はwebGLを使用して3Dオブジェクトを描画します。今回使用する3Dオブジェクトは、three.jsのexamplesを参考にしました。モーションセンサー「LSM303DLH」からのデータを取得するために「OrbitControls.js」を次のように変更して、300msごとに加速度データを取得しています。
OrbitControls.js
・・・
window.addEventListener('DOMContentLoaded', function () {
setInterval(() => {
rotateEnd.set(lsm303dx, lsm303dy);
rotateDelta.subVectors(rotateEnd, rotateStart).multiplyScalar(scope.rotateSpeed);
const element = scope.domElement;
rotateLeft(2 * Math.PI * rotateDelta.x / element.clientHeight); // yes, height
rotateUp(2 * Math.PI * rotateDelta.y / element.clientHeight);
rotateStart.copy(rotateEnd);
scope.update();
}, 300);
});
・・・
-
kati
さんが
2021/04/21
に
編集
をしました。
(メッセージ: 初版)
ログインしてコメントを投稿する