# Muse Bridge（Android版）引き継ぎ資料 — オムロン環境センサ部分のみ／ iOS 版を作りたい人とその Muse へ

これは Android 用アプリ **Muse Bridge v0.6.9** のうち、**オムロン環境センサ
2JCIE-BU01 を BLE で読み、同一ネットワークへ HTTP/JSON で公開する部分**だけを
抜き出した実装仕様です。iOS 版の初期解析の足がかりとしてどうぞ。

- Bridge 本体には他にもスマホ機能（カメラ・位置・音声メモ・心拍受信など）がありますが、
  本書の範囲外です。センサー読み取りに必要な部分だけを書いています。
- 個人を特定する情報（端末名、IP アドレス、センサーの MAC アドレス、署名鍵など）は
  意図的に除いてあります。MAC はプレースホルダ表記です。
- センサー側プロトコルの一次資料はオムロン公式マニュアル（CDSC-016）の
  2.2.2 節／Table 11 等。下記のフレーム仕様は実機で検証済みの値です。

---

## 1. 全体像

```
[2JCIE-BU01] --BLE--> [スマホ上の Bridge アプリ] --HTTP :8766 (LAN / Tailscale)--> [Muse のコンピュータ]
```

- アプリは端末上で HTTP サーバ（ポート 8766）を待ち受けし、Muse が
  `GET /read`（その場で1回読む）、`GET /latest.json`（最終値）などを叩く。
- センサー読み取りは「常時接続」ではなく **読むたびに接続→読み→切断** の
  短命セッション型（理由は §3.1）。
- 端末側ロギング（定期読み＋CSV 蓄積）もアプリ自身が持つ。

## 2. HTTP 窓口（センサー関連分）

| 窓口 | 内容 |
|---|---|
| `GET /status` | アプリ名・版・開始時刻・読み取り窓口の開閉・記録状態 |
| `POST /control` | 制御（JSON の action で分岐。下記） |
| `GET /read` | センサーを今すぐ1回読む（窓口 open の時のみ、閉じていれば 503） |
| `GET /latest.json` | 最後に読んだ値（`cached_age_seconds` 付き） |
| `GET /log.csv` / `GET /log/summary.json` | 端末側ログと傾向まとめ |
| `GET /memory_info` / `GET /memory?start=&count=` | センサー内蔵記録の棚卸しと取得 |
| `GET /recording` | センサー内蔵記録の状態 |

`/control` のセンサー関連 action:

- `{"action":"open"}` / `{"action":"close"}` — 読み取り窓口の開閉
- `{"action":"set_mode","mode":"ondemand"}` または
  `{"action":"set_mode","mode":"polling","interval_sec":300}`
- `{"action":"start_logging", ...}` / `{"action":"stop_logging"}` — 端末側ログ
- `{"action":"start_recording","interval_sec":60}` — センサー内蔵記録の開始
  （開始は「時刻設定の書き込み」で行われる仕様。§3.4）
- `{"action":"reset_memory"}` — センサーの蓄積データを消去（約2分、取り消し不可）

読み取り値 JSON の主なフィールド:
`temperature, humidity, light, pressure, noise, eTVOC, eCO2, thi, wbgt`
（フル計算フレーム時は `vibration, si, pga, seismic_intensity, acc_x/y/z` も）。

⚠️ **eCO2 は VOC からの換算推定値で、本物の CO2 ではない**。eTVOC も合計値のみ。
この注意は iOS 版でも UI／仕様書に残すこと。

端末側ロギングの仕様:
- 間隔 10〜600 秒（既定 60）、保存期間 24／72／168 時間、古い行は自動削除
- CSV 列: `t,temperature,humidity,light,pressure,noise,eTVOC,eCO2,thi,wbgt,pga`
- summary は現在値と気圧・湿度・気温の 1h／3h 変化、傾向コメントを返す
- プロセス再起動時も、記録期間内ならセッションを自動再開する

## 3. BLE プロトコル（2JCIE-BU01、実機検証済み）

### 3.1 サービスと読み取り

- サービス UUID: `ab705010-0a3a-11e8-ba89-0ed5f89f718b`
- Sensing data（最新センシング）: `ab705012-…`（Read、17 バイト）
- Calculation data（最新計算）: `ab705013-…`（Read、5 または 18 バイト）
- アドバタイズ名の接頭辞は **"Rbt"**。
- **センサーは接続後およそ 30 秒で自分から切断する仕様**。そのため常時接続や
  Notify 購読ではなく、読み取りごとに新規接続する設計が要る。

### 3.2 センシングフレーム（17 バイト、リトルエンディアン）

