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;
}