联盛德W801系列6-从微信小程序的角度来分析W801的蓝牙通信源码(indicate方式)
- W801蓝牙通信源代码来源:作者: Mr.赵,CSND博客:W801/W800蓝牙收发数据与控制设计(一)-INDICATE ;源码:代码仓库
- 微信小程序源代码来源:作者:cwlgoodman,码云仓库: 微信小程序源代码,这个仓库有4个工程,我们用的是第一个:
1.bluetooth_demo
2.bluetooth_lock
3.glucometer
4.wechat_api
- 这篇文章要配合另外一篇一起读:《联盛德W801系列5-微信小程序与W801蓝牙通信例程(阅读笔记)》
1.蓝牙BLE GATT协议(转载)
这里摘录一段《ESP32学习笔记(30)——BLE GATT服务端自定义服务和特征》的内容:
1.1 通用属性协议(GATT)
GATT是用Attribute Protocal(属性协议)定义的一个service(服务)框架。这个框架定义了Services以及它们的Characteristics的格式和规程。规程就是定义了包括发现、读、写、通知、指示以及配置广播的characteristics。
为实现配置文件(Profile)的设备定义了两种角色:Client(客户端)、Server(服务器)。esp32(W801)的ble一般就处于Server模式。
一旦两个设备建立了连接,GATT就开始发挥效用,同时意味着GAP协议管理的广播过程结束了。
1.1.1 Profile(规范)
profile 可以理解为一种规范,建立的蓝牙应用任务,蓝牙任务实际上分为两类:标准蓝牙任务规范 profile(公有任务),非标准蓝牙任务规范 profile(私有任务)。
标准蓝牙任务规范 profile:指的是从蓝牙特别兴趣小组 SIG 的官网上已经发布的 GATT 规范列表,包括警告通知(alert notification),血压测量(blood pressure),心率(heart rate),电池(battery)等等。它们都是针对具体的低功耗蓝牙的应用实例来设计的。目前蓝牙技术联盟还在不断的制定新的规范,并且发布。
非标准蓝牙任务规范 profile:指的是供应商自定义的任务,在蓝牙 SIG 小组内未定义的任务规范。
1.1.2 Service(服务)
service 可以理解为一个服务,在 BLE 从机中有多个服务,例如:电量信息服务、系统信息服务等;
每个 service 中又包含多个 characteristic 特征值;
每个具体的 characteristic 特征值才是 BLE 通信的主题,比如当前的电量是 80%,电量的 characteristic 特征值存在从机的 profile 里,这样主机就可以通过这个 characteristic 来读取 80% 这个数据。
GATT 服务一般包含几个具有相关的功能,比如特定传感器的读取和设置,人机接口的输入输出。组织具有相关的特性到服务中既实用又有效,因为它使得逻辑上和用户数据上的边界变得更加清晰,同时它也有助于不同应用程序间代码的重用。
1.1.3 Characteristic(特征)
characteristic 特征,BLE 主从机的通信均是通过 characteristic 来实现,可以理解为一个标签,通过这个标签可以获取或者写入想要的内容。
1.1.4 UUID(通用唯一识别码)
uuid 通用唯一识别码,我们刚才提到的 service 和 characteristic 都需要一个唯一的 uuid 来标识;
每个从机都会有一个 profile,不管是自定义的 simpleprofile,还是标准的防丢器 profile,他们都是由一些 service 组成,每个 service 又包含了多个 characteristic,主机和从机之间的通信,均是通过characteristic来实现。
=================================抄录结束
GATT结构由嵌套的Profile、Service、Characteristics组成,如下图:
2.W801蓝牙例程中的ble_gatt_svc_def结构体
在 《\src\app\bleapp\wm_ble_server_api_demo.c》 中的结构体:
#define WM_GATT_SVC_UUID 0xFFF0
#define WM_GATT_INDICATE_UUID 0xFFF1
#define WM_GATT_WRITE_UUID 0xFFF2
static const struct ble_gatt_svc_def gatt_demo_svr_svcs[] = {
{
/* Service: uart */
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = BLE_UUID16_DECLARE(WM_GATT_SVC_UUID),
.characteristics = (struct ble_gatt_chr_def[]) { {
.uuid = BLE_UUID16_DECLARE(WM_GATT_WRITE_UUID),
.val_handle = &g_ble_demo_attr_write_handle,
.access_cb = gatt_svr_chr_demo_access_func,
.flags = BLE_GATT_CHR_F_WRITE,
},{
.uuid = BLE_UUID16_DECLARE(WM_GATT_INDICATE_UUID),
.val_handle = &g_ble_demo_attr_indicate_handle,
.access_cb = gatt_svr_chr_demo_access_func,
.flags = BLE_GATT_CHR_F_INDICATE,
},{
0, /* No more characteristics in this service */
}
},
},
{
0, /* No more services */
},
};
2.1微信小程序API函数 wx.onBluetoothDeviceFound
wx.onBluetoothDeviceFound,查找设备API,可以获得以下信息:
{"devices":
[
{
"deviceId":"28:6D:CD:D1:5C:30",
"name":"WM-D1:5C:30",
"RSSI":-52,
"connectable":true,
"advertisData":{},
"advertisServiceUUIDs":["0000FFF0-0000-1000-8000-00805F9B34FB"],
"localName":"WM-D1:5C:30",
"serviceData":{}
}
]
}
其中 deviceId为蓝牙MAC地址,name为下面代码生成《\src\app\bleapp\wm_bt_app.c》第22行:
int tls_ble_gap_init(void)
{
char default_device_name[MYNEWT_VAL(BLE_SVC_GAP_DEVICE_NAME_MAX_LENGTH)];
uint8_t bt_mac[6];
int ret_len = 0;
g_scan_state = WM_BLE_SCAN_STOP;
memset(&adv_params_dft, 0, sizeof(adv_params_dft));
adv_params_dft.conn_mode = BLE_GAP_CONN_MODE_UND; //default conn mode;
adv_params_dft.disc_mode = BLE_GAP_DISC_MODE_GEN; //default disc mode;
memset(&disc_params_dft, 0, sizeof(disc_params_dft));
memset(&direct_adv_addr, 0, sizeof(direct_adv_addr));
dl_list_init(&report_evt_list.list);
ble_npl_mutex_init(&report_evt_list.list_mutex);
if(btif_config_get_str("Local", "Adapter", "Name", default_device_name, &ret_len))
{
ble_svc_gap_device_name_set(default_device_name);
}else
{
tls_get_bt_mac_addr(bt_mac);
sprintf(default_device_name, "WM-%02X:%02X:%02X", bt_mac[3], bt_mac[4], bt_mac[5]);
ble_svc_gap_device_name_set(default_device_name);
}
return 0;
}
2.2微信小程序API函数wx.getBLEDeviceServices
wx.getBLEDeviceServices获取服务信息API,获取到以下内容:
{
"services":
[
{
"uuid":"0000FFF0-0000-1000-8000-00805F9B34FB",
"isPrimary":true
}
],
"errCode":0,
"errno":0,
"errMsg":"getBLEDeviceServices:ok"
}
对应下面的内容:
2.3微信小程序API函数wx.getBLEDeviceCharacteristics
wx.getBLEDeviceCharacteristics获取特征值API,获取以下内容:
{"characteristics":
[
{"uuid":"0000FFF2-0000-1000-8000-00805F9B34FB",
"handle":3,
"properties":
{
"read":false,
"write":true,
"notify":false,
"indicate":false,
"writeNoResponse":false,
"writeDefault":true
}
},
{"uuid":"0000FFF1-0000-1000-8000-00805F9B34FB",
"handle":5,
"properties":
{
"read":false,
"write":false,
"notify":false,
"indicate":true,
"writeNoResponse":false,
"writeDefault":false
}
}
],
"errCode":0,
"errno":0,
"errMsg":"getBLEDeviceCharacteristics:ok"
}
获取的特征值由下面的源码决定了:
2.4 微信小程序API函数 wx.notifyBLECharacteristicValueChange
wx.notifyBLECharacteristicValueChange 开启监听某个特征值的内容变化,如果有变化,会触发事件,调用 wx.onBLECharacteristicValueChange
我们判断到 properties.indicate == true,说明这个是指示属性,对这个uuid进行监听。
......
if (item.properties.indicate) {
//可读数据
wx.notifyBLECharacteristicValueChange({
state: true,
deviceId: options.connectedDeviceId,
serviceId: that.data.services[0].uuid,
characteristicId: item.uuid,
success: function (res) {
console.log('启用notify成功')
}
})
......
在 wx.onBLECharacteristicValueChange函数里面,把监听到的数据显示出来。
wx.onBLECharacteristicValueChange(function (res) {
var receiveText = app.buf2string(res.value)
console.log('接收到数据:' + receiveText)
that.setData({
receiveText: receiveText
})
})
2.5 微信小程序API函数 wx.writeBLECharacteristicValue
在本例中,"uuid"为【0000FFF2-0000-1000-8000-00805F9B34FB】的特征可以写入,当手机要对W801写入数据时使用wx.writeBLECharacteristicValue(注意第7行):
sendCmd:function(buff){
var that = this
if (that.data.connected) {
wx.writeBLECharacteristicValue({
deviceId: that.data.connectedDeviceId,
serviceId: that.data.services[0].uuid,
characteristicId: that.data.characteristics[0].uuid,
value: buff,
success: function (res) {
console.log('发送成功')
}
})
}
else {
wx.showModal({
title: '提示',
content: '蓝牙已断开',
showCancel: false,
success: function (res) {
that.setData({
searching: false
})
}
})
}
3.W801接收手机指令控制LED
在Mr.赵的例程中,使用3个字节分别控制3个灯(绿框),如图:
W801接收到数据后,控制LED的代码:
//接收手机发送的数据,注意是数据是按照字节进行的接收
tls_os_queue_receive(ble_q,&msg, 0, 0);
//打印ble收到数据的长度
printf("ble revice len:%d\n",msg[0]);
//依次打印收到的ble数据
for(u8 i=0;i<msg[0];i++){
printf("%x ",msg[i+1]);
send_data[i] = msg[i+1];
}printf("\n");
//分别判断前三个字节的数据,对应开关灯:00 开 其他数据 关
if(msg[1] != 0)
tls_gpio_write(WM_IO_PB_05,1);
else
tls_gpio_write(WM_IO_PB_05,0);
if(msg[2] != 0)
tls_gpio_write(WM_IO_PB_25,1);
else
tls_gpio_write(WM_IO_PB_25,0);
if(msg[3] != 0)
tls_gpio_write(WM_IO_PB_26,1);
else
tls_gpio_write(WM_IO_PB_26,0);
微信小程序端点亮第1个LED(PB5)的代码:
onLed1on:function(){
var that = this
if (that.data.connected) {
var buffer = new ArrayBuffer(3)
var dataView = new Uint8Array(buffer)
that.data.gbuff[0] = 0 // 全局变量,输出低电平--灯亮
dataView[0] = that.data.gbuff[0];
dataView[1]= that.data.gbuff[1];
dataView[2] = that.data.gbuff[2];
console.log('发送内容'+dataView[0]+dataView[1]+dataView[2]);
that.sendCmd(buffer);
}
},
4.W801发送数据到微信小程序
如果W801需要上报状态给手机端,只要调用下面的函数,就能被手机端监听到:
int tls_ble_server_demo_api_send_msg(uint8_t *data, int data_len)
{
int rc;
struct os_mbuf *om;
//TLS_BT_APPL_TRACE_DEBUG("### %s len=%d\r\n", __FUNCTION__, data_len);
if(g_send_pending) return BLE_HS_EBUSY;
if(data_len<=0 || data == NULL)
{
return BLE_HS_EINVAL;
}
om = ble_hs_mbuf_from_flat(data, data_len);
if (!om) {
return BLE_HS_ENOMEM;
}
rc = ble_gattc_indicate_custom(g_ble_demo_conn_handle,g_ble_demo_attr_indicate_handle, om);
if(rc == 0)
{
g_send_pending = 1;
}
return rc;
}