# ukey-demo **Repository Path**: lixinsheng2/ukey-demo ## Basic Information - **Project Name**: ukey-demo - **Description**: 针对外设银行Ukey类产品,鸿蒙化样例 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2024-03-01 - **Last Updated**: 2024-08-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ukey-demo #### 介绍 鸿蒙化样例,针对外设银行Ukey类产品 #### 软件架构 该demo主要是将网银APP与外设ukey通信鸿蒙化 目前手机网银APP与外设ukey通信底层采用libusb开源库,而OpenHarmony采用USB DDK与外设进行底层通信 ![ukey鸿蒙化](ukey.PNG) #### USB DDK接口 [外设管理API](https://gitee.com/openharmony/docs/blob/76a9ff42f552fe095c8a7e0be9beb244fb1d11b5/zh-cn/application-dev/reference/apis-driverdevelopment-kit/js-apis-driver-deviceManager.md) [USB DDK开发指导](https://gitee.com/openharmony/docs/blob/76a9ff42f552fe095c8a7e0be9beb244fb1d11b5/zh-cn/application-dev/napi/usb-ddk-guidelines.md) #### 关键接口替换 | libusb接口 | 扩展外设接口(ArkTs、USB DDK) | 接口主要功能 | | ---------- | ---------- | ----------- | | libusb_get_device_list | queryDevices(ArkTs) | 查询设备列表 | | libusb_init | OH_Usb_Init | 初始化相应资源 | | libusb_get_device_descriptor | OH_Usb_GetDeviceDescriptor | 查询设备描述符 | | libusb_get_config_descriptor | OH_Usb_GetConfigDescriptor | 查询配置描述符 | | libusb_free_config_descriptor | OH_Usb_FreeConfigDescriptor | 释放配置描述符信息 | | libusb_claim_interface | OH_Usb_ClaimInterface | 卸载内核驱动占用接口 | | libusb_release_interface | OH_Usb_ReleaseInterface | 释放占用接口 | | libusb_bulk_transfer | OH_Usb_SendPipeRequest | 与设备通信,写入或输出 | | libusb_exit | OH_Usb_Release | 释放相应资源 | #### 关键代码讲解 ##### 1.查询设备列表 ``` query() { try { // 通过ArkTs接口查询设备列表 let devices = deviceManager.queryDevices(deviceManager.BusType.USB); for (let item of devices) { let device = item as deviceManager.USBDevice; hilog.info(0, 'testTag ui', 'querydevice id:' + device.deviceId + ',bustype:' + device.busType + ',vid:' + device.vendorId + ',pid:' + device.productId + ', des:' + device.description); // 通过设备的vid、pid过滤出目标设备 let index = VENDOR_ID_LIST.indexOf(device.vendorId) if (index >= 0) { return device; } } } catch (error) { hilog.error(0, 'testTag ui', `Failed to query device. Code is ${error.code}, message is ${error.message}`); } return null; } ``` ##### 2.初始化USB DDK并获取设备描述符 ``` static std::tuple GetDeviceInfo() { OH_LOG_INFO(LOG_APP, "devHandle:%{public}llu\n", g_devHandle); // ddk 初始化 int32_t ret = OH_Usb_Init(); if (ret != 0) { OH_LOG_ERROR(LOG_APP, "init ddk failed"); return {false, {}}; } struct UsbDeviceDescriptor devDesc; // 获取设备描述符 ret = OH_Usb_GetDeviceDescriptor(g_devHandle, &devDesc); if (ret != 0) { OH_LOG_ERROR(LOG_APP, "get dev desc failed:%{public}d", ret); return {false, {}}; } // 过滤目的设备 if (devDesc.idVendor != HT_VID && devDesc.idVendor != HT_VID1 && devDesc.idVendor != HT_DCARD) { OH_LOG_ERROR(LOG_APP, "device invalid :%{public}u, %{public}u", devDesc.idVendor, devDesc.idProduct); return {false, {}}; } return {true, devDesc}; } ``` ##### 3.获取配置描述符并占用接口 ``` static bool GetPipeInfo(HT_CDEV &cdev) { struct UsbDdkConfigDescriptor *config = nullptr; // 获取配置描述符 auto ret = OH_Usb_GetConfigDescriptor(g_devHandle, 1, &config); if (ret != 0) { OH_LOG_ERROR(LOG_APP, "get config desc failed:%{public}d", ret); return false; } // 从配置描述符中找到手写板相关的接口和端点 auto res = GetInterfaceAndEndpoint(config, cdev); if (!res) { OH_LOG_ERROR(LOG_APP, "GetInterfaceAndEndpoint failed"); return false; } // 释放配置描述符,防止内存泄露 OH_Usb_FreeConfigDescriptor(config); // 占用接口,同时也会卸载内核键盘驱动 ret = OH_Usb_ClaimInterface(g_devHandle, cdev.interface, &g_interfaceHandle); if (ret != 0) { OH_LOG_ERROR(LOG_APP, "claim interface failed%{public}d", ret); return false; } return true; } ``` 根据返回的配置描述符,遍历所有接口,过滤合适接口(需要选择接口类型可参考usb标准协议) ``` // 选择输入接口并为中断传输 #define IS_INT_IN(epDesc) ((((epDesc.bEndpointAddress >> 7) & 1) == 1) && ((epDesc.bmAttributes & 0x02) == 0x02)) // 获取手写板的接口和端点 static bool GetInterfaceAndEndpoint(const struct UsbDdkConfigDescriptor *config, struct HT_CDEV &cdev) { OH_LOG_INFO(LOG_APP, "bNumInterfaces: %{public}u\n", config->configDescriptor.bNumInterfaces); for (uint32_t intIdx = 0; intIdx < config->configDescriptor.bNumInterfaces; ++intIdx) { struct UsbDdkInterfaceDescriptor *intDesc = config->interface[intIdx].altsetting; uint32_t numSetting = config->interface[intIdx].numAltsetting; OH_LOG_INFO(LOG_APP, "intIdx: %{public}u numSetting: %{public}u\n", intIdx, numSetting); for (uint32_t setIdx = 0; setIdx < numSetting; ++setIdx) { uint32_t numEp = intDesc[setIdx].interfaceDescriptor.bNumEndpoints; struct UsbDdkEndpointDescriptor *epDesc = intDesc[setIdx].endPoint; OH_LOG_INFO(LOG_APP, "interface: %{public}u setIdx: %{public}u numEp: %{public}u\n", intDesc[setIdx].interfaceDescriptor.bInterfaceNumber, setIdx, numEp); if (numEp == 2 || numEp == 3) { OH_LOG_INFO(LOG_APP, "0->endpoint: %{public}u maxPktSize: %{public}u bmAttributes: %{public}u\n", epDesc[0].endpointDescriptor.bEndpointAddress, epDesc[0].endpointDescriptor.wMaxPacketSize, epDesc[0].endpointDescriptor.bmAttributes); OH_LOG_INFO(LOG_APP, "1->endpoint: %{public}u maxPktSize: %{public}u bmAttributes: %{public}u\n", epDesc[1].endpointDescriptor.bEndpointAddress, epDesc[1].endpointDescriptor.wMaxPacketSize, epDesc[1].endpointDescriptor.bmAttributes); cdev.interface = intDesc[setIdx].interfaceDescriptor.bInterfaceNumber; if (((epDesc[0].endpointDescriptor.bEndpointAddress >> 7) & 1) == 1) { cdev.in_endpoint = epDesc[0].endpointDescriptor.bEndpointAddress; cdev.in_maxPktSize = epDesc[0].endpointDescriptor.wMaxPacketSize; cdev.out_endpoint = epDesc[1].endpointDescriptor.bEndpointAddress; cdev.out_maxPktSize = epDesc[1].endpointDescriptor.wMaxPacketSize; } else { cdev.out_endpoint = epDesc[0].endpointDescriptor.bEndpointAddress; cdev.out_maxPktSize = epDesc[0].endpointDescriptor.wMaxPacketSize; cdev.in_endpoint = epDesc[1].endpointDescriptor.bEndpointAddress; cdev.in_maxPktSize = epDesc[1].endpointDescriptor.wMaxPacketSize; } OH_LOG_INFO(LOG_APP, "in->endpoint: %{public}u maxPktSize: %{public}u\n", cdev.in_endpoint, cdev.in_maxPktSize); OH_LOG_INFO(LOG_APP, "out->endpoint: %{public}u maxPktSize: %{public}u\n", cdev.out_endpoint, cdev.out_maxPktSize); return true; } } } return false; } ``` ##### 4.读取设备信息(非必须,根据需要调用) ``` static std::string* GetProductStringDescriptor(uint16_t iProduct) { uint8_t strDesc[100] = {0}; // 获取产品字符串描述符 uint32_t len = 100; struct UsbControlRequestSetup strDescSetup; strDescSetup.bmRequestType = 0x80; strDescSetup.bRequest = 0x06; strDescSetup.wValue = (0x03 << 8) | (iProduct); // desc Index strDescSetup.wIndex = 0x409; // language Id strDescSetup.wLength = len; // 像设备发送控制读,获取设备描述信息 auto ret = OH_Usb_SendControlReadRequest(g_interfaceHandle, &strDescSetup, UINT32_MAX, strDesc, &len); if (ret != 0) { OH_LOG_ERROR(LOG_APP, "send ctrl read failed%{public}d", ret); return nullptr; } // 将unicode形式的描述符转化为ASCII, 便于打印 std::string* desc = UnicodeToAsc(strDesc + 2, len - 2); OH_LOG_INFO(LOG_APP, "strDesc %{public}s\n", desc->data()); return desc; } ``` ##### 5.创建数据缓冲区,并与设备发送读/写请求 ``` UsbDeviceMemMap *devMmap = nullptr; // 创建用于存放数据的缓冲区 int32_t ret = OH_Usb_CreateDeviceMemMap(g_devHandle, bufferSize, &devMmap); if (ret != 0 || devMmap == nullptr) { OH_LOG_ERROR(LOG_APP, "OH_Usb_CreateDeviceMemMap failed"); return result; } struct command_block_wrapper send_cbw; memset(&send_cbw, 0, sizeof(send_cbw)); send_cbw.dCBWSignature = 0x43425355; send_cbw.dCBWTag = 0x876aa008; send_cbw.bCBWCBLength = 0x0A; send_cbw.dCBWDataTransferLength = 5; send_cbw.bmCBWFlags = 0; send_cbw.bCBWLUN = 0; send_cbw.CBWCB[0] = 0xFF; send_cbw.CBWCB[1] = 0x02; memcpy(devMmap->address, &send_cbw, sizeof(send_cbw)); devMmap->bufferLength = sizeof(send_cbw); // 向设备写入数据 ret = writeDataToUkey(cdev, devMmap); if (ret != 0) { OH_LOG_ERROR(LOG_APP, "pipe ddk send data transfer1 ERROR, %{public}d", ret); return result; } OH_LOG_ERROR(LOG_APP, "pipe ddk send data transfer1 OK"); ``` // 向设备发送写请求 ``` static int32_t writeDataToUkey(HT_CDEV cdev, UsbDeviceMemMap *devMmap) { struct UsbRequestPipe pipe; pipe.interfaceHandle = g_interfaceHandle; pipe.endpoint = cdev.out_endpoint; pipe.timeout = UKEY_CMD_TIMEOUT; // 等待5s // 向设备写入数据 int32_t ret = OH_Usb_SendPipeRequest(&pipe, devMmap); if (ret != 0) { OH_LOG_ERROR(LOG_APP, "OH_Usb_SendPipeRequest ERROR, %{public}d", ret); } else { OH_LOG_ERROR(LOG_APP, "OH_Usb_SendPipeRequest OK, size:%{public}d", devMmap->transferedLength); } return ret; } ``` // 向设备发送读请求 ``` static int32_t readDataFromUkey(HT_CDEV cdev, UsbDeviceMemMap *devMmap) { struct UsbRequestPipe pipe; pipe.interfaceHandle = g_interfaceHandle; pipe.endpoint = cdev.in_endpoint; pipe.timeout = UKEY_CMD_TIMEOUT; // 等待5s // 向设备写入数据 int32_t ret = OH_Usb_SendPipeRequest(&pipe, devMmap); if (ret != 0) { OH_LOG_ERROR(LOG_APP, "OH_Usb_SendPipeRequest ERROR, %{public}d", ret); } else { OH_LOG_ERROR(LOG_APP, "OH_Usb_SendPipeRequest OK, size:%{public}d", devMmap->transferedLength); PrintBuffer(devMmap->address, devMmap->transferedLength); } return ret; } ``` ##### 6.释放资源(重要,使用完后一定要释放资源,否则可能会引起资源泄露) ``` OH_Usb_DestroyDeviceMemMap(devMmap); OH_Usb_DestroyDeviceMemMap(devMmap1); ret = OH_Usb_ReleaseInterface(g_interfaceHandle); if (ret != 0) { OH_LOG_ERROR(LOG_APP, "OH_Usb_ReleaseInterface ERROR, %{public}d", ret); return result; } OH_Usb_Release(); ```