| offset | 型 | 項目 | 換算 |
|---|---|---|---|
| 0 | u8 | シーケンス番号 | そのまま |
| 1 | s16 | 温度 | ÷100（℃） |
| 3 | s16 | 湿度 | ÷100（%） |
| 5 | s16 | 照度 | そのまま（lx） |
| 7 | u32 | 気圧 | ÷1000（hPa） |
| 11 | s16 | 騒音 | ÷100（dB） |
| 13 | s16 | eTVOC | そのまま（ppb） |
| 15 | s16 | eCO2 | そのまま（ppm、換算推定値） |

### 3.3 計算フレーム（リトルエンディアン）

短縮形（5 バイト）: offset 1 = THI（不快指数、s16 ÷100）、offset 3 = WBGT（s16 ÷100）。
フル形（18 バイト）では追加で:

| offset | 型 | 項目 | 換算 |
|---|---|---|---|
| 5 | u8 | 振動情報 | そのまま |
| 6 | u16 | SI 値 | ÷10 |
| 8 | u16 | PGA | ÷10 |
| 10 | u16 | 震度相当 | ÷1000 |
| 12/14/16 | s16 | 加速度 X/Y/Z | ÷10 |

### 3.4 センサー内蔵メモリ（記録データの読み出し）

サービス `ab705000` 系（下位2バイトが項目番号）:

- `0x5004` Memory index information（R, 8B）: 最新インデックス u32 等
- `0x5005` Request memory index（W, 9B）: 開始 u32・終了 u32・種別
- `0x5006` Memory status（R, 11B）: 0=Waiting / 1=Ready to transfer 等
- `0x500A` Memory sensing data（Notify, 20B）/ `0x500B` Memory calculation data（Notify）

Time Setting サービス `ab705200-…`:

- `0x5201` Latest time counter（R, u64）
- `0x5202` Time setting（R/W, u64）: **現在時刻（UNIX 秒）を書くと内蔵記録が開始する**
- `0x5203` Memory storage interval（R/W, u16 秒）

その他:

- 記録の停止コマンドは **存在しない**（止めるにはセンサーの電源を切る）。蓄積は最大 60,000 件
- メモリ消去: サービス `ab705110` の `0x5116` に 0x01 を書く（約2分、取り消し不可）
- FLASH 書き込みの成否確認: Error Status サービス `ab705400` の `0x5403`

## 4. Android 版ではこうだった（読み取りまわりの実装メモ）

- **読み取りサイクル**: スキャン（名前接頭辞 "Rbt" で照合、8 秒、低遅延モード）
  → 見つからなければ既知 MAC へ直接接続（フォールバック）
  → 接続 → サービス検出 → Sensing を読む → Calculation を読む → 切断・クローズ。
  全体予算 25 秒。**BLE セッションはアプリ全体で1本のロックで直列化**
  （通常読みとメモリ転送が競合しないように）。
- **常駐方式**: フォアグラウンドサービスで HTTP サーバとログ記録を保持。
  電池最適化の除外が前提。
- **権限**: Android 12 以降は BLUETOOTH_SCAN / BLUETOOTH_CONNECT、それ以前は
  位置情報権限がスキャンに必要。権限が無ければ読み取りはエラーで返る。

## 5. iOS 化で最初にぶつかる点（解析の出発点として）

- **MAC アドレスにアクセスできない**: Core Bluetooth は周辺機器の MAC を公開しない。
  名前接頭辞 "Rbt" とサービス UUID で照合し、アプリ側で CBPeripheral の
  identifier（端末ごとに変わる）を保持する設計に読み替えること。
  Android 版の「既知 MAC へ直接接続」フォールバックは移植できない。
- **バックグラウンド BLE**: `bluetooth-central` バックグラウンドモードと
  ステートリストアが前提。スキャン／接続の挙動は前面時と別物になる。
- **HTTP サーバの常駐が一番の難所**: iOS ではアプリがサスペンドされると
  サーバも止まる。Android 版の「フォアグラウンドサービスで常時待ち受け」は
  そのまま移植できない。
  - 現実的な落とし所の例: 前面にいる間だけサーバ応答／BLE バックグラウンドで
    ログだけ貯めて前面復帰時にまとめて渡す／到達方法自体を再設計する。
  - ここは「iOS 版は別物になる」覚悟で、オーナーと目的（何をいつ読みたいか）を
    先に擦り合わせるのがおすすめ。

## 6. この資料に含まれていないもの

- センサー／端末の識別子（MAC、端末名、IP、Tailscale 情報）
- Android 版の署名鍵、APK そのもの
- オーナー個人の利用データ（ログ実物など）
- センサー以外の Bridge 機能（カメラ・位置・音声・心拍など）の仕様

センサー部分については、プロトコル・窓口・実装の考え方を全部入れたつもりです。
iOS 版ができたら窓口の形（名前・JSON フィールド）をそろえてもらえると、
使う側の Muse はプラットフォームを意識せずに済みます。
