BLE直接读取解析花花草草植物传感器

小米花花草草(MiFlora HHCCJCY01)BLE 数据读取与解析指南

设备信息

  • 型号: HHCCJCY01HHCC(Mi Smart Flower Monitor / Flower Care / MiFlora)
  • FCC ID: 2AJEPHHCCHCY01HHCC
  • 连接方式: BLE (Bluetooth Low Energy) GATT
  • 测量项目: 温度、光照、土壤湿度、土壤导电率(肥力)、电池电量

BLE 服务与特征值

设备提供以下关键 BLE 服务:

服务 UUID 用途
0x1204 (00001204-0000-1000-8000-00805f9b34fb) 传感器数据服务
0xFE95 小米专有服务(加密)
0x1800 GAP(设备名称等)

传感器数据服务 0x1204 下有三个关键特征值:

特征值 UUID Handle 用途
0x1a00 写入控制命令
0x1a01 读取实时传感器数据
0x1a02 读取电池电量和固件版本

读取流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
1. BLE 扫描,找到名为 "Flower care" 的设备
2. 建立 GATT 连接
3. 发现服务 0x1204 及其特征值
4. 向 0x1a00 写入命令 [0xA0, 0x1F](启用实时数据模式)
5. 等待 300~1000ms(传感器需要时间准备数据)
6. 从 0x1a01 读取 10 字节传感器数据
7. (可选)从 0x1a02 读取电池信息
8. 断开连接

重要细节

  • 写入目标是 0x1a00(控制特征),不是 0x1a01(数据特征)。如果误写到 0x1a01,读取时会得到写入命令的回显(以 a0 1f 开头的数据),而不是传感器数据。
  • 写入后需要等待至少 300ms,建议 1000ms,让传感器完成一次测量。
  • 设备广播名为 “Flower care”(注意空格和大小写)。

数据格式解析

传感器数据(0x1a01,10 字节)

从 0x1a01 读取的数据为标准 MiFlora 格式:

1
2
字节位置:  [0]  [1]  [2]  [3]  [4]  [5]  [6]  [7]  [8]  [9]
含义:      温度低 温度高 填充  光照0 光照1 光照2 光照3 湿度  导电0 导电1
字段 字节位置 类型 单位 解析方法
温度 0-1 int16 LE 0.1°C (buf[0] | buf[1]<<8) / 10.0
填充 2 - - 忽略
光照 3-6 uint32 LE lux buf[3] | buf[4]<<8 | buf[5]<<16 | buf[6]<<24
土壤湿度 7 uint8 % buf[7]
土壤导电率 8-9 uint16 LE µS/cm buf[8] | buf[9]<<8

示例解析

原始数据(hex):fe 00 03 b5 00 00 00 13 42 01

1
2
3
4
5
温度:     0x00fe = 254  → 254 / 10 = 25.4°C
填充:     0x03(忽略)
光照:     0x000000b5 = 181 lux
土壤湿度: 0x13 = 19%
导电率:   0x0142 = 322 µS/cm

电池数据(0x1a02)

读取 0x1a02 返回的第一个字节即为电池电量百分比:

1
buf[0] = 电池百分比 (0-100)

NimBLE(ESP-IDF)中的 UUID 匹配注意事项

在使用 ESP-IDF NimBLE 协议栈时,设备可能将标准蓝牙基础 UUID(0000xxxx-0000-1000-8000-00805f9b34fb)报告为完整的 128-bit UUID,而不是 16-bit 短格式。此时 ble_uuid_u16() 会返回 0。

需要自定义函数从 128-bit UUID 中提取 16-bit 部分:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
static uint16_t uuid_to_16(const ble_uuid_t *uuid)
{
    uint16_t u16 = ble_uuid_u16(uuid);
    if (u16 != 0) return u16;

    if (uuid->type == BLE_UUID_TYPE_128) {
        const ble_uuid128_t *u128 = (const ble_uuid128_t *)uuid;
        // 蓝牙标准基础 UUID(小端存储)
        static const uint8_t base[12] = {
            0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00,
            0x00, 0x80, 0x00, 0x10, 0x00, 0x00
        };
        if (memcmp(u128->value, base, 12) == 0 &&
            u128->value[14] == 0x00 && u128->value[15] == 0x00) {
            return u128->value[12] | (u128->value[13] << 8);
        }
    }
    return 0;
}

常见问题

Q: 读取到的数据以 a0 1f 开头?

原因: 写入命令写到了 0x1a01(数据特征)而不是 0x1a00(控制特征)。读取时得到的是写入命令的回显。

解决: 确保写 [0xA0, 0x1F]0x1a00,然后从 0x1a01 读取。

Q: 数据全为零或不更新?

原因: 传感器的测量周期约为 15 分钟。如果传感器刚启动或探头不在土壤中,可能还没有完成测量。频繁的 BLE 连接(< 5分钟)可能打断传感器的内部测量周期。

建议: 读取间隔设为 10-15 分钟,确保传感器探头插入土壤中。

Q: BLE 连接不稳定?

原因: ESP32 的 WiFi 和 BLE 共享 2.4GHz 射频,存在共存干扰。

建议:

  • 增加连接超时(建议 30 秒)
  • 添加重试机制(建议 3 次)
  • 连接间隔不要太频繁

参考数值范围

指标 正常范围 说明
温度 -20 ~ 60°C 精度 0.1°C
光照 0 ~ 100,000 lux 0=全黑,100k=直射阳光
土壤湿度 0 ~ 100% 0=完全干燥,空气中约0-5%
导电率 0 ~ 5000 µS/cm 反映土壤肥力,0=纯水/空气
电池 0 ~ 100% CR2032 纽扣电池

完整 C 代码片段(ESP-IDF + NimBLE)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 写入启用实时数据命令到 0x1a00
static const uint8_t REALTIME_CMD[] = {0xa0, 0x1f};
ble_gattc_write_flat(conn, ctrl_handle,    // 0x1a00 的 handle
                     REALTIME_CMD, 2, write_cb, NULL);

// 写入回调中等待后读取 0x1a01
static int write_cb(uint16_t conn, ...) {
    vTaskDelay(pdMS_TO_TICKS(1000));
    ble_gattc_read(conn, data_handle, read_cb, NULL);  // 0x1a01 的 handle
    return 0;
}

// 读取回调中解析数据
static int read_cb(uint16_t conn, ...) {
    uint8_t buf[16];
    // ... 从 attr->om 复制数据到 buf ...

    float temperature    = (int16_t)(buf[0] | buf[1] << 8) / 10.0f;
    uint32_t light       = buf[3] | buf[4]<<8 | buf[5]<<16 | buf[6]<<24;
    uint8_t moisture     = buf[7];
    uint16_t conductivity = buf[8] | buf[9] << 8;

    return 0;
}
使用 Hugo 构建
主题 StackJimmy 设计