愛犬のご飯は困らせない！自動給餌マシンを作る！

## コーギーをお迎えするために！
前回に引き続きコーギーと暮らす事を夢見て今日を生きる毎日。
ただ、**システムエンジニアという仕事はピークがまちまちであり、こんな状態ではコーギーがさみしい思いをしていないか？ひもじい思いをしていないか？などと考えてしまい仕事が手につかなくなります**（妄想ですけど）。

そんなタイミングで学んだのがIoTの技術！
**これを使えば遠隔で愛犬の見守りと給餌を行う仕組みが作れるのではないか！？**
と考えて自動給餌マシンを作ってみる事にしました！

## 自動給餌マシン完成品
**さっそくですが完成品はこちら！！**
ありもので作ったためマシンと呼ぶには手作り感がすごいですが、やりたい事は実現できました！
おまけで食欲を掻き立てるご飯が炊けた時の音も入れております！

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">（妄想の）愛犬のため、ありもので自動給餌マシーン？を工作しました！！ピタゴラスイッチかな？<br>これでいつ我が家にコーギーがきてもご飯の準備はOKだよー！！<a href="https://twitter.com/hashtag/protoout?src=hash&amp;ref_src=twsrc%5Etfw">#protoout</a> <a href="https://twitter.com/hashtag/obniz?src=hash&amp;ref_src=twsrc%5Etfw">#obniz</a><a href="https://twitter.com/hashtag/%E7%8A%AC%E5%A5%BD%E3%81%8D%E3%81%95%E3%82%93%E3%81%A8%E7%B9%8B%E3%81%8C%E3%82%8A%E3%81%9F%E3%81%84?src=hash&amp;ref_src=twsrc%5Etfw">#犬好きさんと繋がりたい</a> <a href="https://twitter.com/hashtag/%E7%8A%AC%E3%81%AE%E3%81%84%E3%82%8B%E5%B9%B8%E3%81%9B?src=hash&amp;ref_src=twsrc%5Etfw">#犬のいる幸せ</a> <a href="https://twitter.com/hashtag/%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E3%81%A8%E7%B9%8B%E3%81%8C%E3%82%8A%E3%81%9F%E3%81%84?src=hash&amp;ref_src=twsrc%5Etfw">#エンジニアと繋がりたい</a> <a href="https://twitter.com/hashtag/%E9%A7%86%E3%81%91%E5%87%BA%E3%81%97%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E3%81%A8%E7%B9%8B%E3%81%8C%E3%82%8A%E3%81%9F%E3%81%84?src=hash&amp;ref_src=twsrc%5Etfw">#駆け出しエンジニアと繋がりたい</a> <a href="https://t.co/96uMm35pMh">pic.twitter.com/96uMm35pMh</a></p>&mdash; oooookawa (@ooooooooookawa) <a href="https://twitter.com/ooooooooookawa/status/1720370946039820315?ref_src=twsrc%5Etfw">November 3, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## 環境準備
今回使用した環境・モノは以下の通りです。

**【環境】**
・Node-RED（codespaces）
・LINE BOT（Messaging API）
・obnizノード

**【モノ】**
・obniz Board 1Y（上段左）
・サーボモータ（上段右）
・超音波距離センサ（下段左）
・ブレッドボード（下段中央）
・スピーカー（下段右）
<img src = https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3567275/7603be7c-75b9-d2d7-ada3-04c6edc916a2.png width = "80%">




## 実装開始
### Node-REDフロー図
Node-REDのフローと書き出したJSONは以下の通りとなります。

**■Node-REDフロー図**
![スクリーンショット 2023-11-03 170541.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3567275/6f3bc4f6-71d7-d169-9053-82e11ca0f150.png)

