LoginSignup
5
1

More than 5 years have passed since last update.

社会の窓締め忘れ通知装置 episode2

Last updated at Posted at 2018-12-19

このお話は、IoT縛りの勉強会! IoTLT vol.38 @ ウイングアーク1stでLTした内容です。
ウケ狙いでLTしたため、技術的なことは喋りませんでした、ここに書いておきます。

LTの内容(SlideShare)

thumbnail

概要は上記資料をみてください。

プログラムの解説

システム構成

imagel

ハードウエア(バックル部)

IMG_3045.jpg

材料

下記を使いました

ソフトウエア

バックル部(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/
このようなサービスが、本当に使われるようになるといいですね

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1