Open10

obnizとLINE Botを組み合わせて、ビールの飲み頃温度を教えてくれるIoTに挑戦中のメモ

TANAKA HIROKITANAKA HIROKI

AMG8833使用 赤外線アレイセンサ(Grid-EYE)モジュールが未だ届かず、まずは手元にある温度センサーとLINE Botの組み合わせを確認するところから始める

TANAKA HIROKITANAKA HIROKI

【復習】 環境と整える

npm管理ができるように初期化

  • npm init -y

SDKのインストール

  • npm i @line/bot-sdk
  • npm i @line/bot-sdk express

expressのインストール

  • npm i express

ターミナルをもう1つ用意する、ngrokのインストール

  • npm i -g ngrok

obnizパッケージのインストール

  • npm i obniz
TANAKA HIROKITANAKA HIROKI

復習

【LINE Bot】 バイクで走るタイムリミットコード
'use strict';

// ########################################
//               初期設定など
// ########################################

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

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

// Messaging APIで利用するクレデンシャル(秘匿情報)です。
const config = {
    channelSecret: 'チャネルシークレット',
    channelAccessToken: 'チャネルアクセストークン'
};



// ########## ▼▼▼ サンプル関数 ▼▼▼ ##########
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 = '';
        try {
            // axiosで日の出日の入り時刻のAPIを叩きます(少し時間がかかる・ブロッキングする)
            const res = await axios.get('https://api.sunrise-sunset.org/json?lat=35.7772463&lng=138.9766782');
            // 取得できるのはUTCなので日本時間(+9時間)になおす
            const utc_time = res.data.results.sunrise;
            // '時', '分', '秒 PM' に分割する
            const tm_split = utc_time.split(':');
            // '時' を9時間進めて12時間戻す(13時を過ぎないようにする)
            const jp_hour = Number(tm_split[0]) + 9 - 12;
            // '秒 PM' を '秒' だけにする
            const sec = tm_split[2].split(' ')[0];
            // 再構成する
            const time_string = `${jp_hour}${tm_split[1]}${sec}`;
            pushText = `今日のタイムリミットは${time_string}です!渋滞前に帰りましょう!`;
        } 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によるサーバー部分
// ########################################

// 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サーバーを実行中です…`);
TANAKA HIROKITANAKA HIROKI

復習

【obniz】 ビールの飲み頃温度 4℃以上になったらLEDが光る
const Obniz = require('obniz');
const obniz = new Obniz('0000-0000'); // Obniz_IDに自分のIDを入れます

obniz.onconnect = async function () {
    var led = obniz.wired("LED", {anode:10, cathode:11});
    led.output(true);

    const tempsens = obniz.wired('LM60', { gnd: 0, output: 1, vcc: 2 });
    // setIntervalで間隔を作る
    setInterval(async function () {
        // 同期で取得
        const temp = await tempsens.getWait();
        // 温度をコンソールに表示
        console.log(temp);
        // displayに反映
        obniz.display.clear();
        obniz.display.print(temp + 'C');  // 英語が出力できる
    }, 1000); // 1000ミリ秒 = 1秒  

  for (let i = 0; i < 100; i++) {
    tempsens.onchange = (function(temp){
        console.log(temp);
      
      if (temp > 4) {
        led.on();
      } else {
        led.off();
      }
    });
    await obniz.wait(100);
  }
}
TANAKA HIROKITANAKA HIROKI

全力の参考:LINEBotに尋ねたら温度センサーで室温を計ってみた

サンプルコード
const Obniz = require('obniz');
const obniz = new Obniz('obnizのID');  // Obniz_IDに自分のIDを入れてください

// obnizと接続確立したとき
obniz.onconnect = async () => {
    obniz.display.clear();
    obniz.display.print('obniz Ready');
}
// 温度センサから値を取得して返す
const getObnizTemp = async () => {
    // 温度センサの利用
    const tempsens = obniz.wired('LM60', { gnd: 0, output: 1, vcc: 2 });
    // RGB LEDを利用
    const rgbled = obniz.wired('WS2811', { gnd: 9, vcc: 10, din: 11 });
    // 非同期で取得
    const temp = await tempsens.getWait();

    if (temp>25) {
        // オレンジ
        rgbled.rgb(255, 165, 0);
    } else {
        // 水色
        rgbled.rgb(0, 191, 250);
    }

    // ターミナル表示
    console.log('obniz temp:', temp);
    // obnizディスプレイ表示
    obniz.display.clear();
    obniz.display.print(temp + ' C');
    // 温度値を返す
    return temp;
}

