obnizを使用した起床時の温度でメロディを変える朝のわくわくツール

obnizのキットがあるので、起床時のワクワク度を上げるアイテムをお試しで作りたいと思います。
※Node-RED、Codespaces、obnizを使用します


## こんなの作りましたNode Red
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3770353/129cbb37-eadf-7195-6867-45d9c79f898c.png)

* 1番目のフロー：obnizの強制終了
* 2番目のフロー：時刻チェックループの強制終了
* 3番目のフロー：本流のフロー

### 使用方法の概要
1. 本流フローの最初のノードに、時間と温度を入力します。ここで指定する時間はアラームをかけたい時刻です。温度は「この温度以上だったら、暑くて耐えられないな」という温度に指定します。（例えば、今日は朝から熱そうだからクーラーをもっと強くして、一日家の中でゴロゴロしていよう思ってしまう温度です）
2. 本流フローを実行します
3. 指定時刻になると温度を計測して、指定温度より低ければ「君が代」のメロディ、指定温度より高いと「救急車」のサイレンが流れます



作成したフロー↓

```
[{"id":"227919c26bc871bf","type":"inject","z":"df4913cd5fb7d1f0","name":"時間・温度指定","props":[{"p":"morningTime","v":"12:48","vt":"str"},{"p":"morningTemperature","v":"24","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":140,"y":320,"wires":[["7c2fc64de015c6dc"]]},{"id":"c2f4edb4b20e10b8","type":"obniz-function","z":"df4913cd5fb7d1f0","obniz":"","name":"君が代","code":"node.warn(\"指定温度より低いので君が代♪\");\r\n\r\nvar speaker = obniz.wired(\"Speaker\", { signal: 0, gnd: 1 });\r\n// 音符の周波数を定義\r\nvar frequencies = {\r\n    \"ド\": 523.251, //ド\r\n    \"レ\": 587.330, //レ\r\n    \"ミ\": 659.255, //ミ\r\n    \"ファ\": 698.456, //ファ\r\n    \"ソ\": 783.991, //ソ\r\n    \"ラ\": 880.000, //ラ\r\n    \"シ\": 987.767, //シ\r\n    \"ド2\": 1046.502, //ド\r\n    \"レ2\": 1174.659, //レ\r\n\r\n};\r\n\r\n\r\nspeaker.play(frequencies[\"レ\"]);\r\nawait obniz.wait(500);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"ド\"]);\r\nawait obniz.wait(500);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"レ\"]);\r\nawait obniz.wait(500);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"ミ\"]);\r\nawait obniz.wait(500);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"ソ\"]);\r\nawait obniz.wait(500);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"ミ\"]);\r\nawait obniz.wait(500);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"レ\"]);\r\nawait obniz.wait(1000);\r\nspeaker.stop();\r\n\r\nspeaker.play(frequencies[\"ミ\"]);\r\nawait obniz.wait(500);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"ソ\"]);\r\nawait obniz.wait(500);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"ラ\"]);\r\nawait obniz.wait(500);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"ソ\"]);\r\nawait obniz.wait(250);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"ラ\"]);\r\nawait obniz.wait(250);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"レ2\"]);\r\nawait obniz.wait(500);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"シ\"]);\r\nawait obniz.wait(500);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"ラ\"]);\r\nawait obniz.wait(500);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"ソ\"]);\r\nawait obniz.wait(500);\r\nspeaker.stop();\r\n\r\nspeaker.play(frequencies[\"ミ\"]);\r\nawait obniz.wait(500);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"ソ\"]);\r\nawait obniz.wait(500);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"ラ\"]);\r\nawait obniz.wait(1000);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"レ2\"]);\r\nawait obniz.wait(500);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"ド2\"]);\r\nawait obniz.wait(500);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"レ2\"]);\r\nawait obniz.wait(1000);\r\nspeaker.stop();\r\n\r\nspeaker.play(frequencies[\"ミ\"]);\r\nawait obniz.wait(500);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"ソ\"]);\r\nawait obniz.wait(500);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"ラ\"]);\r\nawait obniz.wait(500);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"ソ\"]);\r\nawait obniz.wait(500);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"ミ\"]);\r\nawait obniz.wait(750);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"ソ\"]);\r\nawait obniz.wait(250);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"レ\"]);\r\nawait obniz.wait(1000);\r\nspeaker.stop();\r\n\r\nspeaker.play(frequencies[\"ラ\"]);\r\nawait obniz.wait(500);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"ド2\"]);\r\nawait obniz.wait(500);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"レ2\"]);\r\nawait obniz.wait(1000);\r\nspeaker.stop();\r\n\r\nspeaker.play(frequencies[\"ド2\"]);\r\nawait obniz.wait(500);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"レ2\"]);\r\nawait obniz.wait(500);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"ラ\"]);\r\nawait obniz.wait(500);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"ソ\"]);\r\nawait obniz.wait(500);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"ラ\"]);\r\nawait obniz.wait(500);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"ソ\"]);\r\nawait obniz.wait(250);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"ミ\"]);\r\nawait obniz.wait(250);\r\nspeaker.stop();\r\nspeaker.play(frequencies[\"レ\"]);\r\nawait obniz.wait(500);\r\nspeaker.stop();\r\n\r\nawait obniz.wait(1000); \r\nobniz.close();\r\n","x":850,"y":300,"wires":[[]]},{"id":"7c2fc64de015c6dc","type":"function","z":"df4913cd5fb7d1f0","name":"時刻チェック","func":"\n// フロー変数で停止フラグを初期化\nflow.set(\"stopFlag\", false);\n\nfunction loop() {\n    // 停止フラグをチェック\n    if (flow.get(\"stopFlag\")) {\n        node.warn(\"ループが停止されました。\");\n        return;\n    }\n\n    // 現在時刻を取得\n    var now = new Date();\n    // 日本時間を計算（9時間を加算）\n    now.setHours(now.getHours() + 9);\n\n    // 日本時間をHH:MM形式で取得\n    var nowHours = String(now.getHours()).padStart(2, '0'); // 時を2桁の文字列に\n    var nowMinutes = String(now.getMinutes()).padStart(2, '0'); // 分を2桁の文字列に\n    var nowTime = nowHours + ':' + nowMinutes;\n\n\n    // 指定時刻と一致しなければループ\n    if (nowTime != msg.morningTime) {\n        node.warn(\"指定時刻:\" + msg.morningTime + \" ,現在時刻:\" + nowTime);\n        setTimeout(loop, 1000); // 1秒ごとにループ\n    } else {\n        node.send(msg); // msgを送信\n        node.done(); // Functionノードを終了\n    }\n}\n\nloop();","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":320,"y":320,"wires":[["71ba3883c0f10c0f"]]},{"id":"cc2f5857e472f40b","type":"debug","z":"df4913cd5fb7d1f0","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":490,"y":160,"wires":[]},{"id":"b2941a0da5c7722b","type":"obniz-function","z":"df4913cd5fb7d1f0","obniz":"","name":"obnizの強制終了","code":"msg.payload = \"finish\";\nawait obniz.wait(1000); \nobniz.close();\n\nreturn msg;","x":320,"y":160,"wires":[["cc2f5857e472f40b"]]},{"id":"e0fbbe25f64239f2","type":"inject","z":"df4913cd5fb7d1f0","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":140,"y":160,"wires":[["b2941a0da5c7722b"]]},{"id":"8533500e3676f158","type":"obniz-function","z":"df4913cd5fb7d1f0","obniz":"","name":"救急車","code":"node.warn(\"指定温度より高いので救急車のサイレン\");\r\n\r\nvar speaker = obniz.wired(\"Speaker\", { signal: 0, gnd: 1 });\r\n\r\n\r\nfor (var i = 0; i < 22; i++) {\r\n    speaker.play(960);\r\n    await obniz.wait(500);\r\n    speaker.stop();\r\n    speaker.play(770);\r\n    await obniz.wait(500);\r\n    speaker.stop();\r\n};\r\n\r\nawait obniz.wait(1000); \r\nobniz.close();","x":850,"y":380,"wires":[[]]},{"id":"186df68eb283f4b5","type":"switch","z":"df4913cd5fb7d1f0","name":"","property":"temperature","propertyType":"msg","rules":[{"t":"lt","v":"morningTemperature","vt":"msg"},{"t":"gte","v":"morningTemperature","vt":"msg"}],"checkall":"true","repair":false,"outputs":2,"x":670,"y":320,"wires":[["c2f4edb4b20e10b8"],["8533500e3676f158"]]},{"id":"71ba3883c0f10c0f","type":"obniz-function","z":"df4913cd5fb7d1f0","obniz":"","name":"温度チェック","code":"msg.temperature = await obnizParts.LM60.getWait(); // 温度センサーのデータを取得\r\nmsg.temperature = Math.round(msg.temperature);\r\nnode.warn(\"現在温度:\" + msg.temperature + \"℃ 指定温度:\" + msg.morningTemperature)\r\n\r\nreturn msg","x":500,"y":320,"wires":[["186df68eb283f4b5"]]},{"id":"1052aebabf24a36d","type":"inject","z":"df4913cd5fb7d1f0","name":"ループ停止","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"stop","payloadType":"str","x":120,"y":240,"wires":[["64ace2995c3def8f"]]},{"id":"64ace2995c3def8f","type":"function","z":"df4913cd5fb7d1f0","name":"時刻チェックループの強制終了","func":"flow.set(\"stopFlag\", true);\nnode.warn(\"停止フラグが設定されました。\");\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":370,"y":240,"wires":[[]]}]
```