```json:JSON
[{"id":"9db103df230548a6","type":"obniz-function","z":"8aa492ba5d2e627a","obniz":"37ad24e55808fdb7","name":"","code":"msg.payload = await obnizParts.hcsr04.measureWait();\nreturn msg;","x":360,"y":260,"wires":[["83931ede40576aa3"]]},{"id":"83931ede40576aa3","type":"switch","z":"8aa492ba5d2e627a","name":"","property":"payload","propertyType":"msg","rules":[{"t":"lt","v":"200","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":530,"y":260,"wires":[["94fc581fcc0c6506"],["fb9d4bfeaea7d2f7"]]},{"id":"fb9d4bfeaea7d2f7","type":"template","z":"8aa492ba5d2e627a","name":"","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"近くにいないよ・・・","output":"str","x":720,"y":300,"wires":[["4630479f668841c5"]]},{"id":"94fc581fcc0c6506","type":"template","z":"8aa492ba5d2e627a","name":"","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"近くにいるよー！！","output":"str","x":720,"y":240,"wires":[["4630479f668841c5"]]},{"id":"91f55b030f15768b","type":"Webhook","z":"8aa492ba5d2e627a","name":"","url":"/webhook","x":100,"y":260,"wires":[["7d31739e4f75063a"]]},{"id":"7d31739e4f75063a","type":"switch","z":"8aa492ba5d2e627a","name":"","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"確認","vt":"str"},{"t":"eq","v":"ご飯","vt":"str"},{"t":"eq","v":"ごちそうさま","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":4,"x":130,"y":340,"wires":[["9db103df230548a6"],["dcf2ba70584d8069"],["dcf2ba70584d8069"],["ee9339579f87686b"]]},{"id":"dcf2ba70584d8069","type":"obniz-function","z":"8aa492ba5d2e627a","obniz":"37ad24e55808fdb7","name":"","code":"let degrees = context.get('degrees') || 90; \r\ncontext.set('degrees', degrees);\r\n\r\n//音用のスリープ時間定義\r\nconst sleep = (msec) => new Promise(res => setTimeout(res, msec));\r\n\r\nif (msg.payload === 'ご飯') {\r\n    // ご飯なら音を鳴らしてサーボモーター起動\r\n    degrees = 90.0;\r\n    // ソ↓\r\n    obnizParts.Speaker.play(783.98); await sleep(400); obnizParts.Speaker.stop(); await sleep(100);\r\n    // ラ↓\r\n    obnizParts.Speaker.play(879.99); await sleep(400); obnizParts.Speaker.stop(); await sleep(100);\r\n    // ソ↓\r\n    obnizParts.Speaker.play(783.98); await sleep(400); obnizParts.Speaker.stop(); await sleep(100);\r\n    // ド↓\r\n    obnizParts.Speaker.play(1046.5); await sleep(400); obnizParts.Speaker.stop(); await sleep(100);\r\n\r\n\r\n    // ソ↓\r\n    obnizParts.Speaker.play(783.98); await sleep(400); obnizParts.Speaker.stop(); await sleep(100);\r\n    // ラ↓\r\n    obnizParts.Speaker.play(879.99); await sleep(400); obnizParts.Speaker.stop(); await sleep(100);\r\n    // ソ↓\r\n    obnizParts.Speaker.play(783.98); await sleep(500); obnizParts.Speaker.stop(); await sleep(100);\r\n\r\n\r\n    // ラ↓\r\n    obnizParts.Speaker.play(879.99); await sleep(400); obnizParts.Speaker.stop(); await sleep(100);\r\n    // ラ↓\r\n    obnizParts.Speaker.play(879.99); await sleep(400); obnizParts.Speaker.stop(); await sleep(00);\r\n    // ソ↓\r\n    obnizParts.Speaker.play(783.98); await sleep(400); obnizParts.Speaker.stop(); await sleep(100);\r\n    // ラ↓\r\n    obnizParts.Speaker.play(879.99); await sleep(400); obnizParts.Speaker.stop(); await sleep(100);\r\n\r\n    // ソ↓\r\n    obnizParts.Speaker.play(783.98); await sleep(200); obnizParts.Speaker.stop(); await sleep(50);\r\n    // ファ↓\r\n    obnizParts.Speaker.play(698.45); await sleep(200); obnizParts.Speaker.stop(); await sleep(50);\r\n    // ミ↓\r\n    obnizParts.Speaker.play(659.25); await sleep(200); obnizParts.Speaker.stop(); await sleep(50);\r\n    // レ↓\r\n    obnizParts.Speaker.play(587.34); await sleep(200); obnizParts.Speaker.stop(); await sleep(50);\r\n    // ミ↓\r\n    obnizParts.Speaker.play(659.25); await sleep(300); obnizParts.Speaker.stop(); await sleep(100);\r\n    // ド↓\r\n    obnizParts.Speaker.play(523.23); await sleep(400); obnizParts.Speaker.stop(); await sleep(100);\r\n\r\n    // ソ↓\r\n    obnizParts.Speaker.play(783.98); await sleep(400); obnizParts.Speaker.stop(); await sleep(100);\r\n    // ラ↓\r\n    obnizParts.Speaker.play(879.99); await sleep(400); obnizParts.Speaker.stop(); await sleep(100);\r\n    // ソ↓\r\n    obnizParts.Speaker.play(783.98); await sleep(400); obnizParts.Speaker.stop(); await sleep(100);\r\n    // ド↓\r\n    obnizParts.Speaker.play(1046.5); await sleep(400); obnizParts.Speaker.stop(); await sleep(100);\r\n\r\n\r\n    // ソ↓\r\n    obnizParts.Speaker.play(783.98); await sleep(400); obnizParts.Speaker.stop(); await sleep(100);\r\n    // ラ↓\r\n    obnizParts.Speaker.play(879.99); await sleep(400); obnizParts.Speaker.stop(); await sleep(100);\r\n    // ソ↓\r\n    obnizParts.Speaker.play(783.98); await sleep(500); obnizParts.Speaker.stop(); await sleep(100);\r\n\r\n\r\n    // ラ↓\r\n    obnizParts.Speaker.play(879.99); await sleep(400); obnizParts.Speaker.stop(); await sleep(100);\r\n    // ラ↓\r\n    obnizParts.Speaker.play(879.99); await sleep(400); obnizParts.Speaker.stop(); await sleep(100);\r\n    // ソ↓\r\n    obnizParts.Speaker.play(783.98); await sleep(400); obnizParts.Speaker.stop(); await sleep(100);\r\n    // ラ↓\r\n    obnizParts.Speaker.play(879.99); await sleep(400); obnizParts.Speaker.stop(); await sleep(100);\r\n\r\n    // ソ↓\r\n    obnizParts.Speaker.play(783.98); await sleep(200); obnizParts.Speaker.stop(); await sleep(50);\r\n    // ファ↓\r\n    obnizParts.Speaker.play(698.45); await sleep(200); obnizParts.Speaker.stop(); await sleep(50);\r\n    // ミ↓\r\n    obnizParts.Speaker.play(659.25); await sleep(200); obnizParts.Speaker.stop(); await sleep(50);\r\n    // レ↓\r\n    obnizParts.Speaker.play(587.34); await sleep(200); obnizParts.Speaker.stop(); await sleep(50);\r\n    // ド↓\r\n    obnizParts.Speaker.play(523.23); await sleep(400); obnizParts.Speaker.stop(); await sleep(200);\r\n\r\n    \r\n} else {\r\n    // 終わりのメッセージならサーボモーターで蓋をする\r\n    degrees = 0.90;\r\n}\r\n\r\n// サーボを指定の角度まで動かす\r\nobnizParts.servo.angle(degrees);","x":338.99996185302734,"y":359.99997901916504,"wires":[[]]},{"id":"4630479f668841c5","type":"ReplyMessage","z":"8aa492ba5d2e627a","name":"","replyMessage":"","x":718.9999618530273,"y":419.99997901916504,"wires":[]},{"id":"ee9339579f87686b","type":"template","z":"8aa492ba5d2e627a","name":"","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"メニューからコーギーかご飯を選んでね！","output":"str","x":338.99996185302734,"y":419.99997901916504,"wires":[["4630479f668841c5"]]},{"id":"c7b24568b7a00d01","type":"comment","z":"8aa492ba5d2e627a","name":"超音波距離センサー","info":"","x":328.99996185302734,"y":219.99997901916504,"wires":[]},{"id":"8e44c19d11e26923","type":"comment","z":"8aa492ba5d2e627a","name":"スピーカー + サーボモータ","info":"","x":348.99996185302734,"y":319.99997901916504,"wires":[]},{"id":"37ad24e55808fdb7","type":"obniz","obnizId":"XXXX-XXXX","deviceType":"obnizboard","name":"","accessToken":"","code":"obniz.display.clear(); // 画面を消去\r\n\r\nobnizParts.hcsr04 = obniz.wired(\"HC-SR04\",{ gnd:0, echo:1, trigger:2, vcc:3 });\r\nobnizParts.Speaker = obniz.wired(\"Speaker\",{ signal:6, gnd:7 });\r\nobnizParts.servo = obniz.wired(\"ServoMotor\", { gnd: 9, vcc: 10, signal: 11 });"}]
```

