《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;}

相关新闻

Agent之Skill:dashi-ppt-skill的简介、安装和使用方法、案例应用之详细攻略

Agent之Skill:dashi-ppt-skill的简介、安装和使用方法、案例应用之详细攻略

Agent之Skill:dashi-ppt-skill的简介、安装和使用方法、案例应用之详细攻略 目录 dashi-ppt-skill的简介 1、特点 dashi-ppt-skill的安装和使用方法 1、安装 2、使用方法 dashi-ppt-skill的案例应用 案例一:根据文档生成浏览器可编辑的演示文稿—…

2026/7/19 0:11:07阅读更多 →
Agent之skills:google/skills的简介、安装和使用方法、案例应用之详细攻略

Agent之skills:google/skills的简介、安装和使用方法、案例应用之详细攻略

Agent之skills:google/skills的简介、安装和使用方法、案例应用之详细攻略 目录 google/skills的简介 1、特点 2、核心内容 google/skills的安装和使用方法 1、安装 2、使用方法 Google Skills的案例应用 案例一:安装 Google Skills 仓库—安装 …

2026/7/19 0:11:07阅读更多 →
原汁机渣汁分离口感细腻,纯正果蔬原汁入口无残渣

原汁机渣汁分离口感细腻,纯正果蔬原汁入口无残渣

原汁机渣汁分离口感细腻,纯正果蔬原汁入口无残渣每天在厨房里折腾半小时,末了喝到嘴里还带着渣渣的果蔬汁,这样的体验确实让人头疼。直到接触蓝宝BP-YJ06多功能饮品机,才真正理解什么叫“渣汁分离口感细腻”——纯正果蔬原汁入口无…

2026/7/19 0:11:07阅读更多 →
Python核心模块分类与使用指南

Python核心模块分类与使用指南

1. Python常用模块全景概览作为一门通用编程语言,Python的强大之处很大程度上来自于其丰富的标准库和第三方模块生态。这些模块覆盖了从基础数据处理到人工智能开发的各个领域,让开发者能够快速构建功能完善的应用程序。在Python的实际开发中&#xff0c…

2026/7/19 22:02:46阅读更多 →
锂电池智能充电器使用指南:从原理到实践,避免常见误区

锂电池智能充电器使用指南:从原理到实践,避免常见误区

锂电池充电器,看似简单,但真正用好它,远不止“插上电、接上电池”这么简单。很多人第一次接触B4-BC4S或B4-800D这类带电压显示、支持即插即充的智能充电器时,往往会被其相对丰富的功能界面和偶尔出现的“ER”报错搞得有些不知所措…

2026/7/19 22:02:46阅读更多 →
Android工程师面试进阶:大厂核心考点与系统设计

Android工程师面试进阶:大厂核心考点与系统设计

1. 面试季突围:Android工程师的进阶指南又到了一年一度的"金三银四"招聘旺季,对于Android开发者来说,这既是机遇也是挑战。作为经历过多次大厂面试的过来人,我深知这个时期准备的重要性。2019年的Android技术栈已经发生…

2026/7/19 22:02:46阅读更多 →
User-Agent解析与浏览器检测实战指南

User-Agent解析与浏览器检测实战指南

1. User-Agent基础解析User-Agent(用户代理)是HTTP协议中的一个重要请求头字段,它向服务器传递客户端软件的相关信息。这个字符串包含了操作系统、浏览器类型及版本、渲染引擎等关键数据,服务器可以根据这些信息返回适配的内容。典…

2026/7/19 22:02:46阅读更多 →
Linux 命令行入门学习资料 day_6

Linux 命令行入门学习资料 day_6

GDB 监视点 watch一、什么是 watch?—— 变量的“监控摄像头” 之前学过的 break 是按位置暂停(比如某一行代码),而 watch 是按数据变化暂停。 watch 的作用: 告诉 GDB 持续监视某个变量(或表达式&#xff…

2026/7/19 22:02:46阅读更多 →
Go-Zero项目开发7: 构建社交服务RPC与好友/群业务实现

Go-Zero项目开发7: 构建社交服务RPC与好友/群业务实现

纲要 社交服务业务需求 好友关系管理(申请、处理、好友列表)群组管理(创建、申请、加入、退群等) 数据表设计 好友关系表 friend 的冗余存储方案好友申请表 friend_request群信息表、群成员表、群申请表 项目结构更新 apps/social…

2026/7/19 22:00:46阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/19 0:01:04阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/19 0:01:04阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/19 0:01:04阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/19 0:01:04阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/19 0:01:04阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/19 0:01:04阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/18 22:49:46阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/19 14:50:26阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/19 18:50:36阅读更多 →