// ########################################
//          LINEBot イベント処理部分
//  channelSecret:LINE Developers → チャネル基本設定 → チャネルシークレット
//  channelAccessToken:LINE Developers → Messaging API設定 → チャネルアクセストークン(長期)
//  ターミナルで `ngrok http 3000` 実行後、発行されたURLをWebhook URLとして設定するのを忘れずに
//  「検証」ボタンをクリックするとターミナルにエラーが出ますがここでは問題ありません(検証イベントのハンドリングをしていないため)
// ########################################
const config = {
    channelSecret: '作成したBotのチャネルシークレット',
    channelAccessToken: '作成したBotのチャネルアクセストークン'    
};
const line = require('@line/bot-sdk');
const client = new line.Client(config);
// ExpressからMessaging APIイベントを渡されて処理するところ
const handleEvent = async (event) => {
    // テキストメッセージ以外を受信したときは何も行わずresolveを返す
    if (event.type !== 'message' || event.message.type !== 'text') {
        return Promise.resolve(null);
    }
    // テキストメッセージを受信したとき
    if (event.message.text === '温度教えて') {
        // 待ってねというメッセージを「リプライ」で先に返す
        client.replyMessage(event.replyToken, {
            type: 'text',
            text: '少々お待ちください!'
        });
        // obnizの温度センサから値をとってくる(ブロッキング・時間のかかる処理で一旦ここで止まる)
        const temp = await getObnizTemp();
        // tempが取得できたらそれを含めたメッセージを「プッシュ」で送信する
        client.pushMessage(event.source.userId, {
            type: 'text',
            text: '今の温度は' + temp + '度!',
        });
    } else {
        // メッセージの中身が「温度教えて」以外だったとき
        client.replyMessage(event.replyToken, {
            type: 'text',
            text: '「温度教えて」と話しかけてね!'
        });
    }
    // resolveを返す
    return Promise.resolve(null);
}

// ########################################
//          Expressサーバー部分
// ########################################
const express = require('express');
const PORT = process.env.PORT || 3000;
const app = express();
// 「(サーバーURL)/webhook」にアクセス(LINEサーバーからのWebhook)があったとき
app.post('/webhook', line.middleware(config), (req, res) => {
    // 受信したイベントをターミナルに表示
    console.log(req.body.events);
    // イベントをhandleEventに渡して1つずつ処理
    Promise.all(
        req.body.events.map(handleEvent)
    ).then(
        result => res.json(result)
    );
});
// PORT番号のポートでサーバーを開始
app.listen(PORT);
console.log('express runnning: PORT =', PORT);
TANAKA HIROKITANAKA HIROKI

カッコの位置要注意、どこからどこまでの範囲か

const Obniz = require('obniz');
const obniz = new Obniz('0000-0000');  // Obniz_IDに自分のIDを入れてください

// obnizと接続確立したとき
obniz.onconnect = async () => {
    obniz.display.clear();
    obniz.display.print('obniz Ready');
}
// 温度センサから値を取得して返す
const getObnizTemp = async () => {
    // 温度センサの利用
    const tempsens = obniz.wired('LM60', { gnd: 0, output: 1, vcc: 2 });
    // RGB LEDを利用
    const rgbled = obniz.wired('WS2811', { gnd: 9, vcc: 10, din: 11 });
    // 非同期で取得
    const temp = await tempsens.getWait();

    if (temp > 4) {
        // ブルー
        rgbled.rgb(0, 51, 255);
    } else {
        // ブラック
        rgbled.rgb(0, 0, 0);
    }

    // ターミナル表示
    console.log('obniz temp:', temp);
    // obnizディスプレイ表示
    obniz.display.clear();
    obniz.display.print(temp + ' C');
    // 温度値を返す
    return temp;
}

// ########################################
//          LINEBot イベント処理部分
//  channelSecret:LINE Developers → チャネル基本設定 → チャネルシークレット
//  channelAccessToken:LINE Developers → Messaging API設定 → チャネルアクセストークン(長期)
//  ターミナルで `ngrok http 3000` 実行後、発行されたURLをWebhook URLとして設定するのを忘れずに
//  「検証」ボタンをクリックするとターミナルにエラーが出ますがここでは問題ありません(検証イベントのハンドリングをしていないため)
// ########################################
const config = {
    channelSecret: '作成したBOTのチャネルシークレット',
    channelAccessToken: '作成したBOTのチャネルアクセストークン'    
};
const line = require('@line/bot-sdk');
const client = new line.Client(config);
// ExpressからMessaging APIイベントを渡されて処理するところ
const handleEvent = async (event) => {
    // テキストメッセージ以外を受信したときは何も行わずresolveを返す
    if (event.type !== 'message' || event.message.type !== 'text') {
        return Promise.resolve(null);
    }
    // テキストメッセージを受信したとき
    if (event.message.text === 'ビールの温度を教えて!') {
        // 測定中というメッセージを「リプライ」で先に返す
        client.replyMessage(event.replyToken, {
            type: 'text',
            text: '測定中、、、'
        });
        // obnizの温度センサから値をとってくる(ブロッキング・時間のかかる処理で一旦ここで止まる)
        const temp = await getObnizTemp();
        // tempが取得できたらそれを含めたメッセージを「プッシュ」で送信する
        client.pushMessage(event.source.userId, {
            type: 'text',
            text: 'ビールの温度は' + temp + '度です!',
        });
    } else {
        // メッセージの中身が「温度教えて」以外だったとき
        client.replyMessage(event.replyToken, {
            type: 'text',
            text: 'ビール画像をタップしてね!'
        });
    }
    // resolveを返す
    return Promise.resolve(null);
}

// ########################################
//          Expressサーバー部分
// ########################################
const express = require('express');
const PORT = process.env.PORT || 3000;
const app = express();
// 「(サーバーURL)/webhook」にアクセス(LINEサーバーからのWebhook)があったとき
app.post('/webhook', line.middleware(config), (req, res) => {
    // 受信したイベントをターミナルに表示
    console.log(req.body.events);
    // イベントをhandleEventに渡して1つずつ処理
    Promise.all(
        req.body.events.map(handleEvent)
    ).then(
        result => res.json(result)
    );
});
// PORT番号のポートでサーバーを開始
app.listen(PORT);
console.log('express runnning: PORT =', PORT);