w800 连接小米温湿度计获取温湿度
时间:6天前 人气:...

w800 具有 ble 功能,所以当我看到家里有几个蓝牙版的温湿度计之后,

不由想到完全可以用 w800 做主控,再搭配一个红外模块(淘宝上才几块钱一个),

就可以采集这几个温湿度传感器的温度,进而用红外模块模拟空调遥控器对室内空调进行控制,这就比那些空调自带的温度采集要准确了,控制家里温度应该可以达到更好的效果。

所以,第一步就是 w800 如何采集他们的温度。经过研究发现,小米温湿度计 2 和 pro 这两款可以在连接上之后,订阅服务 UUID 为 ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6 ,其特征为 ebe0ccc1-7a0a-4b0c-8a1a-6ff2997da3a6 之后,就可以每隔几秒拿到温湿度信息了。

对于青萍温湿度传感器采集更加简单,就不用连接了,其在广播中直接携带温湿度信息,所以只需要定期扫描接收 CG Beacon 帧即可,没啥好多说的了。

下面对 w800 连接小米温湿度传感器 2 做个示例。主要工作流程为:扫描附近的小米温湿度传感器 2,当其存在之后,连接它,连接之后订阅温湿度服务,然后就等着收到。需要注意,保持连接之后会加速消耗小米温湿度传感器 2 的电池电量,所以尽量定期连接一次,拿到温湿度之后断开它。

扫描:

struct bt_le_scan_param scan_param = {
        .type = BT_LE_SCAN_TYPE_ACTIVE,
        .options = BT_LE_SCAN_OPT_NONE,
        .interval = BT_GAP_SCAN_FAST_INTERVAL,
        .window = BT_GAP_SCAN_FAST_WINDOW,
    };

bt_le_scan_start(BT_LE_SCAN_PASSIVE, scan_cb);

连接:

static struct bt_conn *default_conn;

static void scan_cb(const bt_addr_le_t *addr, int8_t rssi,
                   uint8_t adv_type, struct net_buf_simple *buf)
{
    char addr_str[BT_ADDR_LE_STR_LEN];

    /* We're only interested in connectable events */
    if (adv_type != BT_GAP_ADV_TYPE_ADV_IND && adv_type != BT_GAP_ADV_TYPE_ADV_DIRECT_IND) {
        return;
    }

    // 检查MAC地址前缀
    bt_addr_le_to_str(addr, addr_str, sizeof(addr_str));
    
    if (strncmp(addr_str, "A4:C1:38", strlen("A4:C1:38")) != 0) {
        return;
    }

    printk("找到目标设备: %s\n", addr_str);

    // 停止扫描
    bt_le_scan_stop();

    // 建立连接
    bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN, BT_LE_CONN_PARAM_DEFAULT, &default_conn);
}

发现服务:

// 小米温湿度计服务UUID (ebe0ccb0-7a0a-4b0c-8a1a-6ff2997da3a6)
static struct bt_uuid_128 mi_service_uuid = BT_UUID_INIT_128(
    0xA6, 0xA3, 0x7D, 0x99, 0xF2, 0x6F, 0x1A, 0x8A,
    0x0C, 0x4B, 0x0A, 0x7A, 0xB0, 0xCC, 0xE0, 0xEB);

static struct bt_gatt_discover_params discover_params;

static void connected(struct bt_conn *conn, uint8_t err)
{
    char addr[BT_ADDR_LE_STR_LEN];

    bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));

    if (err) {
        printk("连接失败 (%s): %u\n", addr, err);
        bt_conn_unref(default_conn);
        default_conn = NULL;

        start_scanning();
        return;
    }

    printk("已连接: %s\n", addr);
    default_conn = bt_conn_ref(conn);

    // 开始服务发现
    discover_params.uuid = (struct bt_uuid *)&mi_service_uuid;
    discover_params.start_handle = 0x0001;
    discover_params.end_handle = 0xFFFF;
    discover_params.type = BT_GATT_DISCOVER_PRIMARY;
    discover_params.func = discover_service_cb;

    err = bt_gatt_discover(conn, &discover_params);
    if (err) {
        printk("服务发现失败: %d\n", err);
    }
}

