《HarmonyOS技术精讲-Basic Services Kit》USB服务:设备发现与通信

发布时间:2026/7/19 0:13:49

《HarmonyOS技术精讲-Basic Services Kit》USB服务:设备发现与通信
《HarmonyOS技术精讲-Basic Services Kit基础服务》USB服务设备发现与通信USB服务是HarmonyOS Basic Services Kit中经常被误解的一个模块。很多人以为接上U盘就能直接用fileIO读文件但应用沙箱机制并不允许直接访问外部存储。真正能用的方案是通过USB服务自行实现大容量存储协议——也就是手动发送SCSI命令把U盘当成一个“黑盒设备”来读写扇区。这个例子本身能跑但放到实际项目里还需要处理很多细节。本文将从设备枚举、权限请求、控制传输到批量传输一步步实现检测U盘并读取根目录文件列表的功能。它解决了什么问题USB大容量存储设备U盘、移动硬盘在HarmonyOS设备上可以通过USB Host模式直接通信。系统不提供“挂载成文件系统再读文件”的途径所以必须自己实现SCSI命令和文件系统解析。适用场景OTG U盘数据采集如读取传感器记录文件嵌入式设备固件升级定制化文件管理工具不适用场景有更好方案简单的文件复制建议用文件选择器Picker让用户主动选择文件打印机或键盘有专门的服务Kit环境说明DevEco Studio 版本DevEco Studio 6.1.0 及以上 HarmonyOS SDK 版本HarmonyOS 6.1.0(23) 及以上 目标设备手机支持USB OTG核心实现从枚举到读取文件列表第一步添加权限与导入模块在entry/src/main/module.json5中添加USB权限{module:{requestPermissions:[{name:ohos.permission.USB,reason:需要访问USB设备}]}}在代码中导入usbManagerimport{usbManager}fromkit.BasicServicesKit;第二步枚举USB设备并筛选存储设备usbManager.getDevices()返回所有已连接的USB设备。我们需要根据bInterfaceClass 0x08Mass Storage过滤。functiongetMassStorageDevices():ArrayusbManager.USBDevice{constdevicesusbManager.getDevices();conststorageDevices:ArrayusbManager.USBDevice[];for(constdeviceofdevices){for(constconfigofdevice.configs){for(constifaceofconfig.interfaces){if(iface.bInterfaceClass0x08){storageDevices.push(device);break;}}}}returnstorageDevices;}这段代码遍历每个设备的配置描述符找到接口类为Mass Storage的U盘设备。注意一个设备可能包含多个配置通常取第一个即可。第三步请求设备权限只有获取权限后才能与设备通信。requestDevicePermission会弹出系统弹窗asyncfunctionrequestUsbPermission(device:usbManager.USBDevice):Promisevoid{try{awaitusbManager.requestDevicePermission(device.deviceId);console.info(权限获取成功);}catch(error){console.error(权限获取失败JSON.stringify(error));throwerror;}}权限是一次性的应用被杀死后需要重新请求。第四步声明接口claimInterface声明接口后才能进行通信。通常在claimInterface后系统会锁定该接口其他应用无法同时使用。functionclaimMassStorageInterface(device:usbManager.USBDevice):usbManager.USBInterface|undefined{for(constconfigofdevice.configs){for(constifaceofconfig.interfaces){if(iface.bInterfaceClass0x08){usbManager.claimInterface(device.deviceId,iface.id,true);returniface;}}}returnundefined;}第二个参数force设为true表示强制声明如果接口已被其他应用使用会失败。实际项目中建议先检查再声明。第五步获取端点地址存储设备通常有一个批量输入端点设备到主机和一个批量输出端点主机到设备。需要从接口描述符中提取interfaceEndpointInfo{bulkInEndpoint:number;bulkOutEndpoint:number;}functiongetBulkEndpoints(iface:usbManager.USBInterface):EndpointInfo{letbulkIn-1;letbulkOut-1;for(constendpointofiface.endpoints){constdirection(endpoint.bEndpointAddress0x80)7;consttransferTypeendpoint.bmAttributes0x03;if(transferType0x02){// Bulkif(direction1){bulkInendpoint.bEndpointAddress;}else{bulkOutendpoint.bEndpointAddress;}}}if(bulkIn-1||bulkOut-1){thrownewError(未找到批量端点);}return{bulkInEndpoint:bulkIn,bulkOutEndpoint:bulkOut};}bEndpointAddress的低7位是端点号最高位表示方向1输入。第六步控制传输获取设备描述符可选虽然不通过控制传输也能通信但可以用来验证设备连接状态。这里演示获取设备描述符中的厂商ID和产品IDasyncfunctiongetDeviceDescriptor(device:usbManager.USBDevice):PromiseUint8Array{constbuffernewUint8Array(18);// 标准设备描述符长度18字节constresultusbManager.controlTransfer(device.deviceId,0x80,// bmRequestType: 设备到主机0x06,// bRequest: GET_DESCRIPTOR0x0100,// wValue: 设备描述符类型(1) 8 | 索引00x0000,// wIndex: 语言ID (0)buffer);if(result0){thrownewError(控制传输失败);}returnbuffer;}controlTransfer的返回值是实际传输的字节数。注意wValue高字节为描述符类型低字节为索引。第七步批量传输——发送SCSI INQUIRY命令SCSI命令通过批量输出端点发送CBWCommand Block Wrapper然后从批量输入端点接收CSWCommand Status Wrapper和数据。我们封装一个通用函数发送SCSI命令并接收响应interfaceSCSIResponse{status:number;// 0成功data:Uint8Array;}asyncfunctionsendSCSICommand(device:usbManager.USBDevice,endpointInfo:EndpointInfo,cdb:Uint8Array,// 命令描述块如INQUIRY的CDB为{0x12, 0, 0, 0, 36, 0}dataLength:number,direction:in|out):PromiseSCSIResponse{// 构建CBWconstcbwLength31;constcbwnewUint8Array(cbwLength);// CBW签名0x43425355cbw[0]0x55;cbw[1]0x53;cbw[2]0x42;cbw[3]0x43;// dCBWTag随便一个值cbw[4]0x01;cbw[5]0x00;cbw[6]0x00;cbw[7]0x00;// dCBWDataTransferLength小端constviewnewDataView(cbw.buffer);view.setUint32(8,dataLength,true);// bmCBWFlags0x00表示主机到设备0x80表示设备到主机cbw[12]directionin?0x80:0x00;// bCBWLUN通常0cbw[13]0;// bCBWCBLengthCDB长度cbw[14]cdb.length;// 填充CDBfor(leti0;icdb.length;i){cbw[15i]cdb[i];}// 发送CBW到批量输出端点usbManager.bulkTransfer(device.deviceId,endpointInfo.bulkOutEndpoint,cbw,5000);letdata:Uint8Array|undefined;if(dataLength0){if(directionin){constbufnewUint8Array(dataLength);constresultusbManager.bulkTransfer(device.deviceId,endpointInfo.bulkInEndpoint,buf,5000);databuf.slice(0,result);}else{// 写入数据到设备}}// 读取CSW13字节constcswnewUint8Array(13);constcswResultusbManager.bulkTransfer(device.deviceId,endpointInfo.bulkInEndpoint,csw,5000);if(cswResult13){thrownewError(CSW读取失败);}conststatuscsw[12];// bCSWStatus: 0成功, 1失败, 2阶段错误return{status,data:data??newUint8Array(0)};}注意bulkTransfer的返回值是实际传输字节数非负表示成功。如果超时或出错会抛异常。第八步读取设备信息INQUIRY与READ CAPACITY使用INQUIRY命令获取设备型号字符串asyncfunctionscsiInquiry(device:usbManager.USBDevice,endpoints:EndpointInfo):Promisestring{constcdbnewUint8Array([0x12,0x00,0x00,0x00,0x24,0x00]);constresponseawaitsendSCSICommand(device,endpoints,cdb,36,in);if(response.status!0){thrownewError(INQUIRY命令失败);}// 提取厂商信息8字节和产品信息16字节constvendorString.fromCharCode(...response.data.slice(8,16)).trim();constproductString.fromCharCode(...response.data.slice(16,32)).trim();return${vendor}${product};}asyncfunctionscsiReadCapacity(device:usbManager.USBDevice,endpoints:EndpointInfo):Promisenumber{constcdbnewUint8Array([0x25,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00]);constresponseawaitsendSCSICommand(device,endpoints,cdb,8,in);if(response.status!0){thrownewError(READ CAPACITY失败);}// 返回总扇区数大端constviewnewDataView(response.data.buffer);constblocksview.getUint32(0,false);constblockSizeview.getUint32(4,false);returnblocks*blockSize;}第九步读取根目录扇区并解析文件名这是一个“足够简单”的解析假定U盘是FAT32格式。我们读取第一个扇区LBA 0判断是否为MBR然后找到第一个分区再读取分区的引导扇区获取根目录信息。实际代码会很长这里给出关键函数asyncfunctionreadSector(device:usbManager.USBDevice,endpoints:EndpointInfo,lba:number,sectorSize:number):PromiseUint8Array{constcdbnewUint8Array(10);cdb[0]0x28;// READ10cdb[2](lba24)0xFF;cdb[3](lba16)0xFF;cdb[4](lba8)0xFF;cdb[5]lba0xFF;cdb[7](sectorSize8)0xFF;cdb[8]sectorSize0xFF;constresponseawaitsendSCSICommand(device,endpoints,cdb,sectorSize*512,in);if(response.status!0){thrownewError(READ10失败);}returnresponse.data;}functionparseFAT32RootDirectory(sectorData:Uint8Array):string[]{constfiles:string[][];// 每个目录项32字节for(leti0;isectorData.length;i32){if(sectorData[i]0x00)break;// 空条目结束if(sectorData[i]0xE5)continue;// 已删除constattrsectorData[i11];if(attr0x08)continue;// 卷标// 提取文件名8.3格式letname;for(letj0;j8;j){constcsectorData[ij];if(c0x20)break;nameString.fromCharCode(c);}constext[];for(letj8;j11;j){constcsectorData[ij];if(c0x20)break;ext.push(String.fromCharCode(c));}if(ext.length0)name.ext.join();files.push(name);}returnfiles;}

相关新闻

OpenClaw衍生:NullClaw、GoClaw、openJiuwen、LingClaw、MateClaw

OpenClaw衍生:NullClaw、GoClaw、openJiuwen、LingClaw、MateClaw

2026/7/19 0:13:49

关于OpenClaw的项目,请参考: OpenClaw相关项目:Awesome系列、PicoClaw、ClawWork、ClawX、MetaClaw、OpenClawInstaller、Clawra、MicroClaw、OneClawOpenClaw相关开源项目:ZeroClaw、IronClaw、MoltWorker、clawdbot-feishu、Lo…

3D 国漫 CG 仙侠风格女子角色三视图设计

3D 国漫 CG 仙侠风格女子角色三视图设计

2026/7/19 0:13:49

生成一张单人角色设定图,3D国漫CG仙侠风格,只展示一个角色。 角色名:绯羽仙子 身高:170cm 身材:身材比例匀称,高挑修长,九头身比例,肩颈线条优美,腰肢纤细,四肢修长,体态轻盈如鸿,曲线玲珑而不失仙气 年龄:约21岁 气质:明艳大气、自信从容,眼神中带着温柔而坚定…

TI AWR雷达芯片CBUFF模块Linklist配置与高速数据接口实战

TI AWR雷达芯片CBUFF模块Linklist配置与高速数据接口实战

2026/7/19 0:13:49

1. 项目概述:CBUFF模块在高速数据链路中的核心角色在雷达、工业视觉或者任何需要处理海量实时数据的嵌入式系统里,工程师们最头疼的问题之一,就是如何把ADC(模数转换器)采集到的原始数据,既快又稳地“搬”出…

TI Camera ISP CSI-2A寄存器配置实战:从原理到避坑指南

TI Camera ISP CSI-2A寄存器配置实战:从原理到避坑指南

2026/7/19 2:34:03

1. 项目概述与核心价值在嵌入式视觉系统的开发中,图像信号处理器(ISP)的CSI-2接收器配置,往往是决定项目成败的关键一步,也是驱动工程师最容易“踩坑”的地方。很多开发者拿到一份动辄数百页的寄存器手册,面…

文档型数据库核心优势与应用实践

文档型数据库核心优势与应用实践

2026/7/19 2:34:03

1. 文档型数据库的本质与核心优势文档型数据库与传统关系型数据库最根本的区别在于数据建模方式。想象一下关系型数据库就像Excel表格,数据必须严格按行列对齐;而文档型数据库则像Word文档,每个文档可以自由组织内容结构。这种差异带来了几个…

AI模型智能指数评估:从原理到工程实践的完整指南

AI模型智能指数评估:从原理到工程实践的完整指南

2026/7/19 2:34:03

最近在旧金山 101 公路旁,一块醒目的广告牌吸引了全球科技圈的关注——"Artificial Analysis 智能指数"正式登陆。这个事件不仅是一次品牌曝光,更是 AI 行业发展的一个重要标志。作为开发者,我们除了关注行业动态,更需要…

告别手动抄数据:WebPlotDigitizer让图表数字化变得如此简单

告别手动抄数据:WebPlotDigitizer让图表数字化变得如此简单

2026/7/19 2:34:03

告别手动抄数据:WebPlotDigitizer让图表数字化变得如此简单 【免费下载链接】WebPlotDigitizer Computer vision assisted tool to extract numerical data from plot images. 项目地址: https://gitcode.com/gh_mirrors/we/WebPlotDigitizer 你是否曾经为了…

【kernel】第4篇:kernel中的硬件访问技术之内存映射MMIO

【kernel】第4篇:kernel中的硬件访问技术之内存映射MMIO

2026/7/19 2:34:03

本文依据网络资料及工作与学习经验整理而成,如有错误请留言。 文章为付费内容,已加入原创侵权保护,禁止私自转载,违者必究。 文章所在专栏:《黑猫带你深入嵌入式系统:从裸机、U-Boot到内核与驱动》 文 / 黑猫学长 1 ioremap / iounmap ● 概述 在 Linux 系统中,无论是…

LM Studio Bionic实战:本地AI智能体部署与多模型协作指南

LM Studio Bionic实战:本地AI智能体部署与多模型协作指南

2026/7/19 2:24:03

LM Studio Bionic 发布:面向开放模型的 AI 智能体完整实战指南最近在本地部署 AI 大模型时,很多开发者都遇到了一个共同难题:如何在不依赖云端 API 的情况下,让多个开源模型协同工作,实现复杂的智能体任务?…

微服务进阶:服务网格与Istio

微服务进阶:服务网格与Istio

2026/7/19 0:03:49

541|微服务进阶:服务网格与Istio 上篇文章我们聊了微服务的基本概念和拆分方法。 但微服务多了,问题也多了: 服务之间怎么通信? 怎么监控每个服务的调用链路? 熔断、限流、重试怎么做? 安全认证怎么统一? 以前这些都靠SDK库(比如Hystrix、Feign),每个服务都要集成…

零售超级终端全域协同:ShareKit 碰一碰商品流转业务落地案例

零售超级终端全域协同:ShareKit 碰一碰商品流转业务落地案例

2026/7/19 0:03:49

一、零售门店全域协同业务背景与行业痛点 1.1 门店超级终端设备矩阵(连锁便利店/商超标准配置) 自助收银Kiosk一体机:顾客结算、自助核销优惠券、商品素材预览;运营折叠平板:店长后台商品上新、图片录入、活动配置、…

噗叽短视频界面分析

噗叽短视频界面分析

2026/7/19 0:03:49

1 和小红书类似,可以采用类似判断方法------------其实他比小红书好判断,因为他没有图片,控件位置几乎是固定的,都不用判断------------2 因为他没有点赞按钮------------而且几乎所有控件位置都是完全一样的,所以我就…

微服务进阶:服务网格与Istio

微服务进阶:服务网格与Istio

2026/7/19 0:03:49

541|微服务进阶:服务网格与Istio 上篇文章我们聊了微服务的基本概念和拆分方法。 但微服务多了,问题也多了: 服务之间怎么通信? 怎么监控每个服务的调用链路? 熔断、限流、重试怎么做? 安全认证怎么统一? 以前这些都靠SDK库(比如Hystrix、Feign),每个服务都要集成…

零售超级终端全域协同:ShareKit 碰一碰商品流转业务落地案例

零售超级终端全域协同:ShareKit 碰一碰商品流转业务落地案例

2026/7/19 0:03:49

一、零售门店全域协同业务背景与行业痛点 1.1 门店超级终端设备矩阵(连锁便利店/商超标准配置) 自助收银Kiosk一体机:顾客结算、自助核销优惠券、商品素材预览;运营折叠平板:店长后台商品上新、图片录入、活动配置、…

噗叽短视频界面分析

噗叽短视频界面分析

2026/7/19 0:03:49

1 和小红书类似,可以采用类似判断方法------------其实他比小红书好判断,因为他没有图片,控件位置几乎是固定的,都不用判断------------2 因为他没有点赞按钮------------而且几乎所有控件位置都是完全一样的,所以我就…