《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/10/18 6:07:29 阅读更多 →
3D 国漫 CG 仙侠风格女子角色三视图设计

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

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

2026/10/17 8:07:40 阅读更多 →
TI AWR雷达芯片CBUFF模块Linklist配置与高速数据接口实战

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

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

2026/10/18 6:08:45 阅读更多 →

最新新闻

Metadef实践指南:CANN自定义算子开发的基石

Metadef实践指南:CANN自定义算子开发的基石

Metadef 这个名字在自定义算子开发圈子里,说实话一开始很容易被忽略。很多人刚接触 CANN 算子开发时,目光全放在 kernel 怎么实现、tiling 怎么切分上,等真正动手写第一个算子才发现,最卡脖子的不是计算逻辑,而是“这个…

2026/10/19 3:16:05 阅读更多 →
多平台OLED12864驱动例程详解:基于SSD1306的I2C/SPI显示方案

多平台OLED12864驱动例程详解:基于SSD1306的I2C/SPI显示方案

简介:OLED12864在多种单片机上的驱动例程包,面向嵌入式初学者与物联网项目开发者,旨在解决不同平台间屏幕驱动与引脚配置问题。例程全部使用软件模拟SPI,通过普通GPIO即可完成通信,适合无硬件SPI外设或希望灵活连线的场…

2026/10/19 3:16:05 阅读更多 →
双指针算法详解:从对撞、快慢到滑动窗口的实战应用

双指针算法详解:从对撞、快慢到滑动窗口的实战应用

双指针这个算法,我在面试和实际项目里都吃过不少亏,也尝过甜头。很多人一听到“双指针”觉得简单,无非就是两个下标来回动,可真到做题、写业务代码的时候,要么想不到用它,要么写出了死循环,要么…

2026/10/19 3:16:05 阅读更多 →
ffmpeg实战:B站m4s缓存视频合并成MP4指南

ffmpeg实战:B站m4s缓存视频合并成MP4指南

简介:面向需要将B站缓存视频中的audio.m4s与video.m4s合并为mp4的用户,此压缩包提供了一套完整的小型自动化方案。资源仅5个文件,含3个bat批处理脚本和2个Python脚本,压缩包体积仅2KB,非常轻量。Python脚本承担核心逻辑…

2026/10/19 3:16:05 阅读更多 →
PyTorch实现36种果蔬识别:从深度卷积网络到视频流推理的工程实践

PyTorch实现36种果蔬识别:从深度卷积网络到视频流推理的工程实践

做“36种水果和蔬菜识别”这个项目的时候,我其实并没有把它当成一个简单的分类任务来处理。PyTorch框架、深度卷积网络模型、照片连续识别和视频识别,这几个关键词拼在一起,意味着这个项目要同时兼顾精度、速度、部署灵活性和工程化落地。它不…

2026/10/19 3:16:05 阅读更多 →
2019年CSP-S初赛选择题1-5题精讲:进制、位运算、栈、二叉树与KMP陷阱全解析

2019年CSP-S初赛选择题1-5题精讲:进制、位运算、栈、二叉树与KMP陷阱全解析

2019年的CSP-S初赛,是我第一次以“CSP”而不是“NOIP”的名义走进考场。说实话,当时心里多少有点没底——毕竟是新名字的第一年,谁也不知道命题风格会不会大改。等我翻到选择题第一页,看到前5题的时候反而松了口气:进制…

2026/10/19 3:15:05 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →