本文件定義 SolidFocus App 與健身設備之間的藍牙 (BLE) 通訊設計,基於 FTMS v1.0.1 規範與 SolidFocus 客製化需求。
涵蓋內容:
- 支援的設備種類 (Bike)
- GATT Services & Characteristics 定義
- BLE 連線狀態機
- 即時數據解析 (Indoor Bike Data, Rower Data)
- 控制指令發送 (Control Point)
- 錯誤處理與 Retry 策略
3.2.1 支援的裝置種類¶
第一版支援:
- SolidFocus Bike (健身車 - 電控阻力)
- 廣播名稱:
SF-Bike-XXXX - 型號:
SF-Bike-M1(範例) - FTMS Indoor Bike Data (0x2AD2)
- 廣播名稱:
3.2.2 廣播封包 (Advertising Packet) 定義¶
為確保 App 能精確識別 SolidFocus 設備,設備端必須在廣播封包中包含以下 AD Types:
| AD Type | 內容 (Value) | 規範要求 |
|---|---|---|
| 0x01 (Flags) | 0x06 (General Discoverable, BR/EDR Not Supported) | 強制 |
| 0x03 (Complete List of 16-bit Service UUIDs) | [0x1826, 0x180A, 0x180F] | 強制,必須包含 FTMS、Device Info、Battery |
| 0x09 (Complete Local Name) | SF-Bike-XXXX 或 SF-Rower-XXXX |
強制,XXXX 為設備唯一識別碼 (如 MAC 末四碼) |
| 0x16 (Service Data) | [0x26, 0x18, 0x01, XX, XX] | 強制,包含 FTMS Service UUID (0x1826) + Fitness Machine Type |
Fitness Machine Type (AD Type 0x16 的一部分):
- SF-Bike 必須設置: Bit 5 (Indoor Bike Supported) = 1
App 掃描策略:
- 為避免連接到其他品牌的 FTMS 設備,App 掃描時會優先過濾 Complete Local Name 中包含
SF-Bike-或SF-Rower-的設備 - 同時驗證 Service UUIDs 中包含 0x1826 (FTMS)
3.2.3 GATT Services & Characteristics 概述¶
必要服務:
1. Device Information Service (0x180A)¶
用於識別設備型號與製造商。
| Characteristic | UUID | 權限 | 說明 |
|---|---|---|---|
| Manufacturer Name String | 0x2A29 | Read | 固定值: SolidFocus |
| Model Number String | 0x2A24 | Read | 範例: SF-Bike-M1 |
2. Fitness Machine Service (0x1826)¶
核心運動數據與控制服務。
| Characteristic | UUID | 權限 | 說明 |
|---|---|---|---|
| Fitness Machine Feature | 0x2ACC | Read | 宣告設備支援的功能(詳見 3.2.5) |
| Indoor Bike Data | 0x2AD2 | Notify | 即時運動數據(僅 Bike) |
| Rower Data | 0x2AD1 | Notify | 即時運動數據(僅 Rower) |
| Fitness Machine Control Point | 0x2AD9 | Write + Indicate | 控制指令(開始/暫停/設定阻力等) |
| Fitness Machine Status | 0x2ADA | Notify | 設備狀態變更通知(使用者手動操作) |
3. Battery Service (0x180F) - Optional¶
| Characteristic | UUID | 權限 | 說明 |
|---|---|---|---|
| Battery Level | 0x2A19 | Read / Notify | 電池電量 (0-100%) |
3.2.4 BLE 連線狀態機 (State Machine)¶
┌────────────────────────────────────────────────────────┐
│ BLE Connection States │
└────────────────────────────────────────────────────────┘
[Disconnected]
│
│ 1. Start Scan (篩選 SF-Bike-XXXX / SF-Rower-XXXX)
▼
[Scanning] ──────────► Device Found
│ │
│ Timeout │ 2. Connect
▼ ▼
[Scan Failed] [Connecting]
│
│ Success
▼
[Connected]
│
│ 3. Discover Services (0x180A, 0x1826)
▼
[Discovering Services]
│
│ Services Discovered
▼
[Services Ready]
│
│ 4. Read Device Info (0x2A24, 0x2A29)
│ 5. Read Feature (0x2ACC)
▼
[Feature Read]
│
│ 6. Subscribe to Notify (0x2AD2, 0x2ADA)
▼
[Subscribed]
│
│ 7. Request Control (0x00)
▼
[Controlled]
│
│ 8. Ready for Workout
▼
[Ready / Idle State]
狀態說明:
| 狀態 | 說明 | App 行為 |
|---|---|---|
| Disconnected | 初始狀態,未連線 | 檢查是否有已儲存的設備 ID |
| Scanning | 掃描中 | 篩選 Complete Local Name 包含 SF-Bike- 或 SF-Rower- |
| Connecting | 嘗試連線至選定設備 | 顯示「連線中」提示 |
| Connected | BLE 連線建立 | 開始發現服務 |
| Discovering Services | 發現 GATT Services | 等待 0x180A, 0x1826 被發現 |
| Services Ready | Services 發現完成 | 讀取設備型號與製造商 |
| Feature Read | 讀取 Fitness Machine Feature | 根據 Feature 決定 UI 顯示項目 |
| Subscribed | 訂閱 Data / Status | 等待數據推播 |
| Controlled | 取得控制權 | 可以發送控制指令 |
| Ready | 準備開始運動 | 使用者可選擇課程並開始 |
重要: App 必須按照上述順序執行,否則可能導致控制指令失敗。
3.2.5 Fitness Machine Feature (0x2ACC) 詳細規格¶
此 Characteristic 是 App 識別設備功能的關鍵。設備必須正確設置以下 Bits,App 會根據這些 Bits 動態決定 UI 顯示項目。
Bike - Fitness Machine Features (Table 4.3)¶
| Bit | 定義 | 實作要求 | App 行為 |
|---|---|---|---|
| 1 | Cadence Supported (支援踏頻) | 強制 (1) | 顯示踏頻欄位 |
| 2 | Total Distance Supported (支援總距離) | 強制 (1) | 顯示距離欄位 |
| 7 | Resistance Level Supported (支援阻力等級讀取) | 強制 (1) | 顯示當前阻力欄位 |
| 9 | Expended Energy Supported (支援消耗能量) | 強制 (1) | 顯示卡路里欄位 |
| 10 | Heart Rate Measurement Supported (支援心率測量) | 強制 (1) | 顯示心率欄位 |
| 12 | Elapsed Time Supported (支援經過時間) | 強制 (1) | 顯示運動時間欄位 |
| 14 | Power Measurement Supported (支援功率測量) | 強制 (1) | 顯示功率欄位 |
Bike - Target Setting Features (Table 4.4)¶
| Bit | 定義 | 實作要求 | App 行為 |
|---|---|---|---|
| 2 | Resistance Target Setting Supported (支援阻力目標設定) | 強制 (1) | 顯示阻力控制滑桿 (可調整) |
| 3 | Power Target Setting Supported (支援功率目標設定) | 建議 (1) | 顯示 ERG 模式選項 |
| 8 | Targeted Distance Configuration Supported (支援目標距離設定) | 建議 (1) | 顯示目標距離設定 |
| 9 | Targeted Training Time Configuration Supported (支援目標時間設定) | 建議 (1) | 顯示目標時間設定 |
| 16 | Targeted Cadence Configuration Supported (支援目標踏頻設定) | 建議 (1) | 顯示目標踏頻設定 |
App 動態 UI 建構邏輯:
- App 連線後先讀取 0x2ACC
- 檢查 Bit 2 (Resistance Target Setting):
- 若為 1 → 顯示「阻力控制滑桿」(使用者可調整)
- 若為 0 → 僅顯示「當前阻力」(唯讀,如 Rower)
- 檢查 Bit 10 (Heart Rate Measurement):
- 若為 1 → 顯示「心率」欄位
- 若為 0 → 不顯示
3.2.6 Indoor Bike Data (0x2AD2) 數據封包結構¶
此 Characteristic 用於推播即時運動數據。封包結構為可變長度,根據 Flags (前 2 bytes) 決定包含哪些欄位。
數據封包結構 (Bike)¶
| 欄位 | 格式 | 對應 Flags Bit | Server 要求 | 解析說明 |
|---|---|---|---|---|
| Flags | UINT16 (Little Endian) | N/A | 強制 | 前 2 bytes,決定後續欄位 |
| Instantaneous Speed (即時速度) | UINT16 | Bit 0 = 0 | 強制 | 單位: 0.01 km/h (需 × 0.01) |
| Average Speed (平均速度) | UINT16 | Bit 1 = 1 | 建議 | 單位: 0.01 km/h |
| Instantaneous Cadence (即時踏頻) | UINT16 | Bit 2 = 1 | 強制 | 單位: 0.5 rpm (需 × 0.5) |
| Average Cadence (平均踏頻) | UINT16 | Bit 3 = 1 | 建議 | 單位: 0.5 rpm |
| Total Distance (總距離) | UINT24 | Bit 4 = 1 | 強制 | 單位: 公尺 (3 bytes) |
| Resistance Level (阻力等級) | UINT16 | Bit 5 = 1 | 強制 | 範圍: 0-100 |
| Instantaneous Power (即時功率) | SINT16 | Bit 6 = 1 | 強制 | 單位: 瓦特 (可為負值) |
| Average Power (平均功率) | SINT16 | Bit 7 = 1 | 建議 | 單位: 瓦特 |
| Total Energy (總消耗能量) | UINT16 | Bit 8 = 1 | 強制 | 單位: 千卡 (kcal) |
| Heart Rate (心率) | UINT8 | Bit 9 = 1 | 強制 | 單位: bpm |
| Elapsed Time (經過時間) | UINT16 | Bit 11 = 1 | 強制 | 單位: 秒 |
重要:
- 所有標記為「強制」的欄位必須在 Feature (0x2ACC) 中宣告支援
- App 必須嚴格依照 Flags 解析封包,不能假設欄位順序
範例封包 (16 進位):
[Flags: 0x0964] [Speed: 0x0320] [Cadence: 0x005A] [Distance: 0x0001F4]
[Resistance: 0x000F] [Power: 0x0078] [Calories: 0x0032] [Heart Rate: 0x78]
[Time: 0x012C]
解析:
- Flags = 0x0964 (Bit 2, 5, 6, 8, 9, 11 = 1)
- Speed = 800 × 0.01 = 8.00 km/h
- Cadence = 90 × 0.5 = 45 rpm
- Distance = 500 公尺
- Resistance = 15
- Power = 120 W
- Calories = 50 kcal
- Heart Rate = 120 bpm
- Time = 300 秒 (5 分鐘)
3.2.7 Fitness Machine Control Point (0x2AD9) 控制指令¶
此 Characteristic 用於 App 控制設備行為。App 必須先發送 Request Control (0x00) 取得控制權,才能發送其他指令。
Bike - Control Point Op Codes¶
| Op Code | 定義 | 參數格式 | Server 要求 | 使用時機 |
|---|---|---|---|---|
| 0x00 | Request Control (請求控制權) | 無參數 | 強制 | 連線後必須先執行 |
| 0x01 | Reset (重置) | 無參數 | 強制 | 重置所有累計數據 (距離、時間、卡路里) |
| 0x04 | Set Target Resistance Level (設定目標阻力) | SINT16 (Little Endian) | 強制 | 調整阻力 (範圍: 0-100) |
| 0x05 | Set Target Power (設定目標功率) | SINT16 (Little Endian) | 建議 | ERG 模式 (設備會自動調整阻力) |
| 0x07 | Start or Resume (開始或恢復) | 無參數 | 強制 | 開始運動或從暫停恢復 |
| 0x08 | Stop or Pause (停止或暫停) | UINT8 (0x01=Stop, 0x02=Pause) | 強制 | 暫停或結束運動 |
| 0x0C | Set Targeted Distance (設定目標距離) | UINT24 (Little Endian) | 建議 | 設定目標距離 (公尺) |
| 0x0D | Set Targeted Training Time (設定目標時間) | UINT16 (Little Endian) | 建議 | 設定目標時間 (秒) |
Control Point 寫入格式:
[Op Code: 1 byte] [Parameter: 0-N bytes]
範例 1: Request Control
→ App 寫入: [0x00]
← Server Indicate: [0x80, 0x00, 0x01] (Success)
範例 2: Set Resistance Level = 15
→ App 寫入: [0x04, 0x0F, 0x00] (Little Endian)
← Server Indicate: [0x80, 0x04, 0x01] (Success)
範例 3: Start
→ App 寫入: [0x07]
← Server Indicate: [0x80, 0x07, 0x01] (Success)
範例 4: Pause
→ App 寫入: [0x08, 0x02]
← Server Indicate: [0x80, 0x08, 0x01] (Success)
Response Code (Indicate): | Byte 0 | Byte 1 | Byte 2 | 說明 | |--------|--------|--------|------| | 0x80 | Request Op Code | 0x01 | Success | | 0x80 | Request Op Code | 0x02 | Op Code Not Supported | | 0x80 | Request Op Code | 0x03 | Invalid Parameter | | 0x80 | Request Op Code | 0x04 | Operation Failed | | 0x80 | Request Op Code | 0x05 | Control Not Permitted |
3.2.8 Fitness Machine Status (0x2ADA) 狀態通知¶
此 Characteristic 用於通知 App 非 App 觸發的事件(如使用者直接操作設備面板)。
Bike - Status Op Codes¶
| Op Code | 定義 | 觸發情境 | App 行為 |
|---|---|---|---|
| 0x01 | Reset (重置) | 使用者按下設備「重置」鈕 | 清空 UI 顯示的累計數據 |
| 0x02 | Stopped or Paused by User (使用者停止或暫停) | 使用者按下設備「暫停」或「停止」鈕 | 立即切換 UI 至暫停狀態 |
| 0x04 | Started or Resumed by User (使用者開始或恢復) | 使用者按下設備「開始」或「恢復」鈕 | 立即切換 UI 至運動狀態 |
| 0x07 | Target Resistance Level Changed (目標阻力等級變更) | 使用者手動調整設備阻力 | 立即更新 UI 上的阻力滑桿位置 |
| 0xFF | Control Permission Lost (控制權遺失) | 控制權被另一個 Client 搶走 | 顯示錯誤訊息,禁用控制項 |
重要:
- App 必須即時響應 Status 通知,確保 UI 與設備狀態同步
- 特別是 0x02 (暫停) 和 0x07 (阻力變更),必須立即更新 UI
範例:
使用者在設備上手動調整阻力 → 設備推播:
[0x07, 0x14, 0x00] (阻力調整為 20)
→ App 必須立即更新 UI 上的阻力滑桿位置為 20
3.2.9 More Data Bit 處理機制¶
當單筆數據封包超過 ATT_MTU 限制(通常為 23 bytes)時,設備會將數據拆分為多個 Notify 封包。
拆分機制¶
| 封包順序 | Flags Bit 0 (More Data) | 說明 |
|---|---|---|
| 第 1 包 | 1 | 還有後續封包 |
| 第 2 包 | 1 | 還有後續封包 |
| ... | 1 | 還有後續封包 |
| 最後一包 | 0 | 這是最後一包 |
Server 職責:
- 將數據拆分為多個封包
- 前面所有封包的 Flags Bit 0 = 1
- 最後一包的 Flags Bit 0 = 0
- 強制欄位(如 Speed, Cadence)必須在最後一包才發送
App 職責:
- 檢查 Flags Bit 0
- 若 Bit 0 = 1 → 緩存此封包,等待下一包
- 若 Bit 0 = 0 → 組合所有緩存封包,視為一筆完整數據
- 更新 UI
範例流程:
封包 1: [Flags: 0x0001] [Speed: 0x0320] [Cadence: 0x005A] ...
→ Bit 0 = 1 (More Data) → App 緩存
封包 2: [Flags: 0x0000] [Distance: 0x0001F4] [Resistance: 0x000F] ...
→ Bit 0 = 0 (Last) → App 組合封包 1 + 封包 2 → 更新 UI
重要:
- Elapsed Time (Bit 11) 建議在每個封包都包含,方便 App 重組數據
- App 必須實作緩存邏輯,否則會漏失數據
3.2.10 App 實作指南¶
掃描與識別流程¶
- 自動連線 (優先):
- App 啟動時,檢查是否有已儲存的設備 ID (Complete Local Name)
- 若有 → 嘗試直接連線
-
若連線失敗 → 進入手動搜尋流程
-
手動搜尋流程:
- App 開始掃描,篩選 Complete Local Name 包含
SF-Bike-或SF-Rower-的設備 - 顯示設備列表 (顯示 Complete Local Name)
- 使用者選擇設備 → 儲存設備 ID → 連線
連線後動態 UI 建構¶
- 讀取設備型號 (0x180A):
- Model Number String (0x2A24) → 判斷是 Bike 還是 Rower
-
Manufacturer Name String (0x2A29) → 驗證是否為 SolidFocus
-
讀取功能特徵 (0x2ACC):
- 檢查 Bit 2 (Resistance Target Setting):
- 若為 1 → 顯示阻力控制滑桿(Bike)
- 若為 0 → 顯示當前阻力(唯讀,Rower)
-
檢查 Bit 10 (Heart Rate Measurement):
- 若為 1 → 顯示心率欄位
- 若為 0 → 不顯示
-
訂閱數據:
- 訂閱 Indoor Bike Data (0x2AD2) - Notify
- 訂閱 Fitness Machine Status (0x2ADA) - Notify
-
訂閱 Control Point (0x2AD9) - Indicate
-
取得控制權:
- 向 Control Point (0x2AD9) 寫入 [0x00] (Request Control)
- 等待 Indicate 回應 [0x80, 0x00, 0x01] (Success)
運動流程控制¶
開始運動:
- 使用者選擇課程 → 進入運動畫面
- App 發送 Reset (0x01) → 重置所有累計數據
- App 發送 Set Target Resistance Level (0x04) → 設定初始阻力
- App 發送 Start (0x07) → 開始運動
- 設備開始推播 Indoor Bike Data (0x2AD2)
阻力調整 (依課程時間軸): - App 根據課程設定,定時發送 Set Target Resistance Level (0x04)
暫停 / 繼續:
- 暫停: App 發送 Stop or Pause (0x08, 0x02)
- 繼續: App 發送 Start or Resume (0x07)
結束運動:
- App 發送 Stop or Pause (0x08, 0x01)
- 顯示訓練完成畫面(顯示累計數據)
處理使用者手動操作¶
當使用者直接操作設備面板時,App 會收到 Fitness Machine Status (0x2ADA) 通知:
| 收到通知 | App 行為 |
|---|---|
| 0x02 (使用者暫停) | 立即切換 UI 至暫停狀態 |
| 0x04 (使用者開始) | 立即切換 UI 至運動狀態 |
| 0x07 (使用者調整阻力) | 立即更新 UI 上的阻力滑桿位置 |
3.2.11 Custom Service (設備訓練名稱)¶
⚠️ 待定義 - 需與 SolidFocus 確認 UUID 與格式
用途:
- App 將課程名稱傳送給設備,設備可在螢幕上顯示
預期規格:
- Service UUID: (待定義)
- Characteristic UUID: (待定義)
- 權限: Write
- 格式: UTF-8 字串 (最大長度待定義)
範例:
App 寫入: "20分鐘燃脂訓練" (UTF-8 編碼)
→ 設備螢幕顯示: "20分鐘燃脂訓練"
3.2.12 錯誤處理與 Retry 策略¶
BLE 常見錯誤¶
| 錯誤類型 | Retry 策略 | 使用者提示 |
|---|---|---|
| 連線失敗 | Retry 3 次,間隔 2 秒 | "連線失敗,請確認設備已開機" |
| 服務發現失敗 | 重新連線 (最多 3 次) | "設備連線異常,請稍後再試" |
| 訂閱失敗 | 重新訂閱 (最多 3 次) | "設備通訊異常" |
| Control Point 指令失敗 | 依據 Response Code 決定 | 根據錯誤碼顯示對應訊息 |
| 設備意外斷線 (運動中) | 自動重連 (最多 3 次) | "設備連線中斷,嘗試重新連線" + 本機儲存資料 |
| Control Permission Lost | 不 retry | "設備被其他裝置控制" |
Response Code 錯誤處理¶
| Response Code | 說明 | App 行為 |
|---|---|---|
| 0x01 | Success | 繼續執行 |
| 0x02 | Op Code Not Supported | 提示: "此設備不支援此功能" |
| 0x03 | Invalid Parameter | 提示: "參數錯誤" (檢查阻力範圍) |
| 0x04 | Operation Failed | 提示: "操作失敗,請稍後再試" |
| 0x05 | Control Not Permitted | 提示: "請先取得控制權" (重新發送 0x00) |
重要原則¶
- ✅ 所有 BLE 錯誤都不應導致 App crash
- ✅ 提供清楚的錯誤訊息給使用者
- ✅ 運動中的資料必須先儲存本機 (避免斷線導致數據遺失)
- ✅ 斷線重連成功後,繼續使用本機儲存的數據
- ✅ 不應無限 retry,避免耗盡電量
🔗 相關章節¶
- 03.1 App 架構 - React Native 技術堆疊與架構
- 03.3 資料流與同步策略 ⭐ - 資料同步核心邏輯