在ATT层协议框架内,拥有一组属性的设备称为服务端(Server),读写该属性值的设备称为客户端(Client),Server和Client通过ATT PDU进行交互。属性协议共有6种:
主机RX 从机TX 方向:
通知:从机端上传数据给主机,不需要主机回复一个响应
指示:从机端上传数据给主机,需要主机端发一个确认给服务器
通知和指示之间不同之处在于指示有应用层上的确认,而通知没有。
写
没有回应的写
读
/*GATT Characteristic Properties. */
typedef struct
{
/* Standard properties */
uint8_t broadcast :1; /**< Broadcasting of the value permitted. */
uint8_t read :1; /**< Reading the value permitted. */
uint8_t write_wo_resp :1; /**< Writing the value with Write Command permitted. */
uint8_t write :1; /**< Writing the value with Write Request permitted. */
uint8_t notify :1; /**< Notification of the value permitted. */
uint8_t indicate :1; /**< Indications of the value permitted. */
uint8_t auth_signed_wr :1; /**< Writing the value with Signed Write Command permitted. */
} ble_gatt_char_props_t;
Client Characteristic Configuration Descriptor(CCCD)是客户端特征配置描述符
当主机向CCCD中写入0x0001,此时使能notify;当写入0x0000时,此时禁止notify。
从机设备流程
1. 开启广播
2. 被主机成功连接,并交互连接参数
3. 等待主机获取服务
4. 等待主机成功使能notify功能
5. 从机给主机发送相应的notify数据包
初始化服务:
注册的服务句柄p_nus->service_handle。
使能了按键的notify通知属性 add_char_params.char_props.notify = 1;
uint32_t ble_nus_init(ble_nus_t * p_nus, ble_nus_init_t const * p_nus_init)
{
ble_uuid_t ble_uuid;
ble_uuid128_t nus_base_uuid = NUS_BASE_UUID;
ble_add_char_params_t add_char_params;
// Initialize the service structure.
p_nus->data_handler = p_nus_init->data_handler;
// Add a custom base UUID.
sd_ble_uuid_vs_add(&nus_base_uuid, &p_nus->uuid_type);
ble_uuid.type = p_nus->uuid_type;
ble_uuid.uuid = BLE_UUID_NUS_SERVICE;
// Add the service.
sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY,&ble_uuid, &p_nus->service_handle);
add_char_params.uuid = BLE_UUID_NUS_RX_CHARACTERISTIC;
add_char_params.uuid_type = p_nus->uuid_type;
add_char_params.max_len = BLE_NUS_MAX_RX_CHAR_LEN;
add_char_params.init_len = sizeof(uint8_t);
add_char_params.is_var_len = true;
add_char_params.char_props.write = 1; /**< */
add_char_params.char_props.write_wo_resp = 1; /**< */
#ifdef BOND_ENABLE
add_char_params.read_access = SEC_MITM;
add_char_params.write_access = SEC_MITM;
#else
add_char_params.read_access = SEC_OPEN;
add_char_params.write_access = SEC_OPEN;
#endif
characteristic_add(p_nus->service_handle,&add_char_params,&p_nus->rx_handles);
// Add the TX Characteristic.
memset(&add_char_params, 0, sizeof(add_char_params));
add_char_params.uuid = BLE_UUID_NUS_TX_CHARACTERISTIC;
add_char_params.uuid_type = p_nus->uuid_type;
add_char_params.max_len = BLE_NUS_MAX_TX_CHAR_LEN;
add_char_params.init_len = sizeof(uint8_t);
add_char_params.is_var_len = true;
add_char_params.char_props.notify = 1; /* 使能通知 */
#ifdef BOND_ENABLE
add_char_params.read_access = SEC_MITM; /**< Access possible with 'MITM' security at least. */
add_char_params.write_access = SEC_MITM;
add_char_params.cccd_write_access = SEC_MITM;
#else
add_char_params.read_access = SEC_OPEN; /**< Access open. */
add_char_params.write_access = SEC_OPEN;
add_char_params.cccd_write_access = SEC_OPEN;
#endif
Return characteristic_add(p_nus->service_handle, &add_char_params, &p_nus->tx_handles);
}
接收通知使能:
在BLE 事件处理的函数中,要处理 CCCD_Write 的数据,所以在由 softdevice 返回消息的 ble_nus_on_ble_evt 函数中,需要处理BLE_GATTS_EVT_WRITE 事件。
void ble_nus_on_ble_evt(ble_evt_t const * p_ble_evt, void * p_context)
{
ble_nus_t * p_nus = (ble_nus_t *)p_context;
switch (p_ble_evt->header.evt_id)
{
case BLE_GAP_EVT_CONNECTED:
on_connect(p_nus, p_ble_evt);
break;
case BLE_GATTS_EVT_WRITE:
on_write(p_nus, p_ble_evt);
break;
case BLE_GATTS_EVT_HVN_TX_COMPLETE:
on_hvx_tx_complete(p_nus, p_ble_evt);
break;
default:
// No implementation needed.
break;
}
}
static void on_write(ble_nus_t * p_nus, ble_evt_t const * p_ble_evt)
{
ble_nus_evt_t evt;
ble_nus_client_context_t * p_client;
ble_gatts_evt_write_t const * p_evt_write = &p_ble_evt->evt.gatts_evt.params.write;
blcm_link_ctx_get(p_nus->p_link_ctx_storage,
p_ble_evt->evt.gatts_evt.conn_handle,(void*)&p_client);
evt.p_nus = p_nus;
evt.conn_handle = p_ble_evt->evt.gatts_evt.conn_handle;
evt.p_link_ctx = p_client;
/* check cccd_handle and data length, 0100 */
if ((p_evt_write->handle == p_nus->tx_handles.cccd_handle) && (p_evt_write->len == 2))
{
if (p_client != NULL)
{
if (ble_srv_is_notification_enabled(p_evt_write->data))
{
p_client->is_notification_enabled = true;
evt.type = BLE_NUS_EVT_COMM_STARTED;
}
else
{
p_client->is_notification_enabled = false;
evt.type = BLE_NUS_EVT_COMM_STOPPED;
}
if (p_nus->data_handler != NULL)
{
p_nus->data_handler(&evt);
}
}
}
else if ((p_evt_write->handle == p_nus->rx_handles.value_handle) &&
(p_nus->data_handler != NULL))
{
evt.type = BLE_NUS_EVT_RX_DATA;
evt.params.rx_data.p_data = p_evt_write->data;
evt.params.rx_data.length = p_evt_write->len;
p_nus->data_handler(&evt);
}
else
{
}
}
static void on_hvx_tx_complete(ble_nus_t * p_nus, ble_evt_t const * p_ble_evt)
{
ble_nus_evt_t evt;
ble_nus_client_context_t * p_client;
blcm_link_ctx_get(p_nus->p_link_ctx_storage,
p_ble_evt->evt.gatts_evt.conn_handle, (void *) &p_client);
if (p_client->is_notification_enabled)
{
memset(&evt, 0, sizeof(ble_nus_evt_t));
evt.type = BLE_NUS_EVT_TX_RDY;
evt.p_nus = p_nus;
evt.conn_handle = p_ble_evt->evt.gatts_evt.conn_handle;
evt.p_link_ctx = p_client;
p_nus->data_handler(&evt);
}
}
从机发送notify数据:
uint32_t ble_nus_data_send(ble_nus_t * p_nus,
uint8_t * p_data,
uint16_t * p_length,
uint16_t conn_handle)
{
ble_gatts_hvx_params_t hvx_params;
ble_nus_client_context_t * p_client;
blcm_link_ctx_get(p_nus->p_link_ctx_storage, conn_handle, (void *) &p_client);
hvx_params.handle = p_nus->tx_handles.value_handle;
hvx_params.p_data = p_data;
hvx_params.p_len = p_length;
hvx_params.type = BLE_GATT_HVX_NOTIFICATION; /* notify 属性的数据 */
return sd_ble_gatts_hvx(conn_handle, &hvx_params);
}