Picooc(有品)体脂秤 BLE 通信协议分析

来源:反编译 Picooc App v4.13.4 (com.picooc),jadx 解包 486MB 源码 设备:Picooc Mini(广播协议)/ Latin-S(连接协议)/ Dual-SPP(经典蓝牙)


1. 型号矩阵与协议差异

通信方式代表型号特点
Classic SPPDual-SPP经典蓝牙,非 BLE,独立协议栈(BTStandard.java)
BLE 连接型Latin-S / PICOOC-C1~CQ / PICOOC-14~25 / PICOOC-ISBLE GATT 连接,走 0xFFF0 服务
BLE 广播型PICOOC-L(Mini 等新秤)直读广播包,无需配对连接

Mini 的定位:走 PICOOC-L 广播协议,是 Picooc 最新方案。


2. BLE 服务与 UUID(连接型)

主服务

  • Service: 0000fff0-0000-1000-8000-00805f9b34fb
  • Write (App→秤): 0000fff2-0000-1000-8000-00805f9b34fb
  • Notify (秤→App): 0000fff1-0000-1000-8000-00805f9b34fb

辅助服务

  • Device Information: 0000180a-...
  • Quintic OTA: 0000fee8-...
  • Quintic QPP: 0000fee9-...

3. 通信协议数据包(连接型)

命令码总表

方向含义
0x30(48)秤→App请求用户信息(性别/身高/年龄)
0x32(50)秤→App体成分结果(含体脂率/水分/骨量等)
0x35(53)秤→AppUTC 时间同步请求
0x36(54)秤→App历史记忆数据
0x37(55)秤→App历史数据发送完毕
0x39(57)秤→App实时体重+阻抗(主数据流)
0x3A(58)秤→AppBLE 版本+BOM 信息+握手
0x3C(60)秤→App心率测量数据
0x3E(62)秤→App多频生物电阻抗数据
0x3F(63)秤→App平衡能力测试
0x51(81)秤→App握手(替代 0x3A)
0x52(82)秤→App实时体重(替代 0x39)
3B 03 01秤→App低电量提醒

写命令前缀

  • 拉丁系列: 0xF1 (如 F1 03 39 确认数据)
  • 部分新型号: 0xA1 (如 A1 03 52 确认数据)

0x39 实时体重数据格式

byte[0]  = 0x39
byte[1]  = length
byte[2-5] = Timestamp (Unix 秒, big-endian)
byte[6-7] = Weight (kg) = (b6<<8 | b7) / 20
byte[8-9] = Impedance = (b8<<8 | b9) / 10
byte[10-11] = WeightLB = (b10<<8 | b11) / 10
byte[12]   = Unit (0=kg, 1=斤, 2=lb)
byte[13-14] = Phase50K (相位角) = (b13<<8 | b14) / 100
byte[15]   = Flag (0=完成可上报)

0x32 体成分结果格式

byte[0]  = 0x32
byte[1]  = length
byte[2-3] = Weight (kg) /20
byte[4-5] = BodyFat (%) /10
byte[6-7] = Water (%) /10
byte[8-9] = (unknown)
byte[10-11] = BoneMass (kg) /10/2
byte[12-13] = (unknown)
byte[14]   = VisceralFat (直接取值)
byte[15]   = BodyAge (直接取值)
byte[18-19] = Impedance

连接握手流程

秤发 0x3A/0x51 (版本+时间请求)
  → App 回 F1 0A 3A [UTC-4B] [flag] [unit] [extra]
秤发 0x30 (请求用户信息)
  → App 回 F1 03 30 (ACK)
  → App 100ms 后回 31 06 01 [sex] [heightHex] [ageHex]
     sex: 01=男, 02=女
     height: (身高*10 - 1000) / 5
     age: 十六进制(<18 取 18)
秤开始发 0x39 (实时数据)
  → App 回 F1 03 39 (ACK 每包)
测量完成 → 秤主动断开

BOM 信息解析(握手阶段 0x3A)

data[11] 高 4 位 = 1 时:

