kati が 2021年04月21日17時52分38秒 に編集
初版
タイトルの変更
モーションセンサーで動く3Dオブジェクト
タグの変更
IoT
obniz
LSM303DLH
three
音声認識
音声合成
メイン画像の変更
記事種類の変更
製作品
本文の変更
# デモ動画 @[twitter](https://twitter.com/kati40047805/status/1384765868287041537) # 部品 |部品名| 個数 |---|--- |obniz Board 1Y| 1 |LSM303DLH| 1 # 設計図 「モーションセンサーで動く3Dオブジェクト」は、obniz Board 1Yに接続されたモーションセンサー「LSM303DLH」の動きに応じて、ブラウザに表示された3Dオブジェクトが動きます。また、3Dオブジェクトの動きの開始及び終了は、Web Speech APIを使用した音声認識により行い、音声合成により状況を通知します。3Dオブジェクトはjavascriptライブラリ「Three.js」を用いて描画します。 ### 使用方法 「モーションセンサーで動く3Dオブジェクト」の使用方法を次に示します。 1. obniz Board 1Yに電源を入れて、作成したブラウザソースコードをアクセスすると、ブラウザに3Dオブジェクトが表示されます。 2. ブラウザ画面に表示されている「音声合成」ボタンを押して、マイクに「開始して」を発話すると、音声合成により「運動を開始します」が発話されます。 3. obniz Board 1Yに接続されているモーションセンサー「LSM303DLH」を傾けると、その傾きに従って表示されている3Dオブジェクトが傾きます。 4. マイクに「終了して」を発話すると、音声合成により「運動を終了します」が発話され、以降モーションセンサー「LSM303DLH」を傾けても、表示されている3Dオブジェクトは傾きません。 ### 回路図2 obniz Board 1Yとモーションセンサー「LSM303DLH」を次のようにI2Cにより接続します。 ![キャプションを入力できます](https://camo.elchika.com/3ce10419a05fc7822b76c2de964d054e61a7a775/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f32376166663263352d333862312d343363632d386666362d6634376364396530343432352f37326462326333352d336261352d343361632d383633302d373934396133313361303865/) ### ハードウェア ハードウェアの接続画像を次に示します。 ![キャプションを入力できます](https://camo.elchika.com/f2f9b897417622269c4068c549bb7a6af3008e6c/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f32376166663263352d333862312d343363632d386666362d6634376364396530343432352f65616431376337382d383766652d343232342d613630342d636633383663643662343065/) ### ブラウザ表示 ブラウザソースコードにアクセスするとブラウザに次のように3Dオブジェクトが表示されます。 ![キャプションを入力できます](https://camo.elchika.com/7e91960ed37fa289ccb84511d8d663f9b5036142/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f32376166663263352d333862312d343363632d386666362d6634376364396530343432352f34343466303931372d653039612d343533392d386231322d336665653439303966663032/) # ソースコード 「ブラウザソースコード」にアクセスすることにより、「モーションセンサーで動く3Dオブジェクト」が開始されます。javascriptライブラリ「Three.js」は「ブラウザソースコード」から呼ばれて3Dオブジェクトを描画し、「LSM303DLHクラスライブラリ」は「ブラウザソースコード」から呼ばれて加速度データや方位角を取得します。 ### ブラウザソースコード ブラウザソースコードは次の処理を行います。 - 「音声合成」ボタンを表示します。このボタンを押すことにより音声合成を許可します。 - Obnizクラスをインスタンス化してobniz Board 1Yに接続します。 - 接続が完了すると、LSM303DLHクラス「 LSM303」をインスタンス化してモーションセンサー「LSM303DLH」を初期化します。 - 音声認識のコールバック関数「recognition.onresult」を登録し、発話すると音声認識が行われて結果がコールバック関数「recognition.onresult」に渡され、音声合成を用いて音声認識に対応する音声を発話します。 - 300msごとに運動の開始が要求されているかを調べ、運動の開始が要求されているとaccelreadAxesメソッドで加速度データを取得し、readHeadingメソッドで方位角を取得します。 ```html: index.html <!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メソッドで磁気コンパスからデータを取得して方位角を計算します。 ```javascript: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](https://threejs.org/examples/)を参考にしました。モーションセンサー「LSM303DLH」からのデータを取得するために「OrbitControls.js」を次のように変更して、300msごとに加速度データを取得しています。 ```javascript: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); }); ・・・ ```