
クリスマス。静かに光る手まりを楽しみます。コットンボールライトの手まりバージョンで、部屋を彩ります。
光る手まりのインテリア

カーテンレールにつけて飾ったり・・・

クリスマスにツリーに付けて光らせたり・・・。
コロナで家の中で過ごすとが増えました。
室内を明るくして、新しい年を迎えられればと思います。

ユニバーサル社会で役立つデザインや技術の研究

クリスマス。静かに光る手まりを楽しみます。コットンボールライトの手まりバージョンで、部屋を彩ります。

カーテンレールにつけて飾ったり・・・

クリスマスにツリーに付けて光らせたり・・・。
コロナで家の中で過ごすとが増えました。
室内を明るくして、新しい年を迎えられればと思います。

七色に光る手まりを見ながら、静寂のクリスマスイブを過ごそうと思います。手まりの中には傾斜センサーが入っており、傾きで点灯します。

桜の花をかたどった手まりです。
この中にフルカラーLEDが仕込まれており、時間経過で色が変わります。

※早送りしてしまいましたが、実際はもっとゆっくり時間をかけて色が変わります。

手まりの中に、傾斜センサーと電池とLEDを突っ込みました。
傾きで点灯する、不思議な手まりです。
それでは、すてきなクリスマスをお過ごしください。

動画ファイルから、フレームが縦並びになった1枚の画像ファイルを作る場合、PhotoShopでJavaScriptを動かせば、瞬時に生成可能です。ゲームやWebの素材として利用する場面があります。

まずはPhotoShopのメニューで「ファイル>読み込み>ビデオフレームからレイヤー」を選択します。
加工したい動画を選択すると、読込の設定画面が開きます。

おそらく、今回のような動画を画像に変換するケースでは、軽量化を求められると思います。
頻度制限の値を大きくしたり、動画の尺を短く調整してみてください。
「OK」を押すと、レイヤーに動画フレームが読み込まれます。

