本文使用的环境:
- sdk版本: nRF5_SDK_15.3.0_59ac345
- 硬件平台: nRF52832
- 例子:examples\ble_peripheral\ble_app_uart\pca10040
一 准备写入请求与执行写入请求
Prepare Write Request & Execute Write Request
准备写入请求与执行写入请求实现了两种功能。
- 它们提供了长属性值的写入功能。
- 它们允许在一个单独执行的原子操作中写入多个值。
属性服务器包含一个准备写入队列,其中保存有准备写入请求。队列的大小可独立配置,但通常它足够储存所有需要准备写入的服务。只有在收到执行写入请求时,准备写入的值才会写入属性,也就是说执行写入请求给出了执行这些准备写入操作的开始信号。
准备写入请求包含句柄、偏移量和部分属性值,这和大对象读取请求类似。这说明客户端既可以在队列中准备多个属性的值,又可以准备一个长属性值的各个部分。这样,在真正执行准备队列之前,客户端可以确定某属性的所有部分都能写入服务器。
准备写入响应也包含请求中所有的句柄、偏移量和部分属性值。之所以近乎偏执地重复这些数据是为了保证数据传递的可靠性。客户端可以对比响应和请求的字段值,保证准备的数据被正确地接收。对一些应用来说,这个验证环节是必要的。回忆先前的下水道阀门的例子,对这种重要的操作最好发送两次来保证可靠性,避免由于一个位的错误而导致未处理的污水淹没游乐园里的孩子们。
想知道客户端有多么的谨慎,不妨让我们向服务器发送一个字节,看看这个字节的数据受到了哪些保护:当客户端通过加密连接向服务器发送准备写入请求时,将有一个24位的CRC码来避免位错误,还有一个32位的MIC值来保证该请求发自正确的客户端;准备写入响应也用同样的24位CRC码和32位的MIC值来保护。
这意味着,为了保护属性值仅为一个字节的准备写入请求,总共用到了112位的错误检查值:保护字节与数据字节的比例是14:1。此外,如果客户端对准备写入响应不满意,它可以快速地发送一条标识位为"取消"的执行写入请求,终止准备写入队列,并重新开始值的写入。
一旦接收完所有的准备写入请求,服务器将拥有一个随时可以执行的准备写入队列。客户端发送标识位为"立刻写入"的执行写入请求,随后服务器将在一次原子操作中写入所有值。属性将按照其准备的顺序写入。如果客户端多次准备了同一个属性的值,那么服务器将按照顺序向该属性写入这些值。这意味着如果使用准备队列配置硬件状态,例如硬件需要先后执行禁用、配置、重新启用的操作,那么可以用一个准备队列来实现:在队列中先将对应的属性写入"禁用",接着写入"配置",之后在准备队列的末尾写入"启用",随后执行该队列。这样便能在一个原子操作中实现该硬件的重新配置。
二 nordic平台的实现过程
- 客户端(PEER,主设备)发送 Prepare Write Request 给到服务端的协议栈(SD)
- 服务端的协议栈会产生一个BLE_EVT_USER_MEM_REQUEST 事件给到用户层,向用户申请一块内存空间
- 用户使用 sd_ble_user_mem_reply 函数将内存信息告知协议栈。
- 接下来的事情就是在两端的协议栈里面发送的事情,客户端使用多个 Prepare Write Request 事件将长数据发给服务端的协议栈,协议栈会对每次的请求进行回复,回复时会将客户端发送过来的数据返回给客户端以表示正确收到数据。
注意:这个时候数据只是暂存在内存中,并没有写到属性里面去。
- 客户端写完数据后发送 Execute Write Request 用于告知服务端可以将数据写到属性中了。
- 客户端协议栈向用户层上报 BLE_GATTS_EVT_WRITE 事件表示开始写属性值。(只要是写属性都会上报这个事件)
- 客户端协议栈将长数据全部写到属性之后,会上报 BLE_EVT_USER_MEM_RELEASE 事件,此时用户就可以从属性里面拿值了。
实测发现,当写完属性之后,协议栈没有将属性数据上报给用户(没有BLE_NUS_EVT_RX_DATA事件),需要用户手动的去获取。
- 该操作的规定在蓝牙规范的Vol 3 ,Part F,3.4.6
三 代码中的具体实现
知道原理之后代码就比较简单了。
static uint8_t m_nus_buf[MEM_LENGTH];
static ble_user_mem_block_t m_nus_mem = {
.p_mem = m_nus_buf,
.len = MEM_LENGTH
};
static void get_value_form_SD(ble_nus_t * p_nus, ble_gatts_value_t * value)
{
uint32_t err_code;
/*
*从Rx中取数据
*参数1:连接句柄
*参数2:指向Rx属性特征值的句柄
*参数3:接收数据
*/
err_code = sd_ble_gatts_value_get(p_nus->conn_handle, p_nus->rx_handles.value_handle, value);
APP_ERROR_CHECK(err_code);
NRF_LOG_DEBUG("prepare len:%d value:%s",value->len, value->p_value);
}
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
{
switch (p_ble_evt->header.evt_id){
//.....
case BLE_EVT_USER_MEM_REQUEST:
//如果第二个参数为空,那么协议栈将上报BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST事件,不为空则不会不会。
err_code = sd_ble_user_mem_reply(p_ble_evt->evt.gattc_evt.conn_handle, &m_nus_mem);
APP_ERROR_CHECK(err_code);
break;
case BLE_EVT_USER_MEM_RELEASE:
{
//MEM_LENGTH的值不能大于510个字节,在ble_gatts.h中定义
uint8_t value_buffer[MEM_LENGTH] = {0};
ble_gatts_value_t value =
{
.len = sizeof(value_buffer),
.offset = 0,
.p_value = &(value_buffer[0])
};
get_value_form_SD(&m_nus, &value);
break;
}
}
}
ble_gatts.h
注意:在添加Rx属性的时候,需要attr_char_value.max_len的长度和MEM_LENGTH保持一致,否则在收到Execute Write Request之后会给客户端返回一个Invalid Attribute Value Length错误
重要
MEM_LENGTH的长度可以是510个字节,协议栈的队列空间大小计算公式为:
Head length = handle (2 byte) + offset (2 byte) + length (2 byte) = 6
MTU length = 23
DATA length = MTU length - Head length = 17
MEM_LENGTH = (Queue length * MTU length / DATA length) + 2
Queue length = (MEM_LENGTH - 2) * DATA length / MTU length
比如MEM_LENGTH为500,那么:
Queue length = (500 - 2) * 17 / 23 = 368
也就说客户端最多只能发送368个字节的数据到服务端的队列中,超过这个长度服务端将会返回一个队列满的错误给客户端(Prepare Queue Full).
四 另外一个实现方式
sd_ble_user_mem_reply函数传一个NULL下去,协议栈会上报BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST事件,用户可以在这个事件里面手动回复Prepare Write Request 和 Execute Write Request请求
static void on_long_write(ble_nus_t * p_nus, ble_evt_t const * p_ble_evt)
{
//static uint16_t write_evt_uuid;
static bool write_evt_uuid_set = false;
uint32_t err_code;
VERIFY_PARAM_NOT_NULL_VOID(p_nus);
VERIFY_PARAM_NOT_NULL_VOID(p_ble_evt);
ble_gatts_evt_write_t const * p_evt_write =
&p_ble_evt->evt.gatts_evt.params.authorize_request.request.write;
ble_gatts_rw_authorize_reply_params_t reply = {0};
if (p_evt_write->op == BLE_GATTS_OP_PREP_WRITE_REQ)
{
//err_code = get_evt_type_for_handle(p_evt_write->handle, &write_evt_uuid);
//APP_ERROR_CHECK(err_code);
write_evt_uuid_set = true;
reply.type = BLE_GATTS_AUTHORIZE_TYPE_WRITE;
reply.params.write.gatt_status = BLE_GATT_STATUS_SUCCESS;
reply.params.write.update = 1;
reply.params.write.offset = 0;
reply.params.write.len = p_evt_write->len;
reply.params.write.p_data = p_evt_write->data;
err_code = sd_ble_gatts_rw_authorize_reply(p_nus->conn_handle, &reply);
APP_ERROR_CHECK(err_code);
}
else if (p_evt_write->op == BLE_GATTS_OP_EXEC_WRITE_REQ_NOW)
{
uint8_t value_buffer[MEM_LENGTH] = {0};
ble_gatts_value_t value =
{
.len = sizeof(value_buffer),
.offset = 0,
.p_value = &(value_buffer[0])
};
ASSERT(write_evt_uuid_set);
write_evt_uuid_set = false;
reply.type = BLE_GATTS_AUTHORIZE_TYPE_WRITE;
reply.params.write.gatt_status = BLE_GATT_STATUS_SUCCESS;
reply.params.write.update = 1;
reply.params.write.offset = p_evt_write->offset;
reply.params.write.len = p_evt_write->len;
reply.params.write.p_data = p_evt_write->data;
err_code = sd_ble_gatts_rw_authorize_reply(p_nus->conn_handle, &reply);
APP_ERROR_CHECK(err_code);
/*
在BLE_EVT_USER_MEM_REQUEST事件中sd_ble_user_mem_reply第2个参数设置为空,那么协议栈会上报BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST事件。
通过抓包可以看到每次prepare write request请求都有回应,Execute Write Request也有回应。
但是这里得不到数据,不知什么原因。
*/
err_code = sd_ble_gatts_value_get( p_nus->conn_handle,
p_nus->rx_handles.value_handle,
&value);
APP_ERROR_CHECK(err_code);
NRF_LOG_DEBUG("prepare value %s",value.p_value);
}
else
{
}
}
static ret_code_t on_rw_authorize_req(ble_nus_t * p_nus, ble_evt_t const * p_ble_evt)
{
ret_code_t err_code;
VERIFY_PARAM_NOT_NULL(p_nus);
VERIFY_PARAM_NOT_NULL(p_ble_evt);
ble_gatts_evt_rw_authorize_request_t const * ar =
&p_ble_evt->evt.gatts_evt.params.authorize_request;
if (ar->type == BLE_GATTS_AUTHORIZE_TYPE_READ)
{
//err_code = on_read(p_escs, p_ble_evt);
//RETURN_IF_ERROR(err_code);
}
else if (ar->type == BLE_GATTS_AUTHORIZE_TYPE_WRITE)
{
if (ar->request.write.op == BLE_GATTS_OP_WRITE_REQ
|| ar->request.write.op == BLE_GATTS_OP_WRITE_CMD)
{
//err_code = on_write(p_escs, p_ble_evt);
//RETURN_IF_ERROR(err_code);
}
else if (ar->request.write.op == BLE_GATTS_OP_PREP_WRITE_REQ
|| ar->request.write.op == BLE_GATTS_OP_EXEC_WRITE_REQ_NOW)
{
on_long_write(p_nus, p_ble_evt);
}
else if (ar->request.write.op == BLE_GATTS_OP_EXEC_WRITE_REQ_CANCEL)
{
ble_gatts_rw_authorize_reply_params_t auth_reply;
memset(&auth_reply, 0, sizeof(auth_reply));
auth_reply.type = BLE_GATTS_AUTHORIZE_TYPE_WRITE;
auth_reply.params.write.gatt_status = BLE_GATT_STATUS_SUCCESS;
err_code = sd_ble_gatts_rw_authorize_reply(p_ble_evt->evt.gatts_evt.conn_handle, &auth_reply);
VERIFY_SUCCESS(err_code);
}
else
{
}
}
else
{
return NRF_ERROR_INVALID_STATE;
}
return NRF_SUCCESS;
}
static void ble_evt_handler(ble_evt_t const * p_ble_evt, void * p_context)
{
switch (p_ble_evt->header.evt_id){
//.....
case BLE_EVT_USER_MEM_REQUEST:
//如果第二个参数为空,那么协议栈将上报BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST事件,不为空则不会不会。
err_code = sd_ble_user_mem_reply(p_ble_evt->evt.gattc_evt.conn_handle, NULL);
APP_ERROR_CHECK(err_code);
break;
case BLE_GATTS_EVT_RW_AUTHORIZE_REQUEST:
{
on_rw_authorize_req(&m_nus, p_ble_evt);
}
break;
}
}
按照这种实现模式,通过抓包分析,Prepare Write Request 和 Execute Write Request请求都有给予正确的答复,但就是得不到属性中的数据,暂时没有搞清楚是怎么回事。