IoT機器が手に入ったので推しを動かしてみた

## IoTって面白い
この度、obnizというIoT機器が手に入ったので色々触ってみました。
これまで自分でコードを書いたりしても画面の中で動作するのを見るのみだったのですが、IoTは現実世界で自分の作ったものが動作しているのを見ることができるので、その点が本当に面白いなと感じました。

## 推しを動かしてみたい
obnizには色々なセンサーやモーター、LEDといったパーツがありますが、これらの触り方を一通り知って真っ先に思ったことがあります。

**「推しを動かしてみたい」**
…はい。何を言っているか分からない方もいらっしゃると思いますが、obnizを使えば光と音と動きを簡単に制御できる、ということは推しキャラクターのフィギュアやアクリルスタント(以下、アクスタ)を使ってライブを再現できるのではないかと思い至ったわけです。

## 実際にやってみた
思い立ったら後は行動するのみ。という訳で実際に作ったものが以下になります。

https://twitter.com/kelab_8/status/1518393585032626176

「ライブを再現」ということで、ペンライトの光を照度センサーで捉えて動き出す仕組みにしています。
メロディーも作りこんで、照明もこのキャラのイメージカラーである青にして、見事に(？)ライブを再現することができました！
それでは、以下どのように制作したか書いていきたいと思います。

## 環境
obniz
Node-RED（Heroku環境上で実装）
JavaScript

## obniz構成
obnizの組み方、使用したパーツについては以下をご覧ください。
![DSC_0893.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2607554/dd1a8a65-af93-7e3e-0c8a-211e2e08bc28.jpeg)

**使用したパーツ**
- obniz Board 1Y
- 照度センサ(CdS) * 1
- サーボモーター * 1
- スピーカー * 1
- LED(青) * 2
- ~~渡辺曜アクスタ(Hop? Stop? Nonstop! ver)~~

