BLE個体識別の技術調査 + エピソード記憶の容量設計
BLE個体識別: 選択肢と制約
案1: iPhoneをiBeacon送信機にする
- CoreLocationでiBeacon広告を出せる(Apple公式API)
- 致命的制約: バックグラウンドでは広告フォーマットが壊れる
- iOSはバックグラウンドに入ると独自フォーマットに切り替える
- UUID/Major/Minorが正しく読めなくなる
- アプリをフォアグラウンドに保つ必要がある → 非現実的
- 判定: 不採用
案2: 専用BLEビーコン(小型デバイス)
- キーホルダー型iBeaconビーコン(1000-2000円/個)
- 電池で数ヶ月〜1年持つ
- 常に一定のUUID/Major/Minorを広告し続ける
- Pi側: bleak (Python) でスキャン → iBeacon広告をパース → 個体識別
- 利点: 確実。バックグラウンド問題なし。Pi内蔵BTで受信可能
- 欠点: 追加ハードウェアの購入が必要。身につけ忘れると検知できない
- 判定: 最も現実的。まず2個購入して実験
案3: iPhoneのBLE広告をIRKで追跡
- iPhoneは常にBLE広告を出している(ランダムMACアドレス)
- IRK(Identity Resolving Key)を使えばランダムMACから端末を特定可能
- ESPresense(ESP32用)がこの手法を実装済み
- 問題: IRKの取得にはペアリングが必要。Piでの実装は非自明
- bleakにはIRK解決の組み込みサポートがない
- bluezのD-Bus APIを叩く必要がある
- ESP32 + ESPresenseに任せてPiはMQTTで結果を受ける手もある
- 判定: 技術的に面白いが複雑。Phase Cの後半で検討
案4: Wi-Fi接続ベースの在室検知
- 同じLANにいるデバイスのMACアドレスで判定
arp -aやDHCPリースで確認- 問題: iPhoneはMACランダム化する(iOS 14以降)。同じSSIDでも接続ごとに変わりうる
- 判定: 信頼性低い。補助手段にとどめる
推奨構成
Phase C初期: 専用BLEビーコン × 2(ねおの用、むしはかせ用)
↓
Pi内蔵BT + bleak でスキャン(1-3秒間隔)
↓
iBeacon UUID/Major/Minor → 個体ID マッピング
↓
PerceptionモジュールにBLE検知結果を供給
bleak実装メモ
Koen Vervloesemのibeaconスキャン例が参考になる:
from bleak import BleakScanner
from construct import Array, Byte, Const, Int8sl, Int16ub, Struct
ibeacon_format = Struct(
"type_length" / Const(b"\x02\x15"),
"uuid" / Array(16, Byte),
"major" / Int16ub,
"minor" / Int16ub,
"power" / Int8sl,
)
def detection_callback(device, advertisement_data):
apple_data = advertisement_data.manufacturer_data.get(0x004C) # Apple company ID
if apple_data:
try:
ibeacon = ibeacon_format.parse(apple_data)
# ibeacon.major, ibeacon.minor で個体識別
except:
pass
scanner = BleakScanner(detection_callback=detection_callback)
依存: bleak, construct
Pi 5内蔵BT: bluez経由で動くはず(要検証)
エピソード記憶の容量設計
Episode構造体のサイズ見積もり
018で定義したEpisode:
Episode:
timestamp: float # 8 bytes
duration: float # 8 bytes
trigger: str # ~32 bytes (短い文字列)
sensor_snapshots: [] # 可変。1秒1サンプル × 30秒 × 3センサー = 720 bytes
position_trace: Vec2[] # 可変。1秒1サンプル × 30秒 × 2 = 480 bytes
novelty_peak: float # 8 bytes
who_present: str[] # ~64 bytes (ID × 数人)
valence: float # 8 bytes
strength: float # 8 bytes
recall_count: int # 4 bytes
context_vector: float[] # 想起用。次元数次第。32次元 = 256 bytes
1エピソード ≈ 1.5 KB(sensor_snapshotsとposition_traceの長さ次第)
Pi 5のRAM制約
- Pi 5: 4GB or 8GB RAM
- ぼくらのPi: 8GB
- OS + Python + 他プロセス ≈ 1-2 GB使用
- ローバープロセスに割り当て可能: 最低2GB
- メモリはボトルネックにならない
容量の上限は「意味」で決める
- 技術制約ではなく、想起の質で決める
- 1000エピソード × 1.5KB = 1.5MB — メモリ的には余裕
- しかし1000個から類似検索するコストは?
- cosine similarity × 1000 = 1000回の内積計算
- 32次元ベクトル × 1000 = 1ステップあたり ~0.1ms — 無視できる
- 上限500エピソードを提案。十分な経験蓄積 + 検索コスト無視可能
記憶の圧縮・忘却
- strength < 0.01 のエピソードを削除(自然忘却)
- 上限到達時: 最もstrengthが低いエピソードを削除
- 圧縮は不要。削除で十分。忘れることも個性
永続化
- JSONファイルに定期保存(30秒ごと or エピソード追加時)
- SDカードに書くので書き込み頻度に注意(寿命)
/tmp/にRAMディスクで置いて、5分ごとにSDに同期する案もある
context_vectorの生成方法
想起で使うcontext_vectorをどう作るか。LLM embeddingは重すぎる。
案: 手作り特徴ベクトル
def make_context_vector(episode):
return [
episode.novelty_peak, # 驚きの大きさ
episode.valence, # 正負
episode.duration, # 長さ
len(episode.who_present), # 人数
hour_of_day(episode.timestamp), # 時間帯(正規化)
mean(episode.sensor_snapshots), # 平均距離
std(episode.sensor_snapshots), # 距離の変動
# ... 必要に応じて追加
]
8-16次元で十分。これならcosine similarityも軽い。 LLMの意味理解はないが、ローバーの「意味」はセンサー値と感情値の組み合わせで十分表現できる。
開いた問い
- BLEビーコンの具体的製品 — iBeacon対応で安価なもの。秋月?Amazon?
- RSSI(電波強度)の活用 — ビーコンのRSSIで大まかな距離推定ができる。「近くにいる」vs「部屋の隅にいる」
- context_vectorの次元数 — 少なすぎると想起が粗く、多すぎると過学習。8-16次元で始めて調整
- 反芻時のcontext — 人がいないときの想起で何をcontextにするか。時間帯?直前のセンサー値?ランダム?