Help us understand the problem. What is going on with this article?

LINE Beaconを自宅に住まわせる

LINEはほんとにいろんなサービスをしていますよね。
LINEアプリを使うようになってから、便利なことが増えました。そして、そんなたくさんのサービスをWebの技術の勉強のネタに使わせてもらってます。

今回は、LINE Beaconで遊びたいと思います。
どんなことをするかというと、自宅に帰ると「おかえりなさい。今日も一日お疲れさま。」とLINE通知してくれたり、自宅を出ると「いってらっしゃい!今日の天気は晴れだよ。」なんてことをLINE通知してくれるようにしたいと思います。

使うのは、以下の通りです。

[ハードウェア]

  • micro:bit

BLEビーコン端末として使います。LINE Beaconの仕様を満たすものであれば、なんでもよいですが、ちょうど手元に使っていないmicro:bitがあったので、それを使いました。BLEのアドバタイズデータを自由にできるのであれば、なんでもLINE Beacon対応になれそうです。

[ソフトウェア]

[RESTful実行環境]

そして、ハードウェアからの通知を受け取って、ソフトウェアを組み合わせてLINE通知を返すRESTful実行環境が必要です。SSLサーバである必要があります。

(2020/2/11 修正)
・ハードウェアIDの払い出しのURLを追記しました。(こめんといただきありがとうございました!)
・docomo自然対話(雑談対話)が使えなくなるとのことで、UserLocalの人工知能チャットボットも併記しました。

micro:bitをLINE Beacon化する

以下の方が、micro:bitをLINE Beacon化してくださっています(ありがとうございます!)

LINE Simple Beacon with BBC micro:bit
pxt-linebeacon

丁寧に説明していただいていますので、特に問題なくできると思います。
説明の中でも説明がありますが、サンプルプログラムを実行(コンパイル含む)をする前に、これから使うLINE Beacon端末にユニークな番号を付与する必要があります。
LINE Beacon端末には、どのLINEアプリにも反応するのではなく、いづれかのLINEボット(Messaging API)に紐づけられ、そのボットを友達にすることで、LINEアプリにLINE Beaconからの通知が行くようになります。

ということで、まずはボットを作成しておきます。
以下で、プロバイダを作成します。(未作成の場合)
 https://developers.line.biz/console/

それから、Messaging APIの新規チャネル(ようするにLINEボット)を作成します。(未作成の場合)

image.png

たとえば、「TestBeaconBot」という名前を付けます。
プランはフリーを選択しました。

そして、いよいよ、ユニークな番号であるハードウェアIDを払い出します。
 https://admin-official.line.me/beacon/register#/
または
 https://manager.line.biz/beacon/register

image.png

LINE Simple BeaconのハードウェアIDの払い出しを選択します。

image.png

アカウント一覧から、先ほど作成したボットを選択します。
ハードウェアID発行 ボタンを押下すると、5バイトの16進数文字列が表示されました。

image.png

[Arduino IDEの場合]

①を採用した場合です。

使用させていただいたライブラリはこちらです。

LINE Simple Beacon with BBC micro:bit (pizayanz/arduino-LINESimpleBeacon)
 https://github.com/pizayanz/arduino-LINESimpleBeacon

依存するライブラリは以下です。上記のライブラリのReadmeに丁寧に記載していただいています。
・Nordic Semiconductor nRF5 based boards
・BLEPeripheral by Sandeep Mistry

このハードウェアIDを、Arduino IDEから、以下のソースの該当部分に入力して、マイコンボード(micro:bit)に書き込みを行います。

LINESimpleBeacon.ino
// Copyright (c) Pizayanz. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#include <LINESimpleBeacon.h>

// https://admin-official.line.me/beacon/register#/
// LINEから払い出されたhw idを2桁区切りにして0x__の部分に入れる
unsigned char hwid[5] = {0xXX, 0xXX, 0xXX, 0xXX, 0xXX};

LINESimpleBeacon lineSimpleBeacon = LINESimpleBeacon(hwid);

void setup() {
  Serial.begin(9600);

  // deviceMessage は 13文字まで
  lineSimpleBeacon.begin("foobarbaz");

  Serial.println(F("Line Simple Beacon"));
}

