BLEビーコンで人・モノを検知した結果をリアルタイムに表示する【ORUYO/Nuxt×Firebase連携】

更新日:

ORUYOFirebaseNuxt

こんにちは。YUKIです。
今回はBLEビーコンを用いた人・モノ検知のデータの活用方法の一つとして、BLEビーコンをセンサーで検知し、Webでその結果をリアルタイムに表示方法をご紹介します。

BLEビーコンで人・モノを検知した結果をリアルタイムに表示する方法

システム構成

今回のシステムの全体構造は以下の通りです。
Realtime-view-0

サンプルレポジトリはこちら

今回はフロントエンドのフレームワーク/代表的なライブラリとしてとして以下を利用します。
・NuxtJS (https://ja.nuxtjs.org/)
・Vuetify (https://vuetifyjs.com/ja/)
・Vuexfire (https://vuefire.vuejs.org/vuexfire/)

前提

今回は簡易ではありますがWebシステムを構築することになりますので、前提としてJavascriptおよびHTMLの知識が必要となります。

また以下が用意さていることを前提とします。
・Googleアカウント
・yarn(もしくはnpm)が利用できる環境が用意されていること

Firebaseの設定

今回はデータの格納先としてCloud Firestore、APIの作成用にCloud Functions for Firebaseを利用します。

Firebaseにアクセス

FirebaseのコンソールにアクセスしGoogleアカウントで認証を行います。
https://console.firebase.google.com/?hl=ja

プロジェクトの作成

Firebaseのコンソールから「+プロジェクトの追加」をクリック、任意のプロジェクト名を入力後、「続行」をクリックします。
Realtime-view-1

Google アナリティクスの設定は今回は使用しないので、「有効」、「無効」どちらでも構いません。
「プロジェクトを作成」をクリックし、プロジェクトを作成します。
Realtime-view-2

Cloud Firestoreの作成

プロジェクト画面のサイドバーから「Database」をクリックし、「データベースの作成」をクリックします。

Realtime-view-3

通常はセキュリティ設定を行いますが、今回はお試しとして「テストモードで開始」を選択し、「次へ」をクリックします。
※今回は省略しますが、実際のプロジェクトとして利用する場合は、セキュリティルールを必ず適切に設定してください。

Realtime-view-4

ロケーションの設定では「asia-northeast1」を選択して完了を選択します。
※ロケーションによってレイテンシや料金が異なります。実際のプロジェクトではプロジェクトの特性に応じてロケーションを設定してください。

Realtime-view-5

プロジェクトIDの確認

「Project Overview」隣の歯車をクリックし、「プロジェクトの設定」をクリックしSettings画面を表示します。
「全般」タブでプロジェクトIDを確認します。
※後程使用します。

Realtime-view-6

一旦Firebaseの設定はここまでです。

エンドポイントの作成

Cloud Functions for FirebaseにORUYOからWebhookを受け付けるAPIのエンドポイントを作成します。

今回は簡易にするためにエンドポイントの認証などは行いません。実プロジェクトで利用する際は認証機能を実装するかAPI-GW(Apigee、Google Cloud Endpoints等)の利用をご検討ください。
今回はFirestoreの"detectData"というコレクションにデータを格納します。 その際にドキュメントのキーとしてビーコンIDを指定することで、同じビーコンの情報が通知された際に上書きして最新の情報のみ保存する様にします。 ※この辺のデータモデルは見せ方によって変えてください

Cloud Functions for Firebaseの設定

ターミナルからFirebaseにログインします。

$ npm install -g firebase-tools
$ firebase login

Cloud Functions for Firebaseの初期化を行います。

$ firebase init
(中略)
? Are you ready to proceed? (Y/n)  ⇒ Y
? Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confirm your choices. 
 ( ) Database: Deploy Firebase Realtime Database Rules
 ( ) Firestore: Deploy rules and create indexes for Firestore
>(*) Functions: Configure and deploy Cloud Functions
 ( ) Hosting: Configure and deploy Firebase Hosting sites
 ( ) Storage: Deploy Cloud Storage security rules
 ( ) Emulators: Set up local emulators for Firebase features

(中略)
? Please select an option: Use an existing project ← 既存のプロジェクトを使用
? Select a default Firebase project for this directory: (Use arrow keys)
ここで先ほど作成したFirebaseのプロジェクトを選択

(中略)
? What language would you like to use to write Cloud Functions? JavaScript
? Do you want to use ESLint to catch probable bugs and enforce style? Yes

(中略)

? Do you want to install dependencies with npm now? Yes

ご利用の環境のNodeJSのバージョンに併せてfunctions/package.jsonenginesを修正します。
functions/package.json

  "engines": {
    "node": "10"
  },

これで、Functions(エンドポイント(API))の作成準備が完了しました。

functionの作成

APIのメインとなるロジックを作成します。

functions/index.js

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const db = admin.firestore();

// エンドポイント関数の作成
exports.endpoint = functions
  .region("asia-northeast1")
  .https.onRequest(async (req, res) => {

    // HTTPリクエストメソッドをPOST以外はエラーとする
    if (req.method !== "POST") {
      return res.status(405).json({
        message: "Method Not Allowed"
      });
    }

    // beaconIdをキーとするため、beaconIdがないとエラー
    if (!req.body.beaconId) res.status(400);

    // beaconIdをキーとしてFirestoreに情報を格納
    await db
      .collection("detectData")
      .doc(req.body.beaconId)
      .set(req.body);

    // レスポンスを返却
    return res.status(200).json(req.body);
  });

functionを作成したら、Firebaseにデプロイします。

$ firebase deploy --only functions
(中略)
+  functions[endpoint(asia-northeast1)]: Successful create operation.
Function URL (endpoint): https://asia-northeast1-oruyo-realtime-sample.cloudfunctions.net/endpoint
               ↑このURLをWebhookURLとして使用
+  Deploy complete!

ORUYO側の通知設定

ORUYOに先ほど作成したエンドポイントをWebhookの通知先として登録します。
Webhookの通知先の基本的な登録方法はこちらをご覧ください。

通知先の追加は通知先を設定したいワークエリアの「通知先」タブを開き、「通知先設定を追加」をクリックします。
「通知方法」を「Webhook」に設定し、「Method」は「POST」を選択します。
「Webhook URL」には先ほど作成したエンドポイントを設定します。
Realtime-view-7

パラメータを以下の通り追加します。

パラメータ名 値種別
sensorId センサーID
beaconId ビーコンID
beaconName ビーコン名
detectDate 検知日時

Realtime-view-8

入力が完了したら「テスト」を実施します。
テストが正常に完了したらFirebaseコンソールの「Database」タブからテストで送信したデータが格納されていることを確認します。

Realtime-view-9

正常にテストデータが格納されていればORUYO → Cloud Fucntions for Firebase → Firestore連携は完了です。

フロントエンドアプリケーションの作成

ここからはFirestoreからデータを取得して表示するWebアプリケーションを作成します。

Nuxtプロジェクトの作成

基本的にはこちらを参考に進めます。

設定としては以下の通りです。

$ yarn create nuxt-app app

(中略)

? Choose the package manager Yarn
? Choose UI framework Vuetify.js
? Choose custom server framework None (Recommended)
? Choose Nuxt.js modules DotEnv
? Choose linting tools ESLint, Prettier, Lint staged files, StyleLint
? Choose test framework None
? Choose rendering mode Universal (SSR)
? Choose development tools jsconfig.json (Recommended for VS Code)

Firebase関連のパッケージのインストール

firebaseに接続するためのパッケージとvuexfireをインストールします。

$ cd app
$ yarn add firebase vuexfire core-js@2

Firebase接続設定

app/pluginsディレクトリの下にFirebase連携用のファイルを作成します。

app/plugins/firebase.js

import firebase from 'firebase/app'
import 'firebase/firestore'

// firebaseの初期化処理
if (!firebase.apps.length) {
  firebase.initializeApp({
    projectId: process.env.PROJECTID
  })
  // firebase.analytics()
}

export default firebase
export const db = firebase.firestore()

// コレクション'detectData'を参照するための設定
export const bindDetectDataRef = db.collection('detectData')

環境変数を設定します。
.envにFirebaseのプロジェクトIDを設定します。

app/.env

PROJECTID='{FirebaseのプロジェクトIDを設定}'

Storeの設定

Nuxtではデータの状態の管理を行うVuexストアが利用できます。
Vuexストアについてはこちらを参照ください。
VuexfireはVuexストアにデータを格納するので、Vuexストアでの参照データの管理設定を行います。
Vuexストアはapp/storeディレクトリ配下で設定します。

app/store/index.js

import { vuexfireMutations } from 'vuexfire'

export const strict = false

export const mutations = {
  ...vuexfireMutations
}

app/store/firestore/detectData.js

import { firestoreAction, firestoreOptions } from 'vuexfire'
import { bindDetectDataRef } from '@/plugins/firebase'

// always wait for bindings to be resolved
firestoreOptions.wait = true

export const state = () => ({
  list: []
})

export const getters = {
  list: (state) => state.list
}

export const actions = {
  bindList: firestoreAction(({ bindFirestoreRef }) => {
    return bindFirestoreRef('list', bindDetectDataRef)
  })
}

表示するページの設定

ページの設定はapp/pagesディレクトリ配下で設定します。
sensorListはORUYOコンソールのセンサー一覧画面から「SensorID」と「表示名」を取得し、リストにします。

app/pages/index.vue

<template>
  <v-container fluid>
    <v-data-iterator :items="detectDataTree" hide-default-footer>
      <template v-slot:default="props">
        <v-row>
          <v-col
            v-for="sensor in props.items"
            :key="sensor.id"
            cols="12"
            lg="6"
          >
            <v-card min-height="20vh">
              <v-card-title class="subheading font-weight-bold">
                {{ sensor.name }}({{ sensor.id }})
              </v-card-title>

              <v-divider></v-divider>

              <v-list dense>
                <v-list-item
                  v-for="beacon in sensor.children"
                  :key="beacon.beaconId"
                >
                  <v-list-item-content>
                    {{ beacon.beaconName }}
                  </v-list-item-content>
                  <v-list-item-content class="align-end">
                    検知日時:
                    {{ new Date(beacon.detectDate).toLocaleString() }}
                  </v-list-item-content>
                </v-list-item>
              </v-list>
            </v-card>
          </v-col>
        </v-row>
      </template>
    </v-data-iterator>
  </v-container>
</template>

<script>
export default {
  data: () => ({
    sensorList: [
      {
        sensorId: 'dev-0001',
        sensorName: 'センサー#1'
      },
      {
        sensorId: 'dev-0002',
        sensorName: 'センサー#2'
      }
    ]
  }),
  computed: {
    // FireStoreのデータを取得
    detectDataList() {
      return this.$store.getters['firestore/detectData/list']
    },

    // 画面表示用に加工
    detectDataTree() {
      const output = []

      // データをセンサーでグルーピング
      this.sensorList.map((sensorObj) => {
        // センサーIDが一致するものを取得
        const detectDataList = this.detectDataList.filter(
          (detectData) => detectData.sensorId === sensorObj.sensorId
        )

        // センサー毎に情報を格納
        output.push({
          id: sensorObj.sensorId,
          name: sensorObj.sensorName,
          children: detectDataList
        })
      })
      return output
    }
  },
  created() {
    // StoreにFirestoreのデータを読込
    this.$store.dispatch('firestore/detectData/bindList')
  }
}
</script>

アプリケーションの起動

作成したアプリケーションを起動します。
appフォルダ配下で以下を実行します。

yarn dev

起動後 http://localhost:3000 にアクセスすると画面が表示されます。

Realtime-view-10

これで画面を表示しっぱなしでもビーコンが検知される度に情報が更新され、ほぼリアルタイムに検知状況を表示できるようになりました。

最後に

今回はORUYOとFirebaseの連携を行いリアルタイムに検知状態を表示するアプリケーションを作成する方法をご紹介しました。
これを応用することで様々な用途に対応できるアプリケーションが作成可能です。
例として以下の様な活用事例がございます。

<備品管理システム>
センサー名に位置、管理備品にビーコンを付与することで管理備品が
どのセンサーの近くにあるかリアルタイムに管理

<在室管理システム>
誰がどの場所にいるかをリアルタイムに管理

ビーコンの活用方法についてのご相談はぜひこちらからお問合せください。

Yuki Mizoguchi

土井工芸株式会社ビジネスイノベーション事業部