このお話は、IoT縛りの勉強会! IoTLT vol.38 @ ウイングアーク1stでLTした内容です。
ウケ狙いでLTしたため、技術的なことは喋りませんでした、ここに書いておきます。
LTの内容(SlideShare)
概要は上記資料をみてください。
プログラムの解説
システム構成
ハードウエア(バックル部)
材料
下記を使いました
-
ピンバッチのケース
ちょうど大きさがミニブレッドボードにあっていました。 -
BLE Nano
https://www.switch-science.com/catalog/3444/
BLE通信ができる超小型のマイコンです、MbedでもArduinoでも開発できます
今回は慣れているArduinoでコード書きました
本体と書き込み装置があるので両方買いました -
ボタン電池基板取付用ホルダー
http://akizukidenshi.com/catalog/g/gP-00706/ -
リードスイッチ
http://akizukidenshi.com/catalog/g/gP-03676/
近くに磁石があると電気が通じるスイッチです
ソフトウエア
バックル部(arduino)
プログラムの説明
ArduinoでLINE Beaconの信号の送信処理を行います
- hwid[]にLINE BeaconのHWIDを設定してください
- advertising()関数:ビーコンのパケットを送信します、連続送信は行いません、ただし送信を停止します
- setup()関数:初期化を行います、電池を持たせるために通常はLEDを消灯しています、なので電源ON時10間だけ点滅させてます
- loop()関数:D2につけたリードスイッチがOFF(磁石が離れた=チェックが下がった)になったらLEDを点灯します、、3秒間連続でOFFだとビーコンのパケットを送信します
#include <nRF5x_BLE_API.h>
#include "setting.h"
BLEDevice ble;
uint8_t sid[] = {0x6F, 0xFE};
uint8_t sdata[] = {0x6F, 0xFE, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x00};
uint8_t hwid[] = {0x00, 0x00, 0x00, 0x00, 0x00}; // LINE BeaconのHWID
int cnt = 0;
void advertising() {
ble.init();
ble.accumulateAdvertisingPayload(GapAdvertisingData::BREDR_NOT_SUPPORTED | GapAdvertisingData::LE_GENERAL_DISCOVERABLE);
ble.accumulateAdvertisingPayload(GapAdvertisingData::COMPLETE_LIST_16BIT_SERVICE_IDS , sid, sizeof(sid));
ble.accumulateAdvertisingPayload(GapAdvertisingData::SERVICE_DATA , sdata, sizeof(sdata));
ble.setAdvertisingType(GapAdvertisingParams::ADV_NON_CONNECTABLE_UNDIRECTED);
ble.setAdvertisingInterval(160); // 100ms; in multiples of 0.625ms
ble.setAdvertisingTimeout(5); // 単位は秒
ble.startAdvertising();
ble.waitForEvent();
ble.stopAdvertising();
ble.shutdown();
delay(150);
}
void setup() {
Serial.begin(9600);
NRF_POWER->DCDCEN = 0x00000001;
pinMode(LED, OUTPUT);
pinMode(D2, INPUT_PULLUP);
memcpy(&sdata[3], hwid, 5);
for (int i = 0; i < 10; i++) { // 10秒間ウエイト
digitalWrite(LED, HIGH); // LED ON
delay(500);
digitalWrite(LED, LOW); // LED OFF
delay(500);
}
}
void loop() {
int data = digitalRead(D2);
if (data) {
if (++cnt & 0x1) {
digitalWrite(LED, HIGH);
} else {
digitalWrite(LED, LOW);
}
if (cnt > 30 ) { // 3秒間
digitalWrite(LED, HIGH);
advertising(); // チャックが開いている時だけ通信
}
} else {
cnt = 0;
digitalWrite(LED, LOW);
}
Serial.println(data, HEX);
delay(100);
}
サーバ(LINE BOT)
プログラムの説明
HerokuやAzure APP Service上のnodeで動作するプログラムです
- LINE_CHANNEL_ACCESS_TOKENにLINEのアクセストークンを設定してください
- TWILIO_CALL_URLには、後述のTwilio Functionsの/chuckcallのURLを設定してください
- TWILIO_CALL_BODYは携帯の電話番号を設定します「+81」をつけるために「%2B81」とコードを書いています
- function reply(event, text)関数:LINEの画面にメッセージを表示します
- function select(id, hwid)関数:LINEの画面に選択画面を表示します、電話するを選択するとtwilio()関数を呼びます
- app.post('/', function(request, response)関数:LINEからWebHookされます、ビーコンを検知したらselect()関数を、検知しなくなったらreply()関数を呼びます
- function twilio()関数:Twilio Functionsの/chuckcallを呼びます
var express = require('express');
var bodyParser = require('body-parser');
var request = require('request');
var app = express();
var LINE_CHANNEL_ACCESS_TOKEN = "LINEのアクセストークン";
var TWILIO_CALL_URL = "TwilioのURL";
var TWILIO_CALL_BODY = 'NumberToCall=%2B819012345678携帯の電話番号';
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(express.static('img'));
app.set('port', (process.env.PORT || 5000));
function reply(event, text) {
var headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + LINE_CHANNEL_ACCESS_TOKEN
}
console.log(headers);
var body = {
replyToken: event.replyToken,
messages: [{
type: 'text',
text: text
}]
}
console.log(body);
var url = 'https://api.line.me/v2/bot/message/reply';
request({
url: url,
method: 'POST',
headers: headers,
body: body,
json: true
}, function(error, response, bdy){
if (!error && response.statusCode == 200) {
console.log(bdy);
} else {
console.log('error: '+ response.statusCode);
}
});
}
function select(id, hwid) {
var headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + LINE_CHANNEL_ACCESS_TOKEN
}
var body = {
to: id,
messages: [{
type: 'template',
altText: 'チャックを開けている人を発見',
template: {
type: "buttons",
thumbnailImageUrl: 'https://shakainomado.azurewebsites.net/chuck.png',
title: '近くにチャックを開けている人がいます ' + hwid,
text: '探して、伝えてもらえませんか?\n恥ずかしいのであれば、こちらが電話で通知します\nどうします?',
actions: [
{
type: 'postback',
label: '私が伝えます',
data: 'none'
},
{
type: 'postback',
label: '電話で教えてあげて',
data: 'tel'
}
]
}
}]
}
var url = 'https://api.line.me/v2/bot/message/push';
request({
url: url,
method: 'POST',
headers: headers,
body: body,
json: true
}, function(error, response, body){
if (!error && response.statusCode == 200) {
//console.log(body.name);
} else {
console.log('error: '+ response.statusCode);
}
});
}
app.post('/', function(request, response) {
console.log('post');
console.log(request.body);
response.sendStatus(200);
for (var event of request.body.events) {
console.log('event.type : ' + event.type);
console.log('event.source : ' + event.source);
if (event.source.type == 'user') {
console.log('event.source.userId : ' + event.source.userId);
}
if (event.type == 'message') {
console.log('message : ' + event.message.text);
} else if (event.type == 'beacon') {
console.log('*event.beacon.type : ' + event.beacon.type);
console.log('*event.beacon.hwid : ' + event.beacon.hwid);
console.log('*event.beacon.dm : ' + event.beacon.dm);
console.log('*event.source.userId : ' + event.source.userId);
if (event.beacon.type == 'enter') {
select(event.source.userId, event.beacon.hwid);
} else {
reply(event, 'チャックを閉めたか、あるいは離れていきました ' + event.beacon.hwid);
}
}
if (event.type == 'postback') {
console.log('postback: '+ event.postback.data);
if (event.postback.data == 'tel') {
twilio();
}
}
}
});
app.listen(app.get('port'), function() {
console.log("Node app is running at localhost:" + app.get('port'))
});
function twilio() {
var headers = {
'Accept': '*/*',
'Content-Type': 'application/x-www-form-urlencoded'
}
var body = TWILIO_CALL_BODY;
var url = TWILIO_CALL_URL;
request({
url: url,
method: 'POST',
headers: headers,
body: body,
json: false
});
}
Twilio
電話をかけるTwilio Functionsです、電話をかける/chuckcallを音声を返す/chuckvoiceの2つのFunctionsを用意します
/chuckcall(Twilio Functions)
exports.handler = function(context, event, callback) {
const client = context.getTwilioClient()
client.calls.create({
to: event.NumberToCall,
from: context.PHONE_NUMBER,
url: 'https://*****.twil.io/chuckvoice
のURL'
},
function(err, res) {
callback(err, "OK")
})
};
/chuckvoice(Twilio Functions)
exports.handler = function(context, event, callback) {
let twiml = new Twilio.twiml.VoiceResponse();
twiml.say("こんにちは。ちゃっくが、あいていますよ。きをつけましょうね。", {voice : "", language: "ja-JP"});
callback(null, twiml);
};
一昨年のLINE BOT AWARDSグランプリの&HAND(アンドハンド)も同じコンセプトでした
https://www.andhand-project.com/
このようなサービスが、本当に使われるようになるといいですね