忙しいエンジニアに捧げる！obnizとLINE APIでレトルト在庫管理！

##　レトルト食品最高
突然ですが、レトルト食品って最高ですよね。
茹でればすぐに食べられて、なおかつ食費も抑えられる。時間・価格ともにコスパ最高です。
日夜忙しく働くエンジニアの皆さんの心強い味方といっても過言ではありません。

さらに、「安い、早い！！」だけではなく、最近のレトルトはシェフ監修なんて書かれていたりして、非常においしいものがたくさんあります。スーパーで選ぶのもちょっとした楽しみになるくらいです。

*↓推しレトルトパスタ*
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2135866/0a6bad92-9bcc-f577-878e-c3150f3969d3.png)


## 忙しい時に限ってなくなる
しかし、忙しい日が続いてしまうと、いつの間にかレトルト食品も底をつき、
一生懸命働いて帰ってきたのに食べるものがない...なんてことも。
そこで`obniz Board 1Y`というIoT向けのマイコンを使って、レトルトが尽きない仕組みを整えてみました！
![IMG_20211025_212159.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2135866/c49856b2-9188-b319-ad0f-c88afe8dd38e.jpeg)

https://obniz.com/ja/products/obnizboard

## 仕組み
### 1. obniz Board 1Yに超音波距離センサーを装着！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2135866/0b300499-2872-f876-8ca0-ee80b5fe47bb.png)


    
#### 2. パッケージまでの距離を図って、パッケージが何個並んでいるかを算出

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2135866/1387e509-67ab-f85b-a993-67df9067148f.png)


:::note info
便宜上、本棚にレトルトを置いています。
:::

### 3. LINE Messageing API を利用して、LINEからいつでも在庫数を確認！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2135866/fe8aaba0-b28a-232d-5507-83e8f65c8e23.png)


### 実行環境
API
・LINE Messaging API
・obniz ハードウェアAPI

実行環境
・obniz.js(JavaScript SDK)
・Node.js v6.10.0
・LINE Messaging API SDK 7.4.0
・Express 4.17.1
・ngrok 2.3.4
・Powershell 5.1.19041

機材
・obniz Board 1Y
・超音波距離センサー　HC-SR04


## コード
### 前準備部分
各種APIの利用準備や変数・定数を定義します。

```Javascript
// ########################################
//             Initialize Section
// ########################################

// パッケージを使用します
const express = require('express');
const line = require('@line/bot-sdk');
const Obniz = require('obniz');

// ngrokを介してローカル（自分のPC）でサーバーを公開するときのポート番号です
const PORT = process.env.PORT || 3000;

// LINE Messaging APIで利用するクレデンシャル（秘匿情報）です。
const config = {
    channelSecret: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
    channelAccessToken: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
  };
  
//obniz APIを利用
const obniz = new Obniz('obniz_ID'); // obniz_IDに自分のIDを入れます

//変数
var distance = 0; //壁からの距離(mm)

//定数
const Thickness = 22; //レトルトのパッケージの厚み(mm)
```
### obnizで距離を計測する
定期的に距離を測定して、変数に値を記録しておきます。
精度は重視していないため小数点は丸め込み、計測した距離を整数として扱います。

```Javascript
// ########################################
//               obniz Section 
// ########################################

obniz.onconnect = async function () {
    // 超音波距離センサを利用する
    const hcsr04 = obniz.wired('HC-SR04', { gnd: 0, echo: 1, trigger: 2, vcc: 3 });

    // ディスプレイ
    obniz.display.clear(); // クリア
    obniz.display.print('Ready');

    
    // setIntervalで一定間隔で処理
    setInterval(async function () {
        // 距離を取得
        distance = await hcsr04.measureWait();
        // そのままだと小数点以下の桁数がやたら多いので整数に丸める
        distance = Math.floor(distance);

        // 距離(mm)をターミナルに表示
        console.log(distance + ' mm');
        // obnizディスプレイに表示
        // 一度消してから距離+mmの単位を表示
        obniz.display.clear();
        obniz.display.print(distance + ' mm');

    }, 1000*60); //  1分ごとに取得
}

```

### LINE Messanging API でLINEに通知する
今回は、クライアントから「在庫確認」というメッセージを受け取ったら、
距離から在庫数を算出してユーザーに通知します。
なお、在庫数の算出は以下の通り。

```math
(棚の幅　- 　壁から在庫までの距離 - センサーの厚み)  /　パッケージの厚み
```

