◀ 上一章:App 架構 | 下一章:資料流與同步策略 ▶


本文件定義 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-XXXXSF-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 建構邏輯:

  1. App 連線後先讀取 0x2ACC
  2. 檢查 Bit 2 (Resistance Target Setting):
  3. 若為 1 → 顯示「阻力控制滑桿」(使用者可調整)
  4. 若為 0 → 僅顯示「當前阻力」(唯讀,如 Rower)
  5. 檢查 Bit 10 (Heart Rate Measurement):
  6. 若為 1 → 顯示「心率」欄位
  7. 若為 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 職責:

  1. 將數據拆分為多個封包
  2. 前面所有封包的 Flags Bit 0 = 1
  3. 最後一包的 Flags Bit 0 = 0
  4. 強制欄位(如 Speed, Cadence)必須在最後一包才發送

App 職責:

  1. 檢查 Flags Bit 0
  2. 若 Bit 0 = 1 → 緩存此封包,等待下一包
  3. 若 Bit 0 = 0 → 組合所有緩存封包,視為一筆完整數據
  4. 更新 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 實作指南

掃描與識別流程

  1. 自動連線 (優先):
  2. App 啟動時,檢查是否有已儲存的設備 ID (Complete Local Name)
  3. 若有 → 嘗試直接連線
  4. 若連線失敗 → 進入手動搜尋流程

  5. 手動搜尋流程:

  6. App 開始掃描,篩選 Complete Local Name 包含 SF-Bike-SF-Rower- 的設備
  7. 顯示設備列表 (顯示 Complete Local Name)
  8. 使用者選擇設備 → 儲存設備 ID → 連線

連線後動態 UI 建構

  1. 讀取設備型號 (0x180A):
  2. Model Number String (0x2A24) → 判斷是 Bike 還是 Rower
  3. Manufacturer Name String (0x2A29) → 驗證是否為 SolidFocus

  4. 讀取功能特徵 (0x2ACC):

  5. 檢查 Bit 2 (Resistance Target Setting):
    • 若為 1 → 顯示阻力控制滑桿(Bike)
    • 若為 0 → 顯示當前阻力(唯讀,Rower)
  6. 檢查 Bit 10 (Heart Rate Measurement):

    • 若為 1 → 顯示心率欄位
    • 若為 0 → 不顯示
  7. 訂閱數據:

  8. 訂閱 Indoor Bike Data (0x2AD2) - Notify
  9. 訂閱 Fitness Machine Status (0x2ADA) - Notify
  10. 訂閱 Control Point (0x2AD9) - Indicate

  11. 取得控制權:

  12. 向 Control Point (0x2AD9) 寫入 [0x00] (Request Control)
  13. 等待 Indicate 回應 [0x80, 0x00, 0x01] (Success)

運動流程控制

開始運動:

  1. 使用者選擇課程 → 進入運動畫面
  2. App 發送 Reset (0x01) → 重置所有累計數據
  3. App 發送 Set Target Resistance Level (0x04) → 設定初始阻力
  4. App 發送 Start (0x07) → 開始運動
  5. 設備開始推播 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,避免耗盡電量

🔗 相關章節


◀ 上一章:App 架構 | 下一章:資料流與同步策略 ▶