void loop() {
  lineSimpleBeacon.loop();
}

※書き込むときのボード設定は以下のようにしてください。(忘れやすいので)
・ボード:BBC micro:bit
・Softdevice:S110またはS130

[MakeCodeの場合]

②を採用した場合です。

Microsoft MakeCode for micro:bit
 https://makecode.microbit.org/

LINE Beacon for micro:bit (pizayanz/pxt-linebeacon)
 https://github.com/pizayanz/pxt-linebeacon

ハードウェアIDを「LINE Beacon start HWID is」のところに入力しておきます。
ボタンAを押すと10秒間だけアドバタイズされるようにしています。

image.png

以上、書き込みが終わったら、電源を入れなおせば、LINE Beaconとして動作しているはずです。
Androidアプリ「nRF Connect」などを使えば、SCANNERに引っかかっているかと思います。(ちょっと見方が難しいですが)

※最近MakeCodeで書き込んだらなぜかエラーが発生するようになったなあ。。。

LINE Beaconからの通知を受け取る

実はもうこれで、紐づけたボット(TestBeaconBot)を友達に加えれば、LINE Beaconからの通知が出るのですが、受け取る側を用意していませんでした。

そのためには、TestBeaconBotのチャネル基本設定で設定しておく必要があります。
Channel Secret は後で使いますので、覚えておきます。
アクセストークン(ロングターム)を使いますので、まだ発行していなければ発行して、それを覚えておきます。

Webhook送信は「利用する」に編集しておきます。
Webhook URLは、これから立ち上げるRESTful実行環境のエンドポイントのURLを指定します。
自動応答メッセージは「利用しない」に設定しておきましょう。

編集が終わったら、QRコード(アプリ検証時や友達に紹介する際にご活用ください)をAndroidからスキャンして、このボットを友達登録しておきます。

image.png

ちょっと横道にそれて、docomo自然対話(雑談対話)のアプリ登録

ボットっぽく会話をしたいため、docomo 自然対話(雑談対話)を利用します。

(参考)
https://dev.smt.docomo.ne.jp/?p=about.index

以下のページから、「申請する」に進み、アカウント登録とアプリ登録を済ませます。
それにより、API KEYが払い出されます。

次に、以下のページにある通り、雑談対話のためのユーザ登録をします。

https://dev.smt.docomo.ne.jp/?p=docs.api.page&api_name=natural_dialogue&p_name=api_4_user_registration#tag01

Postmanやcurlを使って、実施し、アプリケーションIDを取得しておきます。

RESTful実行環境の構築

これで準備が整いましたので、LINE Beaconからの通知を受け付けるRESTful実行環境を構築します。
毎度の通り、Swaggerを使ってRESTful実行環境を構築します。

以下をご参考にしてください。
SwaggerでRESTful環境を構築する

Swagger定義ファイルは以下の通りです。
適当に「/linebeacon」としています。TestBeaconBotのチャネル基本設定で指定したWebhook URLと同じにしてください。

swagger.yaml
  /linebeacon:
    post:
      x-swagger-router-controller: routing
      operationId: linebeacon
      parameters:
        - in: body
          name: body
          schema:
            type: object
      responses:
        200:
          description: Success
          schema:
            type: object

以下が、実装部分です。解説は後程。

index.js
const config = {
    channelAccessToken: process.env.LINE_CHANNEL_ACCESS_TOKEN,
    channelSecret: process.env.LINE_CHANNEL_SECRET,
};
const DOCOMO_API_KEY = process.env.DOCOMO_API_KEY || docomo Developer supportのアプリのAPI KEY;
const DOCOMO_APP_ID = process.env.DOCOMO_APP_ID || docomo Developer supportのアプリのID;

const LineUtils = require('../../helpers/line-utils');
const app = new LineUtils(config);
const fetch = require('node-fetch');

app.message(async (event, client) =>{
    console.log(event);

    var message = await do_talk(event.message.text);
    const echo = { type: 'text', text: message };
    return client.replyMessage(event.replyToken, echo);
});