static uint8_t discover_service_cb(struct bt_conn *conn,
                                  const struct bt_gatt_attr *attr,
                                  struct bt_gatt_discover_params *params)
{
    if (!attr) {
        printk("服务发现完成\n");
        return BT_GATT_ITER_STOP;
    }

    const struct bt_gatt_service *svc = (struct bt_gatt_service *)attr->user_data;

    // 配置特征发现参数
    discover_params.uuid = (struct bt_uuid *)&mi_data_char_uuid;
    discover_params.start_handle = attr->handle + 1;
    discover_params.end_handle = 0xFFFF;
    discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
    discover_params.func = discover_char_cb;

    int err = bt_gatt_discover(conn, &discover_params);
    if (err) {
        printk("特征发现失败: %d\n", err);
    }

    return BT_GATT_ITER_STOP;
}

订阅:

static struct bt_gatt_subscribe_params subscribe_params;

static uint8_t discover_char_cb(struct bt_conn *conn,
                               const struct bt_gatt_attr *attr,
                               struct bt_gatt_discover_params *params)
{
    if (!attr) {
        printk("特征发现完成\n");
        return BT_GATT_ITER_STOP;
    }

    const struct bt_gatt_chrc *chrc = (struct bt_gatt_chrc *)attr->user_data;

    if (bt_uuid_cmp(chrc->uuid, (struct bt_uuid *)&mi_data_char_uuid) == 0) {
        printk("找到数据特征,句柄: 0x%04x\n", chrc->value_handle);

        // 配置订阅参数
        subscribe_params.notify = notify_cb;
        subscribe_params.value = BT_GATT_CCC_NOTIFY;
        subscribe_params.value_handle = chrc->value_handle;
        subscribe_params.ccc_handle = chrc->value_handle + 2; // CCC描述符位置
        subscribe_params.end_handle = 0xFFFF;

        int err = bt_gatt_subscribe(conn, &subscribe_params);
        if (err) {
            printk("订阅失败: %d\n", err);
        } else {
            printk("订阅成功\n");
        }

        return BT_GATT_ITER_STOP;
    }

    return BT_GATT_ITER_CONTINUE;
}

计算温湿度和电量


static uint8_t notify_cb(struct bt_conn *conn,
                        struct bt_gatt_subscribe_params *params,
                        const void *data, uint16_t length)
{
    if (!data) {
        printk("Notification unsubscribed\n");
        return BT_GATT_ITER_STOP;
    }

    printk("收到数据 (%u bytes):\n", length);
    parse_sensor_data(data, length);
    return BT_GATT_ITER_CONTINUE;
}

static void parse_sensor_data(const uint8_t *data, uint16_t len)
{
    if (len < 5) {
        printk("Invalid data length: %d\n", len);
        return;
    }

    // 温度(小端序,单位0.01°C)
    int16_t temp = (data[1] << 8) | data[0];
    // 湿度(小端序,单位1%)
    uint8_t humidity = data[2];
    // 电池电量(单位mv)
    uint16_t battery = (data[4] << 8) | data[3];
    uint8_t battery_p = calculate_battery(battery);

    //floorf规避四舍五入
    printk("温度: %.1f°C, 湿度: %d%%, 电池: %d%%mv(%d%%)\n",
           (floorf(temp / 10.0f)) / 10.0f, humidity, battery, battery_p);
}

默认读取到的电量单位是 mv,所以需要转百分比的话,需要另行计算:

static int calculate_battery(int voltage) {
    // 定义电压范围常量
    const int MIN_VOLTAGE = 2000;
    const int MAX_VOLTAGE = 3261;
    
    // 边界检查
    if (voltage <= MIN_VOLTAGE) {
        return 0;
    } else if (voltage >= MAX_VOLTAGE) {
        return 100;
    }

    // 计算线性比例
    float percentage = ((float)(voltage - MIN_VOLTAGE) / 
                      (MAX_VOLTAGE - MIN_VOLTAGE)) * 100.0f;

    // 四舍五入并转换为整数
    int battery = roundf(percentage);

    return battery;
}


上一篇:w800 连接国产开源物联云 fastbee 教程
下一篇:没有下一篇了
热门评论