## Node-RED
今回は、以下のようなフローを作成しました。
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2607554/c4ea8d3c-5c0f-6d8c-d2bc-befc8e4cd625.png)
フローをJSON形式で出力したものは以下になります。
```json
[{"id":"47059c7b.003b54","type":"tab","label":"曜ちゃんライブ","disabled":false,"info":""},{"id":"28c3949b.90707c","type":"obniz-repeat","z":"47059c7b.003b54","obniz":"ac5898c.7d1c768","name":"動作開始","interval":"100","code":"//照度センサーからのインプット\nvar voltage = await obniz.ad1.getWait();\nmsg.payload = voltage;\n\nreturn msg;","x":120,"y":80,"wires":[["7ebb593a.3048b8"]]},{"id":"7ebb593a.3048b8","type":"obniz-function","z":"47059c7b.003b54","obniz":"ac5898c.7d1c768","name":"メイン動作","code":"//角度を保持する変数\r\nvar degrees = context.get('degrees')||70;\r\n//メロディーの演奏に使用するカウント\r\nvar count = context.get('count')||0;\r\n//照度\r\nvar voltage = msg.payload\r\n\r\n//メロディーの配列(周波数)\r\nconst melody = [1046.502,\r\n                1046.502,1046.502,932.328,932.328,\r\n                783.991,783.991,698.456,622.254,\r\n                698.456,698.456,783.991,783.991,\r\n                830.609,830.609,783.991,783.991,\r\n                698.456,698.456,783.991,783.991,\r\n                830.609,830.609,783.991,783.991,\r\n                698.456,698.456,783.991,783.991,\r\n                830.609,830.609,932.328,932.328,\r\n                1046.502,1046.502,1046.502,622.254,\r\n                622.254,622.254,0,622.254,\r\n                587.330,587.330,1046.502,1046.502,\r\n                932.328,932.328,783.991,783.991,\r\n                783.991,698.456,0,783.991,\r\n                698.456,0,783.991,698.456,\r\n                0,1046.502,1046.502,932.328,\r\n                1046.502,1046.502,932.328,0,\r\n                0,0,0,0,\r\n                0,0,0,0,\r\n                0,0,0,0,\r\n                0,0,0,0,\r\n                0,0,0,0,\r\n                0,0,0,0]\r\n\r\n//obnizの画面に照度を表示\r\nobniz.display.clear();\r\nobniz.display.print(voltage);\r\n\r\n//照度が0.03以上の場合にメイン動作開始\r\nif (voltage > 0.03) {\r\n //LED点灯\r\n obnizParts.led1.on();\r\n obnizParts.led2.on();\r\n //サーボモーターの角度制御\r\n if (degrees === 70) {\r\n  degrees = 110.0;\r\n } else {\r\n  degrees = 70.0;\r\n }\r\n //スピーカーでメロディ演奏開始\r\n obnizParts.speaker.play(melody[count]);\r\n context.set('count',count + 1);\r\n //サーボモーター動作\r\n obnizParts.servo.angle(degrees);\r\n context.set('degrees',degrees);\r\n} else {\r\n //動作停止\r\n obnizParts.led1.off();\r\n obnizParts.led2.off();\r\n obnizParts.speaker.stop();\r\n}\r\n\r\n//メロディの演奏終了判定\r\nif (count > 64) {\r\n    msg.flg = true\r\n    obnizParts.led1.off();\r\n    obnizParts.led2.off();\r\n    obnizParts.speaker.stop();\r\n} else {\r\n    msg.flg = false\r\n}\r\n\r\nreturn msg;","x":290,"y":80,"wires":[["52b4dae5.f040c4"]]},{"id":"f5c06e2e.1fb14","type":"debug","z":"47059c7b.003b54","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":370,"y":160,"wires":[]},{"id":"89abe67f.d502b8","type":"obniz-function","z":"47059c7b.003b54","obniz":"ac5898c.7d1c768","name":"動作終了","code":"//obniz動作停止\nawait obniz.wait(1000); \nobniz.close();\n\nreturn msg;","x":360,"y":220,"wires":[["bcaccb68.768c78"]]},{"id":"bcaccb68.768c78","type":"debug","z":"47059c7b.003b54","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":530,"y":220,"wires":[]},{"id":"52b4dae5.f040c4","type":"switch","z":"47059c7b.003b54","name":"動作終了判定","property":"flg","propertyType":"msg","rules":[{"t":"false"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":180,"y":180,"wires":[["f5c06e2e.1fb14"],["89abe67f.d502b8"]]},{"id":"ac5898c.7d1c768","type":"obniz","obnizId":"XXXX-XXXX","deviceType":"obnizboard1y","name":"","accessToken":"","code":"obniz.display.clear(); // 画面を消去\r\nobnizParts.led1 = obniz.wired('LED', { anode:3, cathode:4 });\r\nobniz.io0.output(true); //io0を5vに\r\nobniz.io2.output(false); //io2をGNDに\r\nobnizParts.speaker = obniz.wired(\"Speaker\",{ signal:5, gnd:6 });\r\nobnizParts.led2 = obniz.wired('LED', { anode:7, cathode:8 });\r\nobnizParts.servo = obniz.wired(\"ServoMotor\",{ gnd:9, vcc:10, signal:11 });"}]
```

## フローの中身、ソースコード
では、ここからフローの中身について順番に解説していきます。

### obniz初期化
まずはobnizの初期化コードから。各パーツについて初期化していきます。
```js
obniz.display.clear(); // 画面を消去
obniz.io0.output(true); //io0を5vに
obniz.io2.output(false); //io2をGNDに
obnizParts.led1 = obniz.wired('LED', { anode:3, cathode:4 });
obnizParts.speaker = obniz.wired("Speaker",{ signal:5, gnd:6 });
obnizParts.led2 = obniz.wired('LED', { anode:7, cathode:8 });
obnizParts.servo = obniz.wired("ServoMotor",{ gnd:9, vcc:10, signal:11 });
```

### 動作開始
![キャプチャ2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2607554/86a84687-e16a-8828-60b8-09954cd0c1eb.png)
obniz-repeatノードで照度センサーのインプットを受け取ります。
```js
//照度センサーからのインプット
var voltage = await obniz.ad1.getWait();
msg.payload = voltage;

return msg;
```
なお、今回**実行間隔は100msに設定**しています。

### メイン動作
![キャプチャ3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2607554/9a47961a-ee7f-05f0-35f8-a8288573f3f6.png)
モーターの動作、スピーカーのメロディー再生、LEDの点灯といったメインの動作を制御しているノードです。