試したい方は、適宜自分のobniz IDを入れて試してください


## 君が代メロディの音

https://x.com/YopiBee9/status/1807279446409552011


## 救急車メロディの音

https://x.com/YopiBee9/status/1807279289618042919


## 君が代のメロディの中身
楽譜をコードに落とすのがこの記事を作成する上でしんどかった...
※楽譜の読み方から理解する必要あった。学校で習った記憶がない。

https://kaisei-music.org/others/japan.html

>![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3770353/6d27e0ac-9024-1681-5a78-166c51e4924f.png)


音階の周波数はこちらのサイトを利用

https://tomari.org/main/java/oto.html

メロディのソース↓
```javascript
var speaker = obniz.wired("Speaker", { signal: 0, gnd: 1 });
// 音符の周波数を定義
var frequencies = {
    "ド": 523.251, //ド
    "レ": 587.330, //レ
    "ミ": 659.255, //ミ
    "ファ": 698.456, //ファ
    "ソ": 783.991, //ソ
    "ラ": 880.000, //ラ
    "シ": 987.767, //シ
    "ド2": 1046.502, //ド
    "レ2": 1174.659, //レ

};


speaker.play(frequencies["レ"]);
await obniz.wait(500);
speaker.stop();
speaker.play(frequencies["ド"]);
await obniz.wait(500);
speaker.stop();
speaker.play(frequencies["レ"]);
await obniz.wait(500);
speaker.stop();
speaker.play(frequencies["ミ"]);
await obniz.wait(500);
speaker.stop();
speaker.play(frequencies["ソ"]);
await obniz.wait(500);
speaker.stop();
speaker.play(frequencies["ミ"]);
await obniz.wait(500);
speaker.stop();
speaker.play(frequencies["レ"]);
await obniz.wait(1000);
speaker.stop();

speaker.play(frequencies["ミ"]);
await obniz.wait(500);
speaker.stop();
speaker.play(frequencies["ソ"]);
await obniz.wait(500);
speaker.stop();
speaker.play(frequencies["ラ"]);
await obniz.wait(500);
speaker.stop();
speaker.play(frequencies["ソ"]);
await obniz.wait(250);
speaker.stop();
speaker.play(frequencies["ラ"]);
await obniz.wait(250);
speaker.stop();
speaker.play(frequencies["レ2"]);
await obniz.wait(500);
speaker.stop();
speaker.play(frequencies["シ"]);
await obniz.wait(500);
speaker.stop();
speaker.play(frequencies["ラ"]);
await obniz.wait(500);
speaker.stop();
speaker.play(frequencies["ソ"]);
await obniz.wait(500);
speaker.stop();

speaker.play(frequencies["ミ"]);
await obniz.wait(500);
speaker.stop();
speaker.play(frequencies["ソ"]);
await obniz.wait(500);
speaker.stop();
speaker.play(frequencies["ラ"]);
await obniz.wait(1000);
speaker.stop();
speaker.play(frequencies["レ2"]);
await obniz.wait(500);
speaker.stop();
speaker.play(frequencies["ド2"]);
await obniz.wait(500);
speaker.stop();
speaker.play(frequencies["レ2"]);
await obniz.wait(1000);
speaker.stop();

speaker.play(frequencies["ミ"]);
await obniz.wait(500);
speaker.stop();
speaker.play(frequencies["ソ"]);
await obniz.wait(500);
speaker.stop();
speaker.play(frequencies["ラ"]);
await obniz.wait(500);
speaker.stop();
speaker.play(frequencies["ソ"]);
await obniz.wait(500);
speaker.stop();
speaker.play(frequencies["ミ"]);
await obniz.wait(750);
speaker.stop();
speaker.play(frequencies["ソ"]);
await obniz.wait(250);
speaker.stop();
speaker.play(frequencies["レ"]);
await obniz.wait(1000);
speaker.stop();

speaker.play(frequencies["ラ"]);
await obniz.wait(500);
speaker.stop();
speaker.play(frequencies["ド2"]);
await obniz.wait(500);
speaker.stop();
speaker.play(frequencies["レ2"]);
await obniz.wait(1000);
speaker.stop();

speaker.play(frequencies["ド2"]);
await obniz.wait(500);
speaker.stop();
speaker.play(frequencies["レ2"]);
await obniz.wait(500);
speaker.stop();
speaker.play(frequencies["ラ"]);
await obniz.wait(500);
speaker.stop();
speaker.play(frequencies["ソ"]);
await obniz.wait(500);
speaker.stop();
speaker.play(frequencies["ラ"]);
await obniz.wait(500);
speaker.stop();
speaker.play(frequencies["ソ"]);
await obniz.wait(250);
speaker.stop();
speaker.play(frequencies["ミ"]);
await obniz.wait(250);
speaker.stop();
speaker.play(frequencies["レ"]);
await obniz.wait(500);
speaker.stop();

await obniz.wait(1000); 
obniz.close();
```