PhotoshopはJavaScriptで自動化できます。
下記のJavaScriptをコピーして、「convert.js」など、適当な名前で保存します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
var n, w, h; var init = function (){ //レイヤー数取得 n = activeDocument.artLayers.length; //サイズ取得 preferences.rulerUnits = Units.PIXELS; w = activeDocument.width.value; h = activeDocument.height.value; runConvert(); }, runConvert = function(){ runResize(w, n*h); for (var i = 0; i < n; i++) { runMove(i, 0, i*h); } }, runResize = function(w, h){ activeDocument.resizeCanvas(w, h, AnchorPosition.TOPLEFT); }, runMove = function(n, x, y){ activeDocument.artLayers[n].translate(x, y); }, setInput = function(){ docObj = activeDocument; docObj.activeLayer = docObj.layers[1]; }; init(); |
保存したJavaScriptを、Photoshopのツールバーのどこでも良いので、ドラッグします。

うまくいかない場合は、メニューの「ファイル>スクリプト>参照」から、JavaScriptファイルを選択してください。
Photoshopの最新バージョンでは、スクリプト実行前に警告が出ると思います。
「はい」を押すと実行されます。

JavaScriptを実行したら、自動でフレームの縦並び画像が生成されます。
画像サイズが大きかったり、フレーム数が多い場合は負荷がかかるのでご注意ください。
例えば、縦サイズ1080pxのフレームを100枚立て並びにしたら10万8000pxになってしまいます。
画像を必要なサイズに縮小してからJavaScriptを実行させた方が、負荷なく早く仕上がります。

レバースイッチを押し込むと爆発する・・・謎の電子工作に励んでいます。あとは、爆発アニメーションと音を用意してHTMLに組み込めば完成です。

前々回、micro:bitを取り付けた爆破レバーを作り、前回、レバーの傾きを取得してブラウザで受信するプログラムを書きました。
すでにレバーの傾きをJavaScriptで取り込めていれば、爆発させるのは難しくありません。
動画素材サイトPixabayで、商用利用無料、帰属表示は必要なしの動画をダウンロードできます。
利用させてもらったのはNicolas Boulardさんの爆発アニメーションです。

動画をPhotoshopに取り込みます。
メニューの「ファイル>読み込み>ビデオフレームからレイヤー」で、フレームごとのレイヤーを作り、縦に並べました。
全フレームを1枚の画像にまとめることで軽量化できます。
今回は15フレーム、幅480px × 高さ4050(270×15)pxの画像を用意しました。
※加工した素材の再配布はできないため、ここにはアップしません。
効果音ラボから大爆発の効果音をダウンロードさせて頂きました。
こちらも商用利用無料、帰属表示なしで利用できます。
下記の用の素材を配置しました。
img
┗explosion.jpg(爆発画像)
sound
┗big-explosion1.mp3(爆発効果音)
index.html(本体)
micoro:bitを持っていない場合や、手軽に検証したい場合のため、レンジスライダーで動作するようにもしました。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>爆破スイッチ</title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> <style media="screen"> body { background-color: #000; } #controller { position: absolute; bottom: 10px; width: 480px; height: 480px; z-index: 1; } #lever { width: 40px; height: 480px; margin-left: 220px; border: #FFF solid 2px; box-sizing: border-box; background-color: #000; transition: transform 0.1s; } #box { position: absolute; bottom: 0; width: 100%; height: 240px; border: #FFF solid 2px; box-sizing: border-box; background-color: #000; } #connect { margin: 20px; padding: 5px 20px; font-size: 20px; } #imgArea { position: absolute; top: 0; left :calc(50% - 240px); width: 480px; height: 270px; margin: auto; background-color: #000; overflow: hidden; transform: scale(2); transform-origin: center top; } #imgArea img { position: absolute; left: 0; } </style> </head> <body> <div id="controller"> <div id="lever"></div> <div id="box"> <button id="connect">接続</button> <input type="range" id="range" value="0" min="-14" max="14"> </div> </div> <div id="imgArea"> <img src="img/explosion.jpg" alt=""> </div> <audio id="sound" preload="auto"> <source src="sound/big-explosion1.mp3" type="audio/mp3"> </audio> <script> $(function(){ let frame = 0, $img = $('#imgArea img'), $lever = $('#lever'), $sound = $("#sound"), isExplosion = false; const setFrame = function(val){ $img.css("top", val * -270); if(val > 0 && !isExplosion){ isExplosion = true; $sound.get(0).play(); } else if(val < -1){ $sound.get(0).currentTime = 0; $sound.get(0).pause(); isExplosion = false; } }, setAngle = (angle) => { $lever.css("transform", "rotateZ("+angle+"deg)"); setFrame(Math.round(angle/4)); }; $('#range').on('input',function(){ $lever.css("transform", "rotateZ("+$(this).val()*4+"deg)"); setFrame($(this).val() - 0); }); $('#connect').on('click',function(){ connect(); }); // 加速度UUID const ACCELEROMETER_SERVICE_UUID = 'e95d0753-251d-470a-a062-fa1922dfa9a8'; const ACCELEROMETER_CHARACTERISTICS_UUID = 'e95dca4b-251d-470a-a062-fa1922dfa9a8'; let microbit = null; function connect(){ console.log('connect'); let options = {}; // BLEデバイスをスキャンする navigator.bluetooth.requestDevice({ filters: [{ namePrefix: 'BBC micro:bit' }], optionalServices: [ACCELEROMETER_SERVICE_UUID] }) .then(device => { console.log(device); microbit = device; return device.gatt.connect(); }) .then(server => { console.log(server); return server.getPrimaryService(ACCELEROMETER_SERVICE_UUID); }) .then(service => { console.log(service); return service.getCharacteristic(ACCELEROMETER_CHARACTERISTICS_UUID); }) .then(characteristic => { console.log(characteristic); characteristic.startNotifications(); characteristic.addEventListener('characteristicvaluechanged', accelerometerChanged); }) .catch(error => { console.log(error); }); function accelerometerChanged(event){ // 加速度X座標 let //accelX = event.target.value.getInt16(0, true)/1000.0, accelY = event.target.value.getInt16(2, true)/1000.0, accelZ = event.target.value.getInt16(4, true)/1000.0; // 角度 setAngle(Math.atan2(accelZ, accelY) * (180.0 / Math.PI)); } } }); </script> </body> </html> |
上記HTMLはサーバーにアップしなくても、ローカルで動きます。

もし、プロジェクターがあれば、壁に投影すると面白いかもしれません。
作ったHTMLは黒背景になっているため、プロジェクターで壁に投影すると、黒部分が透明になり、現実に爆発している感じがアップします。
また、大きく映し出すことができれば、より爆発の臨場感が増えるでしょう。
以上、爆破スイッチを作りたい人の参考になれば。

micro:bitを利用して、爆破スイッチを作りました。まずはmicro:bitの加速度センサーをPCブラウザで読み込む部分のプログラムを作ります。

分かりやすいように、micro:bitを取り付けた棒の傾きを可視化してみます。
検証はchromeブラウザ(フルスクリーン)で行っています。
micro:bitからブラウザで加速度を取得するところまでは、パソコン工房のmicro:bitで始めるプログラミング入門[Bluetoothでコントローラー編]で勉強させて頂きました。

まずはmicro:bitでビジュアルプログラミングです。
micro:bitでは、bluetooth通信を簡単に設定できます。
パソコン工房に設定の仕方は詳しく書かれており、ここでは割愛します。
最低限だと「最初だけ>bluetooth 加速度計サービス」でおしまいです。
分かりやすいよう、接続状況にによってmicro:bitのアイコンが変わるようにします。
bluetoothで加速度の値を受け取るプログラムをHTMLに書きます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Angle</title> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> <style media="screen"> body { background-color: #000; } #controller { position: relative; width: 480px; height: 480px; margin: auto; } #lever { width: 40px; height: 480px; margin-left: 220px; border: #FFF solid 2px; box-sizing: border-box; background-color: #000; } #connect { margin: 20px; padding: 5px 20px; font-size: 20px; } </style> </head> <body> <div id="controller"> <div id="lever"></div> </div> <button id="connect">接続</button> <script> $(function(){ let $lever = $('#lever'); setAngle = (angle) => { $lever.css("transform", "rotateZ("+angle+"deg)"); }; $('#connect').on('click',function(){ connect(); }); // 加速度UUID ※以下https://www.pc-koubou.jp/magazine/12416参考 const ACCELEROMETER_SERVICE_UUID = 'e95d0753-251d-470a-a062-fa1922dfa9a8'; const ACCELEROMETER_CHARACTERISTICS_UUID = 'e95dca4b-251d-470a-a062-fa1922dfa9a8'; let microbit = null; function connect(){ console.log('connect'); let options = {}; // BLEデバイスをスキャンする navigator.bluetooth.requestDevice({ filters: [{ namePrefix: 'BBC micro:bit' }], optionalServices: [ACCELEROMETER_SERVICE_UUID] }) .then(device => { console.log(device); microbit = device; return device.gatt.connect(); }) .then(server => { console.log(server); return server.getPrimaryService(ACCELEROMETER_SERVICE_UUID); }) .then(service => { console.log(service); return service.getCharacteristic(ACCELEROMETER_CHARACTERISTICS_UUID); }) .then(characteristic => { console.log(characteristic); characteristic.startNotifications(); characteristic.addEventListener('characteristicvaluechanged', accelerometerChanged); }) .catch(error => { console.log(error); }); function accelerometerChanged(event){ // 加速度X座標 let //accelX = event.target.value.getInt16(0, true)/1000.0, accelY = event.target.value.getInt16(2, true)/1000.0, accelZ = event.target.value.getInt16(4, true)/1000.0; // 角度 setAngle(Math.atan2(accelZ, accelY) * (180.0 / Math.PI)); } } }); </script> </body> </html> |
私がmicro:bitの傾きで取得したかったのは、加速度YとZで求める角度でした。
|
1 |
Math.atan2(accelZ, accelY) * (180.0 / Math.PI) |
accelZ、accelYは、どの軸の角度を取得したいかによって入れ替えます。

ローカルでお手軽に動作します。
上記のHTMLをブラウザで開き、接続ボタンを押すと、ペアリング設定が始まります。
接続可能なmicro:bit一覧が表示されるので、選択し、ペア設定ボタンを押します。
そして数秒後に加速度の取得が始まり、ブラウザに表示された棒が動くようになりました。

micro:bitを使って爆破スイッチを作ります。長男がレバースイッチを作っており、これと連動して爆発するプログラムを作りました。

長男が段ボールでレバースイッチを作っています。
持ち手と反対側にmicro:bitが取り付けてあり、加速度でレバーの傾きを取得できます。

箱の中には丸い割りばしが刺さっていて、回転軸となっていました。

箱をふさいで、レバースイッチ完成のようです。

レバースイッチを見ていたら、爆破スイッチにしたくなりました。
レバーを押し込んだときに、爆発するようにします。

micro:bitの加速度をBLEでPCに飛ばします。PCでレバーの角度を計算して、押し込まれたら爆発の画像を表示しています。
そして、壁にプロジェクターで映し出しました。

「obniz Board 1Y」を電池で動かします。単3電池3つで動きますが、消費を抑えるためスリープは必須です。
「obniz Board 1Y」には、3.3Vと書かれた電源差し込み口があります。
このボードは仕様上、動作電圧範囲がDC3.3~5.5Vで、3.3V以上の電池をつなげば動くはずです。
手元にあるエネループは1つ1.2V。3本で3.6V。
つないでみると問題なく稼働しました。

電池で長期間可動させる場合、スリープが必須です。
以前、10分おきの温度記録を行った際、3200mAhのモバイルバッテリーが2日で空になりました。
単三電池3つ(6000mAhくらい)でも、4日で空になる計算です。
モルモットのスマートホームをスリープなしで構築してしまいました。
電源につなぎっぱなしでの稼働だと、無駄に電力を消耗してしまいます。
当たり前ですが長期運用する場合、スリープを考えてプログラミングする必要でした・・・。

obnizドキュメントによると、サーバーレスイベントの実行は1日に150回までとなっています。しかし日付が変わっても解除されず、朝9時ごろの解除でした。obnizのいう1日とはイギリスの標準時が基準なのでしょう。

10分おきにサーバーレスイベントを実行すると、1日144回となります。
しかし・・・なぜか朝の9:30に、150の上限に達してしまいました。
まだ1日は始まったばかりです。
エクセルにログの時間を貼り付けて確認してみると、不思議な結果となりました。
9:05から翌日の9:30で150回に達していました。
1日が24時間超えているようです。
あと、1日の始まりが9時なので、イギリスの標準時が基準のようです。(日本との時差9時間)
1日の終わりは9時ではないようなので、注意が必要です。

もう一つの注意点が、上限に達すると24時間作動しなくなることです。
おそらくですが、前日のカウントがリセットされないまま、今日のカウント判断が実行され、即、上限に達したからだと思います。
これは2020年12月現在の挙動で、近いうちに修正されるかもしれません。

前回のモルモットスマートホームに、スプレッドシートを追加しました。部屋の温度がどう推移しているか、グラフで分かります。スプレッドシートはサーバー化してobnizから直接温度データを受信できます。

先にスプレッドシートの出力結果を掲載します。
10分間隔で、スプレッドシートに日時、温度、湿度が記入されていきます。(プログラムのミスで、最初は1秒間隔で記入されています。)
ただ、よく見ると記入されていない時間もあります。
15時台は3回しか記入がなく、半分は失敗しているようです。13時台のように6回全部記入されることもあります。
これは、obnizとスプレッドシート間の問題ではなく、温度を計測しているμPRISMセンサーとobnizのBLE通信が不安定なことが原因のようです。
温度を確実に記録したい場合、obnizに温度センサーを直接つないでください。

前回までの構成に、スプレッドシートを追加しました。
温度データをスプレッドシートに書き込む方法は、obniz公式ブログにあります。IFTTTを介してスプレッドシートに書き込むのが、手軽な方法だと思います。
しかし、IFTTTは2020年9月から、無料で作れるアプレットの数が3つに制限されてしまいました。
ならばIFTTTを介さず、直接obnizからスプレッドシートに書き込めるようにしましょう。
スプレッドシートを簡易サーバーにします。
Google Spreadsheet を簡易 Webサーバーとして動かして、手軽にWebHookを受け取る方法を参考にしました。
私はPOSTでJSONを送るとき、CORS(クロスドメインエラー)に悩まされ、あきらめてkey=valueの形式で、データ送受信をすることにしました。
下記が、GASに記載したコードです。
|
1 2 3 4 5 6 7 8 9 10 11 |
function doPost(e) { let ss = SpreadsheetApp.getActive(), sheet = ss.getActiveSheet(), data = [new Date()], contents = e.postData.contents.split("&"); for(let i=0;contents[i];i++) { let kv = contents[i].split('='); data[i+1]=kv[1]; } sheet.appendRow(data); } |
Postデータを受け取ったら、シートの最後の行にデータを追加していきます。
温度、あと湿度のデータをPostしているobnizのコードです。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
<html> <head> <meta charset="utf-8"> <script src="https://obniz.io/js/jquery-3.2.1.min.js"></script> <script src="https://unpkg.com/obniz@3.11.0/obniz.js" crossorigin="anonymous"></script> </head> <body> <script> const obniz = new Obniz("obniz-ID"); const ifttt_secret_key = "IFTTT-ID"; let isWrit = false; obniz.onconnect = async () => { await obniz.ble.initWait(); const U_PRISM = Obniz.getPartsClass("uPRISM"); obniz.ble.scan.onfind = async (peripheral) => { if (U_PRISM.isDevice(peripheral)) { console.log("μPRISM発見"); const device = new U_PRISM(peripheral); device.ondisconnect = (reason) => { console.log(reason) } await device.connectWait(); console.log("μPRISM接続完了"); device.onNotify = async (r) => { if(!isWrit){ const temp = r.temperature.toFixed(1); const humid = r.humidity.toFixed(1); isWrit = true; postData = (url, data) => { fetch( url, { method: "POST", mode: "no-cors", body: data } ) .then(() => console.log(url + "にPOST成功")) .catch(error => console.log(error)); }; if(r.temperature < 18){ await postData('https://maker.ifttt.com/trigger/run_meross/with/key/' + ifttt_secret_key, ""); }else if(r.temperature > 20){ await postData('https://maker.ifttt.com/trigger/stop_meross/with/key/' + ifttt_secret_key, ""); } await postData("スプレッドシートで発行したURL", `temp=${temp}&humid=${humid}`); await obniz.wait(1000); if (typeof done === "function") { done(); } } }; await device.startNotifyWait(); } }; await obniz.ble.scan.startWait(); } </script> </body> </html> |
Postしているデータはtemp=${temp}&humid=${humid}の部分です。
tempが温度、humidが湿度。時刻はスプレッドシート側で記述されます。
このコードを、obnizのサーバーレスイベントで10分おきに実行しています。

obnizのサーバーレスイベント画面で、実行ログを見られます。
スプレッドシートに向けてPostした後にエラーが出ていますが・・・、不都合がなかったのでこのまま運用します。

スプレッドシートのA~C列(時間、温度、湿度)を全選択します。
メニューから挿入>グラフを選択すれば、常に更新されていくグラフとなります。
モルモットの温度管理が可視化されました。

モルモットが快適に過ごすスマートホームを作っています。寒くなったら自動的にホットカーペットをONにします。前回はブラウザで動かしていましたが、実用的にするためobnizのサーバーレスイベントを利用します。

18℃を下回ると、モルモットのケージの下のホットカーペットが、自動でONになります。
人間よりモルモットを丁重に扱っている状態に、妻はあきれていました。
この仕組みはブラウザのJavaScriptで動作していましたが、それではPCを閉じたら動かないため、obnizのクラウド上で動かします。
調べると、obnizクラウドには、登録したイベントを自動的に実行する「サーバーレスイベント」があるようです。

前回パソコンだったものが、obnizクラウドに差し替わりました。
その他、μPRISMセンサーやmerossなどは前回の記事に掲載しています。
サーバーレスイベントは、10分おきで繰り返しに設定しました。
パソコンなしで、10分おきに温度をチェックして、寒ければホットカーペットのスイッチがONになり、暑ければOFFになります。
なお、持っているのが「obniz Board 1Y」なら、タイマーではなく公式ブログに書かれている方法で設定したほうが省電力です。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
<html> <head> <meta charset="utf-8"> <script src="https://obniz.io/js/jquery-3.2.1.min.js"></script> <script src="https://unpkg.com/obniz@3.11.0/obniz.js" crossorigin="anonymous"></script> </head> <body> <script> const obniz = new Obniz("obniz-ID"); const ifttt_secret_key = "IFTTT-ID"; let isWrit = false; obniz.onconnect = async () => { await obniz.ble.initWait(); const U_PRISM = Obniz.getPartsClass("uPRISM"); obniz.ble.scan.onfind = async (peripheral) => { if (U_PRISM.isDevice(peripheral)) { console.log("μPRISM発見"); const device = new U_PRISM(peripheral); device.ondisconnect = (reason) => { console.log(reason) } await device.connectWait(); console.log("μPRISM接続完了"); device.onNotify = async (r) => { const temp = r.temperature.toFixed(1); const humid = r.humidity.toFixed(1); postData = (url, data) => { fetch( url, { method: "POST", mode: "no-cors", body: data } ) .then(() => console.log(url + "にPOST成功")) .catch(error => console.log(error)); }; if(r.temperature < 18){ await postData('https://maker.ifttt.com/trigger/run_meross/with/key/' + ifttt_secret_key, ""); }else if(r.temperature > 20){ await postData('https://maker.ifttt.com/trigger/stop_meross/with/key/' + ifttt_secret_key, ""); } await obniz.wait(1000); if (typeof done === "function") { done(); } }; await device.startNotifyWait(); } }; await obniz.ble.scan.startWait(); } </script> </body> </html> |
「await」で待たないと、μPRISMセンサーから温度を取得している間に、イベントが終了してしまいます。
μPRISMとobnizを組み合わせる人は少ないと思いますが・・・参考まで。
次回はスプレッドシートに温度を自動で記述していきます。