```js
//角度を保持する変数
var degrees = context.get('degrees')||70;
//メロディーの演奏に使用するカウント
var count = context.get('count')||0;
//照度
var voltage = msg.payload

//メロディーの配列(周波数)
const melody = [1046.502,
                1046.502,1046.502,932.328,932.328,
                783.991,783.991,698.456,622.254,
                698.456,698.456,783.991,783.991,
                830.609,830.609,783.991,783.991,
                698.456,698.456,783.991,783.991,
                830.609,830.609,783.991,783.991,
                698.456,698.456,783.991,783.991,
                830.609,830.609,932.328,932.328,
                1046.502,1046.502,1046.502,622.254,
                622.254,622.254,0,622.254,
                587.330,587.330,1046.502,1046.502,
                932.328,932.328,783.991,783.991,
                783.991,698.456,0,783.991,
                698.456,0,783.991,698.456,
                0,1046.502,1046.502,932.328,
                1046.502,1046.502,932.328,0,
                0,0,0,0,
                0,0,0,0,
                0,0,0,0,
                0,0,0,0,
                0,0,0,0,
                0,0,0,0]

//obnizの画面に照度を表示
obniz.display.clear();
obniz.display.print(voltage);

//照度が0.03以上の場合にメイン動作開始
if (voltage > 0.03) {
 //LED点灯
 obnizParts.led1.on();
 obnizParts.led2.on();
 //サーボモーターの角度制御
 if (degrees === 70) {
  degrees = 110.0;
 } else {
  degrees = 70.0;
 }
 //スピーカーでメロディ演奏開始
 obnizParts.speaker.play(melody[count]);
 context.set('count',count + 1);
 //サーボモーター動作
 obnizParts.servo.angle(degrees);
 context.set('degrees',degrees);
} else {
 //動作停止
 obnizParts.led1.off();
 obnizParts.led2.off();
 obnizParts.speaker.stop();
}

//メロディの演奏終了判定
if (count > 64) {
    msg.flg = true
    obnizParts.led1.off();
    obnizParts.led2.off();
    obnizParts.speaker.stop();
} else {
    msg.flg = false
}

return msg;
```
メロディーの再生については以下の記事を参考にしました。

https://qiita.com/tseigo/items/56c78be82b6276825ca6

Node-REDではどうも一度のプログラム実行でスピーカーの再生を順番に実行するということができないようなので、**メロディーの各音を予め配列に格納し、一度のプログラム実行で一音ずつ再生するという手法**をとりました。
(恐らくobniz-repeatノードが設定した間隔で動作し、その度にプログラムが実行されることと関係していると推測しています。この辺り、うまく非同期的にプログラム実行する方法があるのかも知れませんが、現時点ではそこまで理解が及んでいないので力業で何とかしています。)

メロディーの最後に周波数"0"(＝無音)を入れているのも、それを入れないとプログラムの実行タイミング的にこの後説明するobnizの動作終了処理が先に動いてしまって、メロディーの途中で動作が終わってしまうための処置になります。

なお、メロディーはアクスタの衣装に合わせて「Hop? Stop? Nonstop!」のサビ部分にしています！

### 動作終了判定
![キャプチャ4.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2607554/3eb6c4a9-6c54-01f2-07c2-45ac41ce30ff.png)
メイン動作ノードで設定したフラグを判定します。
フラグが立っていなかったら特に何もせず動作を継続します。
![キャプチャ1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2607554/4bdf0e5c-af3b-c38f-5788-15d453f271ae.png)

### 動作終了
![キャプチャ5.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2607554/4d43ea7a-95b5-14d6-2c2f-c51f0c4818ce.png)
動作終了と判定された場合は、obnizの動作を停止させて終了です。
```js
//obniz動作停止
await obniz.wait(1000); 
obniz.close();

return msg;
```

## あとがき
趣味全開な開発でしたが、やっぱりこういうのが一番楽しいですね。
特にメロディーのところの制御が色々工夫を求められてかなり苦労しましたが、完成した時はとても嬉しかったです！
今回作ったものが何かの役に立つとは正直あまり思えないのですが、、、obnizの動かし方について学べたので、今後も色々触ってみたいなと思います。

以上、ここまでお読みいただきありがとうございました！