8

从WCH CH579 BLE库学习蓝牙 #2

 1 year ago
source link: https://www.taterli.com/9193/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

从WCH CH579 BLE库学习蓝牙 #2

从WCH CH579 BLE库学习蓝牙 #2

前面还差比较关键的部分,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,他的结构也差不多,所以也明白了吧.

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


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK