Bluetoothで家の中の携帯を見つけてみた

## はじめに
皆さんにこんな経験はありませんか？？

**家の中なのに携帯がない！！**

自分はよく家の中で携帯を無くします。大抵30秒くらい探せば見つかるのですが、時には全く見つからない時もあり、切羽詰まっている時だと心の具合が悪いです。

部屋を整理整頓する！置き場所を決める！という古典的かつ最も有効な手法は一旦無視して、携帯を無くした時用の携帯捜索システムを作ってみました。

:::note warn 
「iPhoneを探す」については、筆者の自宅の環境では中々使用できないので番外編で記載します。
:::

## 携帯捜索システムについて
今回は携帯のBluetoothの信号強度から位置(距離)を推定、携帯を探してみます。
イメージ図にすると以下です。

![ 2024-01-31 1.46.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487820/f67398d3-b5bf-0c93-87b8-30f0ab22ca7c.png)


次に上記を達成するための具体的なステップは以下です。

- 1.携帯の識別(Bluetoothアドレスの取得)
- 2.携帯のBluetoothの信号強度取得
- 3.信号強度から携帯の位置の予測
- 4.実用化

![ 2024-01-31 1.42.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487820/7a41619c-f6f6-2a6c-2355-047d3914de14.png)


順々に解説していきます。

### 1.携帯の識別(Bluetoothアドレスの取得)

まずは自身の携帯のBluetoothのアドレスを調べます。そのために`bleak`というライブラリを使います。

https://bleak.readthedocs.io/en/latest/installation.html

`bleak`は以下のようにインストールできます。

```
(仮想環境)% pip install bleak
```

プログラムから携帯のBluetoothアドレスを探す前に、ターミナルからBluetoothにアクセスできるようにPCの設定を変更します。
筆者はMacなので「プライバシーとセキュリティ」->「Bluetooth」からターミナル(ex. iTerm2)に権限を付与します。

![ 2024-01-21 22.57.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487820/ad2de086-26e2-2ee1-9d67-a5249dfb0a93.png)

次に`bleak`を用いてBluetoothアドレスを特定していきます。

自分はiPhoneユーザーのため、Bluetoothデバイス名がiPhoneであるデバイスのBluetoothアドレスを取得します。

```python
import asyncio
from bleak import BleakScanner

async def scan_for_devices():
    devices = await BleakScanner.discover()
    for device in devices:
        if device.name == "iPhone":
            print(f"Device: {device.name}, Address: {device.address}"  )

loop = asyncio.get_event_loop()
loop.run_until_complete(scan_for_devices())
```
プログラムの結果は以下です。

```bash 
Device: iPhone, Address: XXXXXXXXXXXXXXXXXXXXX
```

上記で得られたBluetoothアドレスは環境変数で以下のように管理します。

```bash
% IPHONE_BLUETOOTH_ADDRESS="XXXX" // Bluetoothアドレス
```

### 2.携帯のBluetoothの信号強度取得

1.で得たBluetoothアドレスから携帯の位置推定で用いる信号強度を測ります。
プログラムは以下です。

```python 
import asyncio
from bleak import BleakScanner
import os

target_address = os.environ['IPHONE_BLUETOOTH_ADDRESS'] 

async def scan_for_specific_device(target_address):
    device = await BleakScanner.find_device_by_address(target_address, timeout=10.0)
    if device:
        print(f"RSSI: {device.rssi}dBm")
    else:
        print(f"No device found.")

asyncio.run(scan_for_specific_device(target_address))
```

:::note 
Bluetoothの信号取得までにリードタイムがあるので、asyncを用いて非同期処理として扱う必要があります。
:::


プログラムの結果は以下のように得られます。

```bash
Found target device RSSI: -63dBm
```

:::note warn
プログラムで得られる信号強度にはバラツキがあるので注意が必要です。
:::

次に信号強度と距離の関係について調べます。


### 3.信号強度から携帯の位置の予測
2.で信号強度がわかったので、本項では信号強度から携帯の位置の予測していきます。
厳密に実行するならば、測定条件で遮蔽物の有無や高低差などを考慮する必要がありますが、今回はPCと携帯の距離と信号強度のみを考慮します。


:::note warn 
測定環境や測定機器によって以降の結果には差があるので、記事の読者の環境で同じ結果が得られるとは限りません。
:::

以下の集計用のプログラムを用いて、任意の距離での信号強度の平均と分散のデータを集めます。(人力)

```python 
import asyncio
from bleak import BleakScanner
import os
import warnings
import numpy as np 

warnings.filterwarnings("ignore")

target_address = os.environ['IPHONE_BLUETOOTH_ADDRESS'] 
dB_list = []

async def scan_for_specific_device(target_address):
    device = await BleakScanner.find_device_by_address(target_address, timeout=10.0)
    if device:
        print(f"RSSI: {device.rssi}dBm")
        dB_list.append(device.rssi)
        print(len(dB_list),np.mean(dB_list),np.std(dB_list))

while True:
    asyncio.run(scan_for_specific_device(target_address))
```

