スマホ2台をBLEビーコンにして、2台のPCで部屋ごとの在室判定ができるかやってみた

スマホ2台をBLEビーコンにして、2台のPCで部屋ごとの在室判定ができるかやってみた

工場や倉庫で「誰がどこにいるか」を把握するシステムの構築を想定し、スマホのBLEビーコンと複数の受信機、Fabricを組み合わせた在室判定の仕組みを手持ちの端末で検証してみました。RSSIの比較だけで部屋の判定が成り立つのか、判定結果を欠落なくクラウドに届けられるのかを確認します。
2026.10.14

概要

こんにちは、クラスメソッド製造ビジネステクノロジー部の田中聖也です。
工場や倉庫で「誰がどのエリアにいるか」を把握する仕組み(RFIDや専用のBLEビーコンを使った入退室管理)が欲しいというニーズがあると思います。
様々な方法で実現できるとは思いますが、今回は手持ちのスマホとPCだけで、判定ロジックと、判定結果がFabricに届くかまでを検証してみました。

なお、Fabricの立ち上げ手順(Eventstream、Eventhouseの作成とPythonからの送信)は別の記事にまとめています。
この記事ではそこは省略し、Eventhouseの presence_events テーブルがすでに用意できている前提で進めます。
https://dev.classmethod.jp/articles/fabric-eventstream-eventhouse-send-data/

検証の背景

なぜ検証したのか

製造現場や倉庫では、「誰が、いつ、どのエリアに入って、いつ出たか」を記録したいというニーズがあります。
これは監視というニーズではなくて、何かトラブルにあった場合に生産に余裕がある現場から忙しい現場に人を移動させたいというニーズです。
上記の場合、管理者は事務所から現場に行ってどの現場に何人いるかを把握してから指示を出す必要がありますが、結構めんどうなので事務所でどこに誰がいるか分かれば画面で確認してピッチなので現場担当者に連絡することができます。

上記を実現する手段としてはRFIDや専用のBLEビーコンなどがありますが、機器を揃えて現場に設置する前に、確かめておきたいことがいくつかありました。

  1. 電波の強さ(RSSI)の比較だけで、部屋の判定は成り立つのか
  2. 判定の結果を、欠落なく、かつ過剰にならずにクラウドへ届けられるのか

この2点は実機で試さないと感覚がつかめないと思っています。
そこで、専用機器を買う前に、手持ちのスマホとPCだけで同じ構造の仕組みを作って確かめることにしました。

確かめたかったこと

確認したいこと 確認の方法
複数の受信機のRSSIを比べて、人がいる部屋を判定できるか 部屋ごとに受信機を置き、スマホ2台を持って部屋を移動する
判定が変わったときだけ、証跡が1件ずつ届くか Eventhouseの presence_events に届いた行を確認する
2人が同時に動いても、それぞれ独立して記録されるか 2台のスマホを同時に移動させ、行が分かれているか確認する
在室判定の結果が、Eventhouseの列定義どおりに取り込まれるか 各列の値と型を確認する

今回の範囲

今回は、スマホから受信機、Eventstream、Eventhouseまでが範囲です。
Eventhouseに貯めたデータをKQLの関数やPower BIで可視化する部分は含みません。

構成

スマホ2台がiBeacon(AppleのBLEビーコン規格)として電波を発信し、部屋ごとに置いたPCがそれを受信します。判定は受信機A(PC1)で行い、結果だけをFabricに送ります。

スマホA / スマホB(iBeacon発信)
  └→ 受信機B(PC2・子供部屋)── RSSIを毎秒(MQTT / LAN)──┐
  └→ 受信機A(PC1・自分の部屋)←──────────────────────┘ 在室判定・未送信分の保持
       └→ Fabric Eventstream → Eventhouse(presence_events)

本来の構成であれば、各現場ごとに受信機を置いて社員証をいれるホルダーをBLE発信機とするような感じかと思いますが、今回はスマホをBLE発信機として使用してリビングと子供部屋に受信機となるPCを2台置いて検証しました。

役割 機材 備考
ビーコン1(P001) Android Major 1 / Minor 101
ビーコン2(P002) Android Major 1 / Minor 102
受信機A PC1(Windows) スキャン、在室判定、Fabricへの送信
受信機B PC2(Windows) スキャンしてRSSIを毎秒PC1へ転送、MQTTブローカーを動かす