※ちなみに、なぜ君が代のメロディを作ろうと思ったかは気にしないでください...


## ループ処理の工夫
時刻チェックノードは指定時刻になるまでずっとループを回す仕組みにしているのですが、途中で急にループを止めたくなるということがあります。
しかし、Node-REDのどこを操作したらこのループが止まるか分かりません。（私が見つけられないだけ？）
そこで、時刻チェックループ強制終了フローを作成しました。いざとなったらこちらを実行すれば止まります。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3770353/ea333cd6-5e36-12ad-902e-ca5c33107aca.png)

中身はこんな感じです。↓

```javascript
flow.set("stopFlag", true);
node.warn("停止フラグが設定されました。");
return msg;
```



ちなみに本流の時刻チェックノードはこんな感じに設定しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3770353/51cf303e-d8cc-6407-7f66-3931d6b82a85.png)


```javascript
// フロー変数で停止フラグを初期化
flow.set("stopFlag", false);

function loop() {
    // 停止フラグをチェック
    if (flow.get("stopFlag")) {
        node.warn("ループが停止されました。");
        return;
    }
    // ・・・
}

loop();
```

つまり、時刻チェックループ強制終了フローを実行すると、stopFlagがtrueになり、本流のループが止まるという仕組みです。



## 感想
君が代が鳴ってもワクワクしませんでしたが、もっとテンションあがるメロディを選択したり、いくつかパターンを設けると、割と楽しいアイテムになるのではないかと思いました。
もし興味ある方は、まずは楽譜からコードに落として試してみてください。