集めた結果を図にするとこんな感じです。(全て30回計測)

![fig.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487820/f5556b73-1e7c-33cf-57f4-971fb200c381.png)


バラツキはありますが、携帯との距離が大きいほど、信号強度が小さいことがわかります。（つまり反比例の関係となります。）
この結果から信号強度を3つにグループ分けて、次の実用化に入ります。


### 4.実用化
ここでは3で作ったプログラムを実生活でも使えるように、UIを少しだけ作ります。

とは言っても、API化してフロントエンドまで作るとサーバーを立ち上げるのに少し時間がかかってしまい、肝心の携帯探しが遅れてしまいます。

今回はターミナルの出力を見やすく変えていきます。

完成系のプログラムは以下です。

```main.py
import asyncio
from bleak import BleakScanner
import os
import warnings

warnings.filterwarnings("ignore")

target_address = os.environ['IPHONE_BLUETOOTH_ADDRESS'] 

async def scan_for_specific_device(target_address):
    device = await BleakScanner.find_device_by_address(target_address, timeout=10.0)
    if device:
        rssi = device.rssi
        if rssi < -70:
            print(f"\033[91m携帯は部屋の外にある可能性が高いです。(RSSI: {rssi}dBm)\033[0m")  # 赤色
        elif rssi < -55:
            print(f"\033[93m携帯は部屋の中にある可能性が高いです。(RSSI: {rssi}dBm)\033[0m")  # 黄色
        else:
            print(f"\033[92m携帯は手の届く範囲にある可能性が高いです。(RSSI: {rssi}dBm)\033[0m")  # 緑色
    else:
        print("\033[94m携帯が見つかりませんでした\033[0m")  # 青色

while True:
    asyncio.run(scan_for_specific_device(target_address))
```

## 実験
では上記のシステムが有効かを実験で確かめます。

実際のユースケースの一つとして、携帯にだんだん近づいたときの実験をします。

具体的に、携帯を遠くに置いて、だんだん近づいた時の実行結果を確認します。


![gamen_test1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487820/c04180c9-fdeb-4bac-14a6-9c2e4225ece0.gif)


測定誤差の影響で、携帯との距離と信号強度が想定される反比例の関係にはなっていませんが、携帯が近くにある時は、緑色の出力(携帯は手の届く範囲にある可能性が高いです。)が出力されました:tada:



## おわりに
今回のシステムをまとめると以下です。

![ 2024-01-31 1.47.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487820/9dd1c345-c76b-c570-ad18-146ee9c29244.png)



Bluetoothをプログラミングを使ったことはなく、楽しい経験となりました。
携帯を探すのに一定役に立ちそうで嬉しいです！！

また末尾に番外編として、iPhoneユーザー限定の既存のソリューションを2つ記載しておきます。

## 番外編1:(iPhoneユーザー限定)iPhoneを探す
「iPhoneを探す」機能は、携帯がiPhoneであり、Mac等のAppleデバイスを持っている場合に使えます。

https://support.apple.com/ja-jp/guide/findmy-mac/fmmc6c7ef383/mac

### 位置情報を用いた捜索
試しにGPS情報で家の中の携帯を探すことを試みましたが、以下写真のように最大まで拡大しても家の中のどこに携帯があるのかはわかりません。（そりゃそう。）

![ 2024-01-21 11.00.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487820/7deaaa67-297c-69e1-9451-41f26a6ad316.png)

### サウンド機能を用いた捜索
次に「iPhoneを探す」のサウンド機能を使ってみます。

![ 2024-01-21 11.29.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/487820/ea107eb5-d8e4-fdb0-d986-503ca769d0cd.png)

「iPhoneを探す」のサウンドでは、iPhoneから大音量でアラートが鳴り、携帯の捜索には適しています。ですが私の同居人がこのアラートに強い嫌悪感を示しているため、有事を除いて使うのを禁止されております。よって上記のような携帯捜索アプリを作りました。

また「iPhoneを探す」のサウンドは、以下のように他アプリの競合などで鳴らないケースも過去にあるようです。

https://discussionsjapan.apple.com/thread/250563874?sortBy=best

## 番外編2:(iPhoneユーザー限定)Siriを使う

「Hey Siri, where are you?」と大声で叫び、Siriが反応してくれれば見つかりますが、筆者はSiriをオフにしているので、これもベストなソリューションではありません。（それと大声で叫ぶのは近所迷惑なので、やめた方が良いかもしれません。）