app.beacon(async (event, client) =>{
    console.log(event);

    var message;
    if( event.beacon.type == 'enter'){
        message = 'おかえりなさい。';
        message += '今日も一日お疲れさまでした。';
    }else{
        message = 'いってらっしゃい!';
        var weather = await do_get_wether(14);
        var weather_message = parse_weather(weather.pref.area['東部'].info[0]);
        message += '\n横浜の天気は、' + weather_message.weather + '\n' + weather_message.rainfallchance;
    }
    const echo = { type: 'text', text: message };
    return client.replyMessage(event.replyToken, echo);
});

/* location: 13:東京、14:神奈川 */
function do_get_wether(location){
    return fetch('https://www.drk7.jp/weather/json/' + location + '.js', {
        method : 'GET'
    })
    .then((response) => {
        return response.text();
    })
    .then(text =>{
        text = text.trim();
        if( text.startsWith('drk7jpweather.callback(') )
            text = text.slice(23, -2);
        return JSON.parse(text);
    });
}

function parse_weather(info){
    var weather = info.weather + ' です。';
    var rainfallchance = "降水確率は、" +
    "0-6時 " + info.rainfallchance.period[0].content + info.rainfallchance.unit +
    "、6-12時 " + info.rainfallchance.period[1].content + info.rainfallchance.unit +
    "、12-18時 " + info.rainfallchance.period[2].content + info.rainfallchance.unit +
    "、17-24時 " + info.rainfallchance.period[3].content + info.rainfallchance.unit +
    " です。";

    return { weather: weather, rainfallchance: rainfallchance };
}

function do_talk(message){
    var url = 'https://api.apigw.smt.docomo.ne.jp/naturalChatting/v1/dialogue?APIKEY=' + DOCOMO_API_KEY;
    var body = {
        language: 'ja-JP',
        botId: 'Chatting',
        appId: DOCOMO_APP_ID,
        voiceText: message
    };
    return do_post(url, body)
    .then(result =>{
        console.log(result);
        return result.systemText.expression;
    });
}

function do_post(url, body){
    return fetch(url, {
        method : 'POST',
        body : JSON.stringify(body),
        headers: { "Content-Type" : "application/json; charset=utf-8" } 
    })
    .then((response) => {
        if(!response.ok)
            throw "status is not 200.";
        return response.json();
    });
}

exports.handler = app.lambda();

ユーティリティも示しておきます。line-utils.jsとresponse.jsです。

line-utils.js
'use strict';

const line = require('@line/bot-sdk');
const Response = require('./response');

class LineUtils{
    constructor(config){
        this.client = new line.Client(config);
        this.map = new Map();
    }

    message(handler){
        this.map.set('message', handler);
    }

    follow(handler){
        this.map.set('follow', handler);
    }

    unfollow(handler){
        this.map.set('unfollow', handler);
    }

    join(handler){
        this.map.set('join', handler);
    }

    leave(handler){
        this.map.set('leave', handler);
    }

    memberJoined(handler){
        this.map.set('memberJoined', handler);
    }

    memberLeft(handler){
        this.map.set('memberLeft', handler);
    }

    postback(handler){
        this.map.set('postback', handler);
    }

    beacon(handler){
        this.map.set('beacon', handler);
    }

    accountLink(handler){
        this.map.set('accountLink', handler);
    }

    things(handler){
        this.map.set('things', handler);
    }

    lambda(){
        return async (event, context, callback) => {
            var body = JSON.parse(event.body);

            return Promise.all(body.events.map((event) =>{
                if( (event.type == 'message') &&
                     (event.replyToken === '00000000000000000000000000000000' || event.replyToken === 'ffffffffffffffffffffffffffffffff' ))
                    return;

                var handler = this.map.get(event.type);
                if( handler )
                    return handler(event, this.client);
                else
                    console.log(event.type + ' is not defined.');
            }))
            .then((result) =>{
//                console.log(result);
//                return new Response(result);
                return new Response({});
            })
            .catch((err) => {
                console.error(err);
                const response = new Response();
                response.set_error(err);
                return response;
            });
        }
    }
};

module.exports = LineUtils;
response.js
class Response{
    constructor(context){
        this.statusCode = 200;
        this.headers = {'Access-Control-Allow-Origin' : '*'};
        if( context )
            this.set_body(context);
        else
            this.body = "";
    }