ポイントは3つあります。

  1. 判定は受信機A(現場側の端末、いわゆるエッジ)で確定させて、Fabricには変化したときだけ送信。
  2. 生のRSSIはFabricに送信しない。行数が過大になりますし、今回の検証にも不要なのであるため。
  3. BLEのスキャンに使う bleak(PythonのBLEライブラリ)がWindowsではWinRT(WindowsのBluetoothを扱うAPI)で動く。

在室判定ロジック

判定は「一番強く受信した受信機の部屋が現在地」という単純な基準です。
ただ、RSSIは瞬間ごとにかなりブレるので、安定させる仕組みを3つ入れています。

仕組み 設定 役割
最強RSSI比較 受信機ごとに比較 一番強く受信した受信機の部屋を現在地にする
移動平均 直近5回 瞬時値のブレ(壁や床で電波が反射して複数経路で届くマルチパスなどが原因)をならす
ヒステリシス 5dB 今いる部屋を覆すには、相手が5dB以上強い必要がある。境界でのパタパタした切り替わりを防ぐ
ロスト判定 60秒 どの受信機でも60秒間受信できなければ、LOSTとして扱う

RSSI(受信電波強度)はdBm(デシベルミリワット)という単位で、0に近いほど強い電波です。

準備

スマホをビーコンにする

iBeaconは、UUID(システム全体で共通の識別子)、Major(グループを分ける番号)、Minor(個体を分ける番号)の3つの値を、BLEの電波の中に入れて発信する規格です。
今回は、UUIDを共通にして、Majorを1、Minorをスマホごとに101と102にしています。

Androidでは、nRF Connect(Nordic Semiconductor社のBLE検証アプリ)のAdvertiser(電波を発信する機能)で、iBeaconとして発信できました。
Manufacturer data(メーカー独自のデータ領域)にCompany IDの 0x004Cを指定し、iBeaconのバイト列を入れる形です。

受信機の準備

受信機は2台ともWindowsで、Python 3.11に bleak、paho-mqtt(MQTTのPythonクライアント)、azure-eventhub(EventstreamへのPython送信用ライブラリ)を入れています。

  • PC2でMQTTブローカー(メッセージの中継役)のmosquittoを動かし、PC1がそこに接続する形にしています。
  • PC1とPC2の時計は、NTP(時計を自動で合わせる仕組み)で同期します
  • 連続で動かすので、どちらもスリープを無効にします
  • 接続文字列は、PC1の .env にだけ置いて、PC2には置きません
  • 受信時刻は、受信機間の時計ずれを避けるため、PC1の時計で付けます

やってみた

PC1台での動作確認

いきなり2台で動かさず、まずはPC1だけで試しました。PC1の判定をいったん送らずに確認する --dry-run で動作を見てから、実際に送信しています。

# PC1(まずは送らずに判定だけ)
python -m ble_room_tracker.receiver_a --dry-run -v

動作を確認できたので、送信ありで起動しました。Eventhouseの presence_events を見ると、PC1だけの確認で届いた最初の3件が入っていました↓

1件目はFabricへの疎通確認で送ったテストイベント(P_TEST)で、2件目と3件目がスマホAの入室(ENTER)と、ビーコン停止後のLOSTです。ここまでで、判定した結果がそのまま届く流れはできました。

2台のPCで検証

次に、PC2を子供部屋に置いて、2台の構成で動かします。

# PC1(自分の部屋)
python -m ble_room_tracker.receiver_a -v
# PC2(子供部屋)
python -m ble_room_tracker.receiver_b -v

この状態で、2台のスマホを持って、自分の部屋と子供部屋を行き来したり、2人が同時に部屋を入れ替わったり、ビーコンのアプリを止めてLOSTにしたりしました。

検証結果

Eventhouseの確認

Eventhouseに届いたデータは、KQLクエリセットから確認し、CSVにエクスポートしました。手順は、presence_eventsテーブルのQuery with codeから「任意のレコードを100件表示」を選びます↓

KQLクエリセットが開くので、画面上部の「CSVにエクスポート」を押します↓

