从WCH CH579 BLE库学习蓝牙 #2

/ 0评 / 0

前面还差比较关键的部分,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, &noti, FALSE);
    if (status != SUCCESS)
    {
      GATT_bm_free((gattMsg_t *)&noti, ATT_HANDLE_VALUE_NOTI);
    }
  }
  else
  {
    status = bleMemAllocError;
  }

  return status;
}

记得前面说的ScanParam_RefreshNotify,他的结构也差不多,所以也明白了吧.

至此,蓝牙的开发最基础最简单部分已经说完了,不过,为了能加深记忆,最好还是再看看其他例子.

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注