    set_error(error){
        this.body = JSON.stringify({"err": error});
    }

    set_body(content){
        this.body = JSON.stringify(content);        
    }

    get_body(){
        return JSON.parse(this.body);
    }
}

module.exports = Response;

以下のnpmモジュールを使います。

  • @line/bot-sdk
  • node-fetch

また、各自で、以下の環境変数を設定する必要があります。

LINE_CHANNEL_ACCESS_TOKEN
 さきほど覚えておいたLINEのアクセストークン(ロングターム)のことです。
LINE_CHANNEL_SECRET
 さきほど覚えておいたLINEのChannel Secretのことです。
DOCOMO_API_KEY
 さきほど覚えておいたdocomo Developer supportのAPI_KEYです。
DOCOMO_APP_ID
 さきほど覚えておいたdocomo Developer supportのアプリケーションIDのことです。

line-utils.js は、LINE BeaconからくるMessaging API形式の通知の受付を、Actions on Google SDKっぽくするために作りました。

以下のURLで示す「イベントのタイプを表す識別子」ごとに、受信後の処理を記述できるようにしています。

https://developers.line.biz/ja/reference/messaging-api/#anchor-ff6d9a0f9685bb1dfdde749b9044d243cadd542e

例えば、メッセージイベントの場合には、

app.message( (event, client) =>{
 /* 処理内容 */
});

と定義します。docomo 自然対話(雑談対話) では、ここに記述しています。
 function do_talk(message)
に実装をまとめています。

そして、LINE Beaconからの通知は、ビーコンイベントであり、

app.beacon( (event, client) =>{
 /* 処理内容 */
});

という感じで実装します。
event.beacon.type が 'enter' の場合には、ビーコンの受信圏内に入ったことを示し、'leave' の場合には、ビーコンの受信圏外に出たことを示します。(このleaveは廃止予定だそうです。残念!!!)

'leave' のときには、天気予報も返しています。
 function do_get_wether(location)
で、天気情報を取得して、
 function parse_weather(info)
で欲しい情報を抽出しています。
locationには、都市の番号を指定するのですが、神奈川が14で、東京が13でした。
以下のページに行くとたくさんリンクがありますが、所望の地域のURLから判断してください。今回は、神奈川を選択し、取得した情報のうちの東部地方(横浜近辺)を使っています。

気象庁の天気予報情報を XML で配信
 https://www.drk7.jp/weather/

動作確認

さっそく、RESTful実行環境を起動しましょう。
スマホのBluetoothは有効になっていますか?
そして、micro:bitの電源をOnにしましょう。

以下のようなLINE通知が来ましたでしょうか?

image.png

今度は、micro:bitの電源をOffにします。今度は以下のようなLINE通知が来ましたでしょうか?(ちょっと1分ほど時間がかかります)

image.png

(わかりやすくするために、Botの画像を初〇ミ〇にしています。。。)

適当に話しかけると、適当に返してくれますよ。

image.png

帰宅時、出社時に声をかけて欲しいことがほかにもありますか?
世の中には便利なWeb APIがたくさんありますので、いろいろ組み合わせてみると、ボットに愛着がわいてくるかもしれませんね。

(追記) UserLocalの人工知能チャットボット(chatbot)に置き換える

docomo自然対話(雑談対話)が使えなくなるそうで、UserLocalのチャットボットに変えてみます。

まずは、以下から、個人開発者向け:ボットAPI利用申請をします。

人工知能チャットボット(chatbot)
 https://ai.userlocal.jp/document/free/top/

そうすると、API Keyが払い出されますので、それをメモっておきます。

あとは、関数do_talkを以下に置き換えれば、完成です。

index.js
const USERLOCAL_API_KEY = '【UserLocalのAPIKey】';

function do_talk(message){
    var body = {
        message: message,
        key: USERLOCAL_API_KEY,
    };
    return do_post('https://chatbot-api.userlocal.jp/api/chat', body)
    .then(json =>{
        console.log(json);
        return json.result;
    });
}

USERLOCAL_API_KEYのところは、取得したAPI Keyに置き換えてください。

以上です。

poruruba
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした