前面还差比较关键的部分,HID的实现,其中advertData在一开始抓包时候已经详细说过了,scanRspData是扫描回复,蓝牙文档里也会说的明白,attDeviceName就是具体设备的名字.
void HidEmu_Init()
{
hidEmuTaskId = TMOS_ProcessEventRegister(HidEmu_ProcessEvent);
// 这里设置需要广播的内容,以及如果需要的话,回复扫描信息
{
uint8 initial_advertising_enable = TRUE;
// 是否广播
GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED, sizeof(uint8), &initial_advertising_enable);
// 广播内容 / 回复扫描信息
GAPRole_SetParameter(GAPROLE_ADVERT_DATA, sizeof(advertData), advertData);
GAPRole_SetParameter(GAPROLE_SCAN_RSP_DATA, sizeof(scanRspData), scanRspData);
}
// 设置GAP字符串/设备名称
GGS_SetParameter(GGS_DEVICE_NAME_ATT, GAP_DEVICE_NAME_LEN, (void *)attDeviceName);
// 设置绑定参数
{
uint32 passkey = DEFAULT_PASSCODE;
uint8 pairMode = DEFAULT_PAIRING_MODE;
uint8 mitm = DEFAULT_MITM_MODE;
uint8 ioCap = DEFAULT_IO_CAPABILITIES;
uint8 bonding = DEFAULT_BONDING_MODE;
GAPBondMgr_SetParameter(GAPBOND_PERI_DEFAULT_PASSCODE, sizeof(uint32), &passkey);
GAPBondMgr_SetParameter(GAPBOND_PERI_PAIRING_MODE, sizeof(uint8), &pairMode);
GAPBondMgr_SetParameter(GAPBOND_PERI_MITM_PROTECTION, sizeof(uint8), &mitm);
GAPBondMgr_SetParameter(GAPBOND_PERI_IO_CAPABILITIES, sizeof(uint8), &ioCap);
GAPBondMgr_SetParameter(GAPBOND_PERI_BONDING_ENABLED, sizeof(uint8), &bonding);
}
// 设置电池警戒数值
{
uint8 critical = DEFAULT_BATT_CRITICAL_LEVEL;
Batt_SetParameter(BATT_PARAM_CRITICAL_LEVEL, sizeof(uint8), &critical);
}
// 具体服务的初始化(鼠标)
Hid_AddService();
// 具体服务的回调
HidDev_Register(&hidEmuCfg, &hidEmuHidCBs);
// 开启HidEmu_ProcessEvent
tmos_set_event(hidEmuTaskId, START_DEVICE_EVT);
}
进入Hid_AddService根据惯例,注册一个hidAttrTbl,以及绑定一些hidCBs,还要通过HidDev_RegisterReports上报自己有的HID属性,其中这里绑定的handle都是0,由库进行填充.这里重点说hidAttrTbl,里面还包含了关于鼠标的描述符.
bStatus_t HidDev_ReadAttrCB(uint16 connHandle, gattAttribute_t *pAttr,
uint8 *pValue, uint16 *pLen, uint16 offset, uint16 maxLen, uint8 method)
{
bStatus_t status = SUCCESS;
hidRptMap_t *pRpt;
uint16 uuid = BUILD_UINT16(pAttr->type.uuid[0], pAttr->type.uuid[1]);
// 只有HID描述符是超长的...
if (offset > 0 && uuid != REPORT_MAP_UUID)
{
return (ATT_ERR_ATTR_NOT_LONG);
}
if (uuid == REPORT_UUID ||
uuid == BOOT_KEY_INPUT_UUID ||
uuid == BOOT_KEY_OUTPUT_UUID ||
uuid == BOOT_MOUSE_INPUT_UUID)
{
// 根据Handle读取我现在应该由哪一个报告回调,前面hidRptMap设置了三个东西嘛,这里就要区分.
// 而且只有mode = HID_PROTOCOL_MODE_REPORT的才会继续.(所幸我们这里全是.)
if ((pRpt = hidDevRptByHandle(pAttr->handle)) != NULL)
{
// 分别对应上层的hidEmuRptCB.
status = (*pHidDevCB->reportCB)(pRpt->id, pRpt->type, uuid,
HID_DEV_OPER_READ, pLen, pValue);
}
else
{
*pLen = 0;
}
}
else if (uuid == REPORT_MAP_UUID)
{
// 偏移检查
if (offset >= hidReportMapLen)
{
status = ATT_ERR_INVALID_OFFSET;
}
else
{
// 看看缓冲区能读多长
*pLen = MIN(maxLen, (hidReportMapLen - offset));
// 复制数据
tmos_memcpy(pValue, pAttr->pValue + offset, *pLen);
}
}
else if (uuid == HID_INFORMATION_UUID)
{
*pLen = HID_INFORMATION_LEN;
tmos_memcpy(pValue, pAttr->pValue, HID_INFORMATION_LEN);
}
else if (uuid == GATT_REPORT_REF_UUID)
{
*pLen = HID_REPORT_REF_LEN;
tmos_memcpy(pValue, pAttr->pValue, HID_REPORT_REF_LEN);
}
else if (uuid == PROTOCOL_MODE_UUID)
{
*pLen = HID_PROTOCOL_MODE_LEN;
pValue[0] = pAttr->pValue[0];
}
else if (uuid == GATT_EXT_REPORT_REF_UUID)
{
*pLen = HID_EXT_REPORT_REF_LEN;
tmos_memcpy(pValue, pAttr->pValue, HID_EXT_REPORT_REF_LEN);
}
return (status);
}
上面关键的操作就是回调到RptCb里,从hidEmuRptCB传递到Hid_GetParameter,写入是他的反向操作,这里主要是几个参数的保存和读取.另外程序里是不是有坑呢?不管读取哪个Feature,都是到hidReportFeature里.我的水平暂时还研究不了.
写入事件就稍微复杂一些,他分别有冻结/解冻,可操作/不可操作等参数的配置.但是也没太多事情,这里也贴出简单解释.
bStatus_t HidDev_WriteAttrCB(uint16 connHandle, gattAttribute_t *pAttr,
uint8 *pValue, uint16 len, uint16 offset, uint8 method)
{
uint16 uuid;
bStatus_t status = SUCCESS;
hidRptMap_t *pRpt;
// 写入过程没有超长的,因此不考虑这个情况.
if (offset > 0)
{
return (ATT_ERR_ATTR_NOT_LONG);
}
uuid = BUILD_UINT16(pAttr->type.uuid[0], pAttr->type.uuid[1]);
if (uuid == REPORT_UUID ||
uuid == BOOT_KEY_OUTPUT_UUID)
{
// 这个在读取时候已经说过了.
if ((pRpt = hidDevRptByHandle(pAttr->handle)) != NULL)
{
// 无非是写入保存参数,读取时候也已经说过了.
status = (*pHidDevCB->reportCB)(pRpt->id, pRpt->type, uuid,
HID_DEV_OPER_WRITE, &len, pValue);
}
}
else if (uuid == HID_CTRL_PT_UUID)
{
// 验证内容长度,一般只有冻结和解冻.
if (len == 1)
{
if (pValue[0] == HID_CMD_SUSPEND || pValue[0] == HID_CMD_EXIT_SUSPEND)
{
// 实际调用hidEmuEvtCB,事实上没有实现任何内容
(*pHidDevCB->evtCB)((pValue[0] == HID_CMD_SUSPEND) ? HID_DEV_SUSPEND_EVT : HID_DEV_EXIT_SUSPEND_EVT);
}
else
{
status = ATT_ERR_INVALID_VALUE;
}
}
else
{
status = ATT_ERR_INVALID_VALUE_SIZE;
}
}
else if (uuid == GATT_CLIENT_CHAR_CFG_UUID)
{
status = GATTServApp_ProcessCCCWriteReq(connHandle, pAttr, pValue, len,
offset, GATT_CLIENT_CFG_NOTIFY);
if (status == SUCCESS)
{
uint16 charCfg = BUILD_UINT16(pValue[0], pValue[1]);
// 首先CCC Handle就只有鼠标那个有,即HID_REPORT_MOUSE_IN_IDX所在AttrMap内.
if ((pRpt = hidDevRptByCccdHandle(pAttr->handle)) != NULL)
{
// 分别对应上层的hidEmuRptCB,又写到那个变量里了.
(*pHidDevCB->reportCB)(pRpt->id, pRpt->type, uuid,
(charCfg == GATT_CLIENT_CFG_NOTIFY) ? HID_DEV_OPER_ENABLE : HID_DEV_OPER_DISABLE,
&len, pValue);
}
}
}
else if (uuid == PROTOCOL_MODE_UUID)
{
if (len == HID_PROTOCOL_MODE_LEN)
{
if (pValue[0] == HID_PROTOCOL_MODE_BOOT ||
pValue[0] == HID_PROTOCOL_MODE_REPORT)
{
pAttr->pValue[0] = pValue[0];
// 没实际实现,在Read时候说过了.
(*pHidDevCB->evtCB)((pValue[0] == HID_PROTOCOL_MODE_BOOT) ? HID_DEV_SET_BOOT_EVT : HID_DEV_SET_REPORT_EVT);
}
else
{
status = ATT_ERR_INVALID_VALUE;
}
}
else
{
status = ATT_ERR_INVALID_VALUE_SIZE;
}
}
return (status);
}
记得一开始我们启动了HidEmu_ProcessEvent,事实上,之前也启动了一个HidDev_ProcessEvent,我们还没分析好.里面的GAPRole_PeripheralStartDevice关联了多个回调,现在才能有一个解答.
// Bond Manager Callbacks
static gapBondCBs_t hidDevBondCB =
{
hidDevPasscodeCB,
hidDevPairStateCB};
// GAP Role Callbacks
static gapRolesCBs_t hidDev_PeripheralCBs =
{
hidDevGapStateCB, // Profile State Change Callbacks
NULL, // When a valid RSSI is read from controller
hidDevParamUpdateCB};
拆开讨论,hidDevPasscodeCB会调用hidEmuCfg->passcodeCB,即hidEmuHidCBs的passcodeCB,但是这里没设置,所以等于没有.hidDevPairStateCB会标记一下配对状态,其中还会调用ScanParam_RefreshNotify,这个后面再说.hidDevGapStateCB主要是标志现在链接状态,并且决定是否广播,一般也不需要调整.hidDevParamUpdateCB指示更新完成,只是打印Log,也没什么关键信息.
现在回到HidDev_WriteAttrCB,当GATT_CLIENT_CHAR_CFG_UUID发生,使得HID_DEV_OPER_ENABLE,则会启动HidEmu_ProcessEvent任务的START_REPORT_EVT事件,然后以每秒一次发送数据.发送是调用hidDevSendReport(发送前需要判断链接状态,避免突然断开.),最终到HidDev_sendNoti,这里需要申请内存,填充要发的内容,调用GATT_Notification发送.
static uint8 HidDev_sendNoti(uint16 handle, uint8 len, uint8 *pData)
{
uint8 status;
attHandleValueNoti_t noti;
noti.pValue = HidDev_sendNoti(gapConnHandle, ATT_HANDLE_VALUE_NOTI, len, NULL, 0);
if (noti.pValue != NULL)
{
noti.handle = handle;
noti.len = len;
tmos_memcpy(noti.pValue, pData, len);
// 发送内容
status = GATT_Notification(gapConnHandle, ¬i, FALSE);
if (status != SUCCESS)
{
GATT_bm_free((gattMsg_t *)¬i, ATT_HANDLE_VALUE_NOTI);
}
}
else
{
status = bleMemAllocError;
}
return status;
}
记得前面说的ScanParam_RefreshNotify,他的结构也差不多,所以也明白了吧.
至此,蓝牙的开发最基础最简单部分已经说完了,不过,为了能加深记忆,最好还是再看看其他例子.