beacon
linebot
microbit
LINEmessagingAPI
LINEBeacon

LINE Beaconを自宅に住まわせる

LINEはほんとにいろんなサービスをしていますよね。

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

今回は、LINE Beaconで遊びたいと思います。

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

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

[ハードウェア]


  • micro:bit

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

[ソフトウェア]

[RESTful実行環境]

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


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#/

image.png

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

image.png

アカウント一覧から、先ほど作成したボットを選択します。

ハードウェアID発行 ボタンを押下すると、5バイトの16進数文字列が表示されました。

image.png

[Arduino IDEの場合]

①を採用した場合です。

このハードウェア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();
}


[MakeCodeの場合]

②を採用した場合です。

ハードウェアIDを「LINE Beacon start HWID is」のところに入力しておきます。

ボタンAを押すと10秒間だけアドバタイズされるようにしています。

image.png

以上、書き込みが終わったら、電源を入れなおせば、LINE Beaconとして動作しているはずです。

Androidアプリ「nRF Connect」などを使えば、SCANNERに引っかかっているかと思います。(ちょっと見方が難しいですが)


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) => {
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) =>{
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がたくさんありますので、いろいろ組み合わせてみると、ボットに愛着がわいてくるかもしれませんね。

以上です。