クエリの結果は41件のレコードで、export.csvとしてダウンロードされました↓

テーブルのプレビューでも、同じ行が確認できます。部屋が ROOM_A と ROOM_KIDS で入れ替わり、受信機も RX_A と RX_B に分かれているのが確認できます。

取り込まれたデータ

取り込まれた41件を、日ごとに集計しました。時刻はUTCで、日本時間は+9時間です。

日付(UTC) 内容 件数
2026-10-10 PC1のみ(ROOM_Aだけ)の動作確認。テストイベント(P_TEST)2件を含む 14件
2026-10-11 PC2台、スマホ2台での検証(02:08〜02:30) 27件

2026-10-11の27件を、人と部屋ごとに分けると次のとおりです。

person_id ROOM_Aへの入室 ROOM_KIDSへの入室 LOST 合計
P001 6 5 3 14
P002 5 6 2 13

2台の構成で、2人が同時に部屋を入れ替わったときの行は次のようになりました。02:16:18.245には、P001とP002が同じ時刻にROOM_KIDSへ入室していますが、event_idが別々の2行として入っています↓

event_time(UTC) person_id event_type room_id receiver_id sent_timeとの差
02:08:07.101 P002 ENTER ROOM_KIDS RX_B 1.022秒
02:08:13.140 P001 ENTER ROOM_A RX_A 0.026秒
02:08:16.177 P001 ENTER ROOM_KIDS RX_B 0.025秒
02:14:54.759 P001 LOST UNKNOWN RX_A 0.027秒
02:16:18.245 P001 ENTER ROOM_KIDS RX_B 0.010秒
02:16:18.245 P002 ENTER ROOM_KIDS RX_B 0.010秒
02:17:46.974 P001 ENTER ROOM_A RX_A 0.051秒
02:17:48.038 P002 ENTER ROOM_A RX_A 0.007秒

確認できたこと

CSVを集計して、想定どおりかを確認した結果です。

観点 結果
件数とevent_id 41件すべてのevent_idが一意で、重複なし
列と型 7列すべてが、列定義どおりの値で取り込まれている
部屋と受信機の対応 ROOM_AはRX_A、ROOM_KIDSはRX_Bの組み合わせのみ。LOSTはUNKNOWN
変化したときだけ送る設計 10/11の27件は、すべて前回と違う部屋への変化で、同じ部屋のENTERが連続した行はなし
LOSTの発生元 LOST 9件(P001とP002の合計)のreceiver_idは、すべて直前にいた部屋の受信機と一致
LOSTの間隔 ENTERからLOSTまでの間隔は最短60.3秒で、60秒のロスト判定と矛盾しない
2人の同時移動 同じ時刻の入室も、人ごとに別の行として記録されている
送信の遅延 sent_timeとevent_timeの差は0.002〜1.085秒、中央値は0.026秒

送信の遅延は、0.5秒を超えたのが41件中7件で、多くが1秒前後でした。
それ以外は数十ミリ秒です。
1秒前後のものは、起動直後や、テストイベントの送信のたびに接続を張り直したときの1件目に見えますが、原因までは追っていません。

同じ確認は、KQLでもできます。件数とevent_idの一意件数、送信の遅延は、次のクエリで見られます。

presence_events
| summarize rows = count(), unique_events = dcount(event_id)

presence_events
| extend delay_s = (sent_time - event_time) / 1s
| summarize min_s = min(delay_s), median_s = percentile(delay_s, 50), max_s = max(delay_s)

まとめ

スマホ2台のBLEビーコンと、2台のPCで部屋ごとの在室判定をして、変化したときだけFabricのEventhouseに送ってみました。41件のデータは、想定どおりの内容で届いていました!!
これが実際の構成で動作しそうか検証したいですね。


製造業のクラウド活用とデジタル化を支援します

クラスメソッドの専門家による包括的なクラウド導入とデジタル化支援で、製造業の業務効率を最大化しましょう。AWSの導入から運用、最適化まで、最新技術と豊富な知見であらゆる課題に対応します。生産ラインのデジタル化やデータ活用、IoTの導入事例もございます。ぜひ、弊社の実績をご覧ください。

製造業界での支援内容を見る

この記事をシェアする

関連記事