### ポイント①：超音波距離センサー
![スクリーンショット 2023-11-03 201941.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3567275/4b7fe10e-f65c-52d9-0815-a39cb1c96e8b.png)
超音波距離センサーでは、LINEのメッセージが契機となりセンサーから対象物までの距離を測定しております。

**＜距対象物との距離が20cm以内の場合＞**
『近くにいるよ』とメッセージを送信します。

**＜距対象物との距離が20cm以上の場合＞**
『近くにいないよ』とメッセージを送信します。


### ポイント②：スピーカー + サーボモータ
![スクリーンショット 2023-11-03 203440.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3567275/61a5d78d-e00d-5db9-5319-e19345ddb986.png)
ここではLINEのメッセージを契機となり、スピーカーとサーボモータを動かす仕組みを実装しております。

**＜LINEから『ご飯』とメッセージが届いた場合＞**
スピーカーでご飯が炊ける音を出して、サーボモータでご飯の出し口を開きます。

**＜LINEから『ごちそうさま』とメッセージが届た場合＞**
サーボモータでご飯の出し口を閉じます。

### 装置の工作
ブレッドボード・センサー類をジャンパワイヤでobnizと繋ぎ合わせます。
obnizの準備はこれで完成しました！

<img src = https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3567275/e85430ca-21ff-5cc2-7a2f-e5762ec4300a.png width = "80%">



最後に家にあったペットボトルを切り、高さが出る箱を準備し、ガムテープで張り付けて自動給餌マシンの完成！（人力感がすごい・・・）
これを動かした結果が最初のリンクとなります！無事動いてよかった！！

<img src = https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3567275/14796a8d-0408-d4a1-607f-ac0573c12523.jpeg width = "80%">

## コーギーのお迎え準備完了！！
今回は限られたパーツと家にあるモノでの工作であったため、少々安っぽい完成品になってしまいましたが、さらによくするために以下のようなバージョンアップを考えております。

::: note info
**【システム面】**
- サーボモータを複数使いご飯の種類やお水選択して出せるようにする
- Webカメラを組み合わせてペット見守りの機能も追加する


**【工作面】**
- ご飯の出し口にスライダーを付けてこぼれないようにする 
- ご飯とサーボモータを入れる専用の箱を自作する
:::



**という事でもう未来の愛犬にご飯の心配をさせる事はございません！！**
いつでも我が家においで！！私のコーギー🐕


## 参考情報
https://qiita.com/tseigo/items/56c78be82b6276825ca6