kouのアイコン画像
kou 2021年05月16日作成 (2021年12月22日更新) © MIT
製作品 製作品 閲覧数 1648
kou 2021年05月16日作成 (2021年12月22日更新) © MIT 製作品 製作品 閲覧数 1648

OnlineLiveでも使えるIoTサイリウム

OnlineLiveでも使えるIoTサイリウム

デモ動画

ここに動画が表示されます

概要

主催者側(アーティストや運営側など)は,曲に合わせてスライダーを移動させることで参加者のサイリウムの点滅速度を変更できます。曲のテンポや雰囲気に合わせて,穏やかなら小さく,盛り上がる部分なら大きくします。
参加者側がやることは,サイリウムを振りまくることです。もちろん,場面に応じてですが盛り上がる部分でha
思いっきり激しくサイリウムを振ります。
そうすると,主催者側の画面下の( ^ ^ ♪が盛り上がりに応じて動きが変化します。
主催者側は,盛り上がってるな〜! を感じる事ができ,モチベーションにも繋がります!そして,参加者はアーティストへ思いを加速度に乗せて伝える事ができます!

きっかけ

最近はコロナなどで無観客でのオンラインライブって増えてきてますよね。私は言ったことがないのですが,ライブに行くとサイリウム(LEDペンライト)という棒を振って観客がライブを盛り上げると聞いたことがあります。しかし,オンラインライブになってしまうと,家で勝手に応援することはできても,それが歌っている人に伝わることはなく一方的なものになってしまいます。
そこで,今回はオンラインライブでも双方向で楽しめるIoTサイリウムを作ってみました!

サイリウム

部品

1からサイリウムを作るのは大変なので,100円ショップでちょうどよい感じのステッキを買ってきて使用しました。
内部にセンサを入れるスペースがあるなら,どんなものでも大丈夫です!

100円ショップは材料の宝庫(基盤も付いてるので改造にうってつけかも)

部品名 価格 個数
おもちゃのステッキ ¥110 1
加速度センサ KXR94-2050 ¥110 1
1x4 コネクタ付きケーブル ¥100 2
ユニバーサル基板 ¥100 1
obniz Board 1Y (提供品) ¥0 1
合計 ¥440 6

配線図

ObnizとLED,センサの以下の図の様に配線しました。

配線図

なお,加速度センサをそのままObnizには接続できないのでこんな感じの変換基盤をつくりました。
(配線が汚いのは許してください...)

自作基盤

ステッキのLEDは中に入っている基盤の,LEDのアノードとカソードに線を伸ばしてます。
ステッキの配線

ソースコード

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> </head> <style> * { margin: 0; padding: 0; } body { height: 100vh; background-color: #000; font-family: "Roboto", sans-serif; background: linear-gradient(180deg, #22abd4 0%, #a7e614 100%) no-repeat; } .container { display: flex; flex-direction: column; align-items: center; justify-content: center; } .box-minmax { margin-top: 30px; width: 608px; display: flex; justify-content: space-between; font-size: 20px; color: #ffffff; } .box-minmax span:first-child { margin-left: 10px; } .range-slider { margin-top: 5vh; } .rs-range { margin-top: 29px; width: 600px; -webkit-appearance: none; } .rs-range:focus { outline: none; } .rs-range::-webkit-slider-runnable-track { width: 100%; height: 1px; cursor: pointer; box-shadow: none; background: #ffffff; border-radius: 0px; border: 0px solid #010101; } .rs-range::-moz-range-track { width: 100%; height: 1px; cursor: pointer; box-shadow: none; background: #ffffff; border-radius: 0px; border: 0px solid #010101; } .rs-range::-webkit-slider-thumb { box-shadow: none; border: 0px solid #ffffff; box-shadow: 0px 10px 10px rgba(0, 0, 0, 0.25); height: 42px; width: 22px; border-radius: 22px; background: white; cursor: pointer; -webkit-appearance: none; margin-top: -20px; } .rs-range::-moz-range-thumb { box-shadow: none; border: 0px solid #ffffff; box-shadow: 0px 10px 10px rgba(0, 0, 0, 0.25); height: 42px; width: 22px; border-radius: 22px; background: white; cursor: pointer; -webkit-appearance: none; margin-top: -20px; } .rs-range::-moz-focus-outer { border: 0; } .rs-label { position: relative; transform-origin: center center; display: block; width: 98px; height: 98px; background: transparent; border-radius: 50%; line-height: 30px; text-align: center; font-weight: bold; padding-top: 22px; box-sizing: border-box; border: 2px solid #fff; margin-top: 20px; margin-left: -38px; left: attr(value); color: #fff; font-style: normal; font-weight: normal; line-height: normal; font-size: 36px; } .rs-label::after { content: "%"; display: block; font-size: 20px; letter-spacing: 0.07em; margin-top: -8px; } h1 { margin-top: 30px; text-shadow: 0 5px 5px black; font-weight: 600; color: white; } .text { font-size: 3em; color: white; margin-top: 3vh; } :root { --speed: 1s; } .text span { display: inline-block; animation: textAnim var(--speed) linear infinite alternate; } @keyframes textAnim { from { transform-origin: left bottom; transform: rotate(-15deg); } to { transform-origin: right bottom; transform: rotate(+15deg); } } </style> <body> <div class="container"> <h1>Live Sharing!</h1> <div class="range-slider"> <span id="rs-bullet" class="rs-label">0</span> <input id="rs-range-line" class="rs-range" type="range" value="0" min="0" max="100"> </div> <div class="box-minmax"> <span>0</span><span>100</span> </div> <div class="text"> <span>(</span> <span>^</span> <span>^</span> <span></span> </div> <h3 id="score">Connecting...</h3> </div> <script> let speed = 1000;//スピード var rangeSlider = document.getElementById("rs-range-line"); var rangeBullet = document.getElementById("rs-bullet"); rangeSlider.addEventListener("input", showSliderValue, false); function showSliderValue() { rangeBullet.innerHTML = rangeSlider.value; var bulletPosition = (rangeSlider.value / rangeSlider.max); if (rangeSlider.value != 0) { speed = 2000 / rangeSlider.value; } else { speed = 2000; } rangeBullet.style.left = (bulletPosition * 578) + "px"; } $(() => { let obniz = new Obniz("OBNIZ_ID_HERE"); obniz.onconnect = async () => { let sensor = obniz.wired("KXR94-2050", { vcc: 0, gnd: 2, x: 5, y: 6, z: 7, enable: 1, self_test: 4, }); led = obniz.wired("LED", { anode: 9, cathode: 8 }); for (let i = 0; i < 5; i++) { initAccelVals = await sensor.getWait(); } await console.log(initAccelVals); const scboard = document.getElementById("score"); let old_length = 0; let save = []; let status = false; obniz.repeat(async () => { let currentAccelVals = await sensor.getWait(); let x = currentAccelVals.x; let y = currentAccelVals.y; let z = currentAccelVals.z; let length = Math.round(Math.sqrt(x * x + y * y + z * z) * 100); let delta = Math.abs(length - old_length); old_length = length; save.push(delta); const sum = save.reduce(function (acc, cur) { return acc + cur; }); let avg = sum / save.length; if (save.length > 10) { save.shift(); } else { avg = 0; } const a = el => document.querySelector(el); if (avg > 70) { a(':root').style.setProperty('--speed', '100ms'); } else if (avg > 50) { a(':root').style.setProperty('--speed', '400ms'); } else if (avg > 30) { a(':root').style.setProperty('--speed', '600ms'); } else { a(':root').style.setProperty('--speed', '1s'); } scboard.innerHTML = Math.round(avg); status = !status; if (status) led.on(); else led.off(); await obniz.wait(Math.round(speed)); }); }; }); </script> </body> </html>

ここでは,3軸の成分を累乗して平方することで加速度のベクトルの大きさを算出しています。
この大きさの差によって,盛り上がりを測定しているためどのような方向に振っても反応するという点がポイントです!

また,移動平均で値の変化をなめらかにしています。少し,応答速度は下がりますがライブでの遅延等を考慮すれば大差はないはず。

Java ScriptのタイマーをObnizで使おうと試みたのですが,タイミングがずれるなど上手く実装できなかったのでLEDのON-OFFとセンサー値読み取りのタイミングをあわせています。LEDの点滅速度によって,読み取り回数が変わってくるので少し精度にばらつきが出てしまいますが,実験してみた限りでは大丈夫そうでした。

感想

今回始めてObnizを触ってみたのですが,Java Scriptだけでプログラムを書くという経験が初めてで新鮮でした。今までESP32をArduinoで使ったことはあったのですが,Obnizのようにマイコンにプログラムを書き込まない開発は初めてで,環境構築がない分とても手軽でした。また,サーバーや通信部分のコードを用意しなくてよいので手軽にIoTデバイスを作れるという点ではとても便利だと思いました。
ただ,センサが多くて処理も多く必要なガチガチのガジェットを作る場合は別に処理用のマイコンが必要なのかなとも思いました。GPIOの数の制限やハードウェアレベルでコードをいじれないので,別途Arduinoを使ってObnizとはシリアル通信するみたいな感じになるのかなと。今後,色々と試してみようと思います!

今後の予定

  • 加速度センサーの精度向上(センサー処理にサブマイコンを追加?)
  • フルカラーLEDで色々な色に光らせる
  • ライブの映像から自動的に点滅速度や色を制御
kouのアイコン画像
電子工作とアプリ開発をしている大学生です。 FPGAやIoTなど色々と触っています
  • kou さんが 2021/05/16 に 編集 をしました。 (メッセージ: 初版)
  • kou さんが 2021/05/16 に 編集 をしました。 (メッセージ: 文章ミスを修正)
  • kou さんが 2021/12/22 に 編集 をしました。
ログインしてコメントを投稿する