version    = (data[11] & 0xF0) >> 4
bomVersion = data[11] & 0x0F
year       = (data[12] & 0xF0) >> 4
month      = data[12] & 0x0F
modelId    = (data[13] << 4) + ((data[14] & 0xF0) >> 4)
shortBom   = ((data[14] & 0x0F) << 10) + (data[15] << 2) + (data[16] >> 6)
shortFactoryId = data[16] & 0x3F

4. Picooc Mini 广播协议(PICOOC-L)

Mini 等新款秤使用广播协议,无需 GATT 连接,直接在 BLE ScanRecord 中携带数据。

广播名识别

  • 设备名: "PICOOC-L"
  • 或 AD Structure 偏移 2-9 匹配 "PICOOC-L"
  • 广播版本标志: scanRecord[18]0x3D(V2) / 0x3E(新协议)

V2 数据解析

position = scanRecord[0] + 1  // AD 结构起始偏移

byte[pos+2 ~ pos+7] = MAC 地址 (6B hex)
byte[pos+13] = Weight MSB
byte[pos+14] = Weight LSB    → kg = (b13<<8 | b14) / 20
byte[pos+15] = Impedance MSB
byte[pos+16] = Impedance LSB → imp = (b15<<8 | b16) / 10
byte[pos+17] = WeightLB MSB
byte[pos+18] = WeightLB LSB  → lb = (b17<<8 | b18) / 10
byte[pos+19] = Unit (0=kg, 1=斤, 2=lb)
byte[pos+20] = Checksum (前 18B 数据校验和)

数据校验

makeCheckSum(18 字节数据 hex) == 最后一个字节 hex

5. 关键源文件(反编译参考)

文件作用
BTBle.javaBLE 连接型协议核心实现(66KB)
BluetoothLeService.javaBLE GATT 服务封装
PicoocBlueToothProfile.java数据解析 + BodyIndexEntity 构建
BTBleForBroadcasDevice.java广播协议核心实现(Mini 等)
SampleGattAttributes.javaUUID 定义表
bleGlobalVariables.javaQuintic OTA 芯片 UUID
BluetoothUtils.javahex 转换工具
FatModelCaculate.java体成分算法
WeightMeasureController.java测量逻辑控制

6. Python 解析示例

连接型(0x39 实时数据)

def parse_picooc_39(data: bytes) -> dict:
    if len(data) < 16 or data[0] != 0x39:
        return None
    ts = (data[2]<<24) | (data[3]<<16) | (data[4]<<8) | data[5]
    weight = ((data[6]<<8) | data[7]) / 20.0
    imp = ((data[8]<<8) | data[9]) / 10.0
    flag = data[15] if len(data) >= 16 else 0
    return {
        'ts': ts, 'weight_kg': round(weight, 1),
        'impedance': int(imp), 'complete': flag == 0
    }

广播型(PICOOC-L ScanRecord)

def parse_picooc_broadcast(scan_record: bytes) -> dict:
    if len(scan_record) < 10: return None
    if bytes(scan_record[2:10]) != b'PICOOC-L':
        return None
    pos = scan_record[0] + 1
    mac = scan_record[pos+2:pos+8].hex()
    dpos = pos + 13
    weight = ((scan_record[dpos]<<8|scan_record[dpos+1])/20.0
              if dpos+1 < len(scan_record) else 0)
    imp = ((scan_record[dpos+2]<<8|scan_record[dpos+3])/10.0
           if dpos+3 < len(scan_record) else 0)
    return {'mac': mac, 'weight_kg': round(weight, 1), 'impedance': int(imp)}

7. 备注

  • App 使用 Flutter + 原生 Java 混合架构,BLE 通信在原生层
  • 体成分(体脂率等)由 App 端 FatModelCaculate 算法计算,非秤端直接返回
  • 体脂秤使用 Quintic 蓝牙芯片
  • 反编译产物路径: /opt/data/workspace/decompiled/picooc/
  • 完整协议文档: /opt/data/workspace/decompiled/picooc-ble-protocol.md