3duilab が 2021年05月11日20時20分05秒 に編集
初版
タイトルの変更
非接触空間センサーとobnizで作る"忍びの地図"
タグの変更
obniz
非接触センサー
IoT
忍びの地図
メイン画像の変更
本文の変更
映画**ハリーポッターに足跡が歩く"忍びの地図(Marauder's Map)"という魔法の地図**が出てきます。これを※**非接触空間センサー**(https://interactive-hand-sensor.com/root/) で作りました。やってみると、とても楽しいです:smile:これは試作ですがIoTで複数連動させるとセキュリティやテーマパーク等で使える斬新なシステムができます。 ※赤外線反射光(フォトリフレクタ)を利用した簡易距離センサーです。約20cmの距離を高速ピンポイント検出(8個1ミリ秒)できます。 # デモ動画 @[youtube](https://youtu.be/4a1zxAH0wtI) # システム ## 非接触空間センサーの3次元グラフアニメーション ## 足の動きをイメージしやすいようz軸を反転しています。ご覧のようにシステムは足の位置しか分からないので、左右と向きはタイミング、位置、動きから判断します。 @[youtube](https://youtu.be/ex_oHPHDCzo) ## デバイス ## - **センサー** - 簀子:床に置くセンサーを保護します。 - センサー:合計16個、10cmの間隔で配置します。センサーは0.1秒毎に距離データを取得します。 - **コントローラ(STM32F303K8)** - シリアル通信:z方向約5センチ以下で足の位置(xy方向)を検出しUARTでobnizに送信します。2個以上反応した時は平均値とします。 - **obniz** - 通路表示:最初に正方形を描き、足跡表示直前に中央の道を消して"表示のゴミ"が残らないようにしています。 - 足跡表示:左右の足跡画像をobnizのリポジトリに保存し、UARTで受信した足の位置に表示します。 ## 配線図 ##  ## 実験装置 ##  # プログラム ```html:obniz <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" /> <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script> <script src="https://unpkg.com/obniz@3.x/obniz.js" crossorigin="anonymous" ></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jcanvas/21.0.1/min/jcanvas.min.js"></script><! *** jcanvas ***> </head> <body> <!div id="obniz-debug"></div> <canvas width="900" height="850"></canvas> <script> let LINE = 400, DELTA = 50, SHIFT = 55, dir_inv = false; function clear() { $('canvas').clearCanvas({x: LINE + DELTA, y: LINE + SHIFT, width: (LINE + DELTA) * 2, height: 240});} function exec(text) { let lrIdx = [[-1, -1],[-1, -1]]; //** ary2 : [[lr,idx],[lr,idx]] ** for(let i in text){ let ch = text.charCodeAt(i); if ((ch < 65) || ((ch > 79) && (ch < 97)) || (ch > 111)) continue; lrIdx[i][0] = (ch >= 97) ? 1 : 0; // lr L:0, R:1 lrIdx[i][1] = ch - ((ch >= 97) ? 97 : 65); // 97:'a', 65:'A' } return lrIdx; } function getFtAng(isLeft, dirInv) { // [footprint, angle] let ftR = 'https://obniz.com/ja/users/4978/repo/fpL.png'; // foot left let ftL = 'https://obniz.com/ja/users/4978/repo/fpR.png'; // foot right return [((isLeft ^ dirInv) ? ftL : ftR), (dirInv ? 180 : 0)]; } function show(ary, dirInv) { // changed:true let LR_INV = false, sca = 0.92, chg = false; clear(); for (i=0;i<2;i++) { if (ary[i][0]>-1) { let isLeft = (ary[i][0] == (LR_INV ? 1 : 0));// left: true, right: false let ftAng = getFtAng(isLeft, dirInv); // foot angle 0 / 180 degree let pm = isLeft ? -1 : 1; // plus / minus $('canvas').drawImage({source: ftAng[0], x: LINE * 2 - DELTA * (ary[i][1]), y: LINE + SHIFT + pm * DELTA, rotate: ftAng[1] - 90, scale: sca}); chg = true; } } return chg; } $('canvas').css('background-color','#E2AE59'); $('canvas').drawText({fillStyle: '#461B7E', strokeStyle:'#E5E4E2',strokeWidth: 1, x: 450, y: 25, fontSize: 48, fontStyle: 'bold', text: '👣忍びの地図👣'}); $('canvas').drawRect({fillStyle: '#7A5E30', x: LINE + DELTA, y: LINE + SHIFT + DELTA, width: (LINE ) * 2, height: (LINE + DELTA) * 2,}); clear(); // ary2 = exec("oA"); // show(ary2); // ********************************************************** let wrtTime = 0, clrCnt = 0; // clrCnt: clear count let obniz = new Obniz("OBNIZ_ID_HERE");:sweat_smile: obniz.onconnect = async function() { // *** called on onLINE *** obniz.display.print("Marauder's Map"); let uart = obniz.getFreeUart(); uart.start({ tx: 9, rx: 10, gnd:8, baud:57600}); uart.send("Xyz\n"); uart.onreceive = function(data, text){ // 2 charcters like "Ac" // console.log(text, data); ary2 = exec(text); if ((clrCnt > 4) && (ary2[0][1] > -1)) { dir_inv = (ary2[0][1] > 3); } if (show(ary2, dir_inv)) { clrCnt = 0; wrtTime = new Date(); } } obniz.onloop = async function() { // delete left footprints // $('h1').text("**" + clrCnt.toString() + "**"); if (wrtTime == 0) { // not written clrCnt++; return; } nowTime = new Date(); if (nowTime - wrtTime > 100) { // ms clear(); wrtTime = 0; } }; }; // *** called on onLINE *** // called on offLINE obniz.onclose = async function() { uart.end(); }; </script> </body> </html> ``` ```html:STM32F303K8(mbed) // *** IHS ver1.1a *** // *** Setting in Sensor.h: every/normal, single-board/cube/cube-dual(row-length) *** #include "mbed.h" #include "CLED.h" // **************** global valiables ************************* const float INTV_SEC = 0.1; //0.1 extern const int COL_LEN, ROW_LEN; // const int MAX_CNT = 400; // **************** global functions ************************* void tick_ISR(); // **************** global objects *************************** Serial ua(PA_9, PA_10); Ticker tick; Sensor sensor(-70); // The smaller the value, the higher the height CLED cled(11); // IHSxx // ********************** main ******************************* int main() { ua.baud(57600); ua.printf("Hello\n"); tick.attach(&tick_ISR, INTV_SEC); // ********************** get initial value ****************** wait(1); sensor.set_adAryInit(); // set initial value for (int i=0; i<100; i++)// 100:cled error measurement cled.set(10); wait(1); for (int i=0; i<100; i++)// 100:cled error measurement cled.set((byte)0); wait(0.2); // *************************** loop ************************** while(1) { } } // ************************ tick_ISR ************************* void tick_ISR() { const char conv[8] = {5,7,6,4,1,3,2,0}; // 1 3 5 7 // 0 2 4 6 const int CRI = 3000; uint16_t ad[2][8]; // ad[lr][col] char tx[8]; for (int r = 0; r < ROW_LEN; r++) { sensor.setAd(r); // get AD value for (int c = 0; c < COL_LEN; c++) { int lr = (c % 2) ? 1 : 0; int i = r * 4 + c / 2; ad[lr][i] = sensor.getColAd(conv[c]); } } int txc = 0; // tx count for (int r=0; r < ROW_LEN; r++) { // ** row 0,1 ** int ovc[8] = {0,0,0,0}; // over criteria int cnt = 0; for (int c = 0; c < COL_LEN; c++) { // check over criteria to col[0-7] if (ad[r][c] > CRI) ovc[cnt++] = c; } for (int k=0; k<cnt; k++) { char ch = ((r==1) ? 'a' : 'A') + ovc[k] * 2; int chc = 0; // check continuous if (k < cnt - 1) { // check continuous 1 or 2 if (ovc[k + 1] == ovc[k] + 1) { chc = 1; if (k < cnt - 2) { if (ovc[k + 2] == ovc[k] + 2) { chc = 2; } } } } tx[txc++] = ch + chc; k += chc; } } // ** row 0,1 ** if (txc > 0) { tx[txc] = '\0'; ua.printf(tx); } } ``` STM32F303K8(mbed)のライブラリはこちら(https://os.mbed.com/users/maro/code/Nucleo_IHS11a/) # 回路図  センサー基板のカラーLEDドライブとタイミング回路が中心です。 SPIでカラーLED制御とフォトトランジスタの出力読み取りをしています。 ユーザーマニュアルはこちら(https://interactive-hand-sensor.com/root/user-manual)   # 感想 非接触空間センサーは新しいデバイスでこれを使うと魔法のようなシステムが出来ますが、obnizはさらに背中を押してくれるよう気がします。IoTのシステムは今回初めてでしたがデータ保存や複数連携など拡張の余地がありまた使ってみようと思います。日本の電子機器産業が残念なのはイノベーションのジレンマで新しいものが作れないから、なのでこれからも**攻めた**:sweat_smile:感じの斬新なシステムを創って行きたいと思います。 ご覧いただきありがとうございました。