《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

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

2026/8/1 14:30:09 阅读更多 →
3D 国漫 CG 仙侠风格女子角色三视图设计

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

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

2026/8/1 14:28:19 阅读更多 →
TI AWR雷达芯片CBUFF模块Linklist配置与高速数据接口实战

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

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

2026/8/1 6:18:46 阅读更多 →

最新新闻

LC76G GNSS模块实战:从硬件拆解到软件优化,解决定位漂移与精度问题

LC76G GNSS模块实战:从硬件拆解到软件优化,解决定位漂移与精度问题

1. 从“找不到模块”到“定位全世界”:LC76G GNSS模块的硬核拆解最近在调试一个物联网项目,后台日志里频繁出现各种“ModuleNotFoundError”和“Cannot find module”,从Python的opencv到Node.js的rollup/rollup-linux-x64-gnu,看…

2026/8/2 1:41:24 阅读更多 →
资深架构师的底层修炼与职业思考:地基坚如磐石,方能平地起高楼

资深架构师的底层修炼与职业思考:地基坚如磐石,方能平地起高楼

资深架构师的底层修炼与职业思考:地基坚如磐石,方能平地起高楼 在头部电商大厂带架构团队、主持核心交易与基础架构重构的这些年来,我救过无数次生产环境的火,也带过很多优秀的年轻工程师。 在繁重的高强度架构工作之余&#xf…

2026/8/2 1:41:24 阅读更多 →
基于RK3588与HDMI接口驱动10.4寸QLED电容触摸屏的嵌入式系统实战

基于RK3588与HDMI接口驱动10.4寸QLED电容触摸屏的嵌入式系统实战

1. 项目概述:10.4HP-CAPQLED,一块“能看又能摸”的专业屏最近在捣鼓一个嵌入式显示项目,核心是一块型号为“10.4HP-CAPQLED”的工业级显示屏。光看这个型号,信息量就挺大:10.4英寸的尺寸,HP(Hig…

2026/8/2 1:41:24 阅读更多 →
React 19 渲染并发陷阱:从 Fiber 树原理看组件边界设计

React 19 渲染并发陷阱:从 Fiber 树原理看组件边界设计

React 19 渲染并发陷阱:从 Fiber 树原理看组件边界设计 很多前端开发者在升级到 React 18 或 React 19 后,以为开启了并发模式(Concurrent Mode)就能自动获得流畅的性能体验。但在实际工程项目中,不少团队发现升级后页…

2026/8/2 1:41:24 阅读更多 →
基于Jetson Orin的UGV机器人AI套件:从硬件集成到SLAM与目标跟踪实战

基于Jetson Orin的UGV机器人AI套件:从硬件集成到SLAM与目标跟踪实战

1. 项目缘起:为什么选择UGV Rover PT Jetson Orin AI套件?最近在折腾一个户外自主巡检的项目,核心需求是让一台小车(UGV)能自己规划路线、识别路障、甚至完成一些简单的抓取动作。选型阶段,市面上各种开发板…

2026/8/2 1:41:24 阅读更多 →
UE5 ALS-Community角色动画系统:架构解析、核心功能与进阶定制指南

UE5 ALS-Community角色动画系统:架构解析、核心功能与进阶定制指南

1. 项目概述:为什么ALS-Community是UE5角色动画的“瑞士军刀”?如果你正在用Unreal Engine 5做角色相关的项目,无论是开放世界、动作冒险还是RPG,大概率都绕不开一个灵魂拷问:角色的移动、跳跃、攀爬、战斗这些动画&am…

2026/8/2 1:40:23 阅读更多 →

日新闻

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

1. 从水管网络到最大流:一个核心问题的诞生想象一下,你是一个城市供水系统的总工程师。你的城市有多个水源(水库),需要通过一个复杂的地下管道网络,将水输送到各个居民区。每条管道都有其最大通水能力&…

2026/8/2 0:00:38 阅读更多 →
基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

2026/8/2 0:00:38 阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

MATLAB xcorr函数详解:从互相关原理到四大实战应用

1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同&#xff…

2026/8/2 0:00:38 阅读更多 →

周新闻

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

1. 从水管网络到最大流:一个核心问题的诞生想象一下,你是一个城市供水系统的总工程师。你的城市有多个水源(水库),需要通过一个复杂的地下管道网络,将水输送到各个居民区。每条管道都有其最大通水能力&…

2026/8/2 0:00:38 阅读更多 →
基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

2026/8/2 0:00:38 阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

MATLAB xcorr函数详解:从互相关原理到四大实战应用

1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同&#xff…

2026/8/2 0:00:38 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/1 0:00:48 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/1 0:00:48 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/2 0:23:22 阅读更多 →