```Javascript
// ########################################
//      LINE Messaging API  Section
// ########################################
const sampleFunction = async (event) => {
    // ユーザーメッセージが「在庫確認」かどうか
    if (event.message.text !== '在庫確認') {
        return client.replyMessage(event.replyToken, {
            type: 'text',
            text: '「在庫確認」と話しかけてね'
        });
    } else {
        // 「リプライ」を使って先に返事しておきます
        await client.replyMessage(event.replyToken, {
            type: 'text',
            text: '調べています……'
        });     
        
        let pushText = '';
        let today_stock = Math.floor(
             //(棚の幅　- 壁から在庫までの距離 - センサーの厚み)  /　パッケージの厚み
            (300 - distance - 22) / Thickness );

        try {
            //返信用のテキストを作成
            pushText = `現在のレトルトの在庫は\n${today_stock}\nです！`;
        } catch (error) {
            pushText = '検索中にエラーが発生しました。ごめんね。';
            // APIからエラーが返ってきたらターミナルに表示する
            console.error(error);
        }

        // 「プッシュ」で後からユーザーに通知します
        return client.pushMessage(event.source.userId, {
            type: 'text',
            text: pushText
        });
    }
};



// ########################################
//  LINEサーバーからのWebhookデータを処理する部分
// ########################################

// LINE SDKを初期化します
const client = new line.Client(config);

// LINEサーバーからWebhookがあると「サーバー部分」から以下の "handleEvent" という関数が呼び出されます
async function handleEvent(event) {
    // 受信したWebhookが「テキストメッセージ以外」であればnullを返すことで無視します
    if (event.type !== 'message' || event.message.type !== 'text') {
        return Promise.resolve(null);
    }
    // サンプル関数を実行します
    return sampleFunction(event);
}



// ########################################
//          Express Section
// ########################################

// expressを初期化します
const app = express();

// HTTP POSTによって '/webhook' のパスにアクセスがあったら、POSTされた内容に応じて様々な処理をします
app.post('/webhook', line.middleware(config), (req, res) => {
  
  // 検証ボタンをクリックしたときに飛んできたWebhookを受信したときのみ以下のif文内を実行
  if (req.body.events.length === 0) {
    res.send('Hello LINE BOT! (HTTP POST)'); // LINEサーバーに返答します（なくてもよい）
    console.log('検証イベントを受信しました！'); // ターミナルに表示します
    return; // これより下は実行されません
  } else {
    // 通常のメッセージなど … Webhookの中身を確認用にターミナルに表示します
    console.log('受信しました:', req.body.events);
  }

  // あらかじめ宣言しておいた "handleEvent" 関数にWebhookの中身を渡して処理してもらい、
  // 関数から戻ってきたデータをそのままLINEサーバーに「レスポンス」として返します
  Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result));
});

// 最初に決めたポート番号でサーバーをPC内だけに公開します
// （環境によってはローカルネットワーク内にも公開されます）
app.listen(PORT);
console.log(`ポート${PORT}番でExpressサーバーを実行中です…`);
```

## 実測
サクっと作った割にはしっかり数えられていました！

### 5個
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2135866/f0f4b1bd-9ec2-7ac4-5bc4-97800ef31d8b.png)[![Image from Gyazo](https://i.gyazo.com/9ca942004da96d74963eaf4b5f24675c.gif)](https://gyazo.com/9ca942004da96d74963eaf4b5f24675c)

### 2個
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2135866/449ee95a-08f6-4e83-87aa-23a04257b264.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2135866/d41c6173-3cda-6b7e-1c22-74bff7c0c8dc.png)

### 在庫がない！！（0個）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2135866/041dc925-4959-4503-6aa5-622d46ee42d4.png)![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2135866/a8c6f44f-ba4b-65bc-daa6-2a31bcdd797a.png)


## 展望
### 減ってきたタイミングでPush通知したりTodoに追加する
- LINE Messager API を工夫
- TodoistのAPIとかでできそう

 https://developer.todoist.com/guides/#developing-with-todoist

### もっと多くのものを管理できるようにしたい
 - 塩、砂糖、米など重量でわかるもの
 
 https://qiita.com/iizuka2019/items/fb0307ec8e6bdc4ec689
 

### いろんな在庫を一元管理したい
 - 在庫数を一覧にしてサクっと確認。やはり簡単なDBはNotion API...!
 
 https://developers.notion.com/reference/update-a-database
 
## 最後に
:::note  warn
   パスタ（麺）の買い忘れにはくれぐれもご注意ください。
:::