基于 Web HID API 的HID透传测试工具(纯前端)

前言

最近在搞HID透传 《STM32 USB使用记录:HID类设备(后篇)》 。

市面上的各种测试工具都或多或少存在问题,所以就自己写一个工具进行测试。目前来说纯前端方案编写这个工具应该是最方便的,这里放上相关代码。

项目地址与代码示例

项目地址:https://github.com/NaisuXu/HID_Passthrough_Tool

在这里插入图片描述

下面代码保存到 index.html 文件,双击打开文件即可使用:

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>HID Passthrough Tool</title><style>* {margin: 0;padding: 0;}html,body {height: 100vh;background-color: #f7f7ff;}div {height: calc(100% - 4rem);padding: 2rem;display: grid;grid-template-columns: 1fr 1fr 1fr;grid-template-rows: 2rem 1fr;row-gap: 1rem;column-gap: 2rem;}textarea {resize: none;overflow-y: scroll;overflow-x: hidden;padding: 1rem;}</style><script>if ("hid" in navigator) {// 浏览器支持hid} else {alert("Browser is not supported Web HID API.");}</script></head><body><div><button id="btnOpen">open</button><button id="btnSend">send</button><button id="btnClear">clear</button><textarea id="iptLog" readonly></textarea><textarea id="iptOutput">D0 D1 D2 D3 D4 D5 D6 D7</textarea><textarea id="iptInput" readonly></textarea></div><script>const btnOpen = document.querySelector("#btnOpen");const btnSend = document.querySelector("#btnSend");const btnClear = document.querySelector("#btnClear");const iptLog = document.querySelector("#iptLog");const iptOutput = document.querySelector("#iptOutput");const iptInput = document.querySelector("#iptInput");iptLog.value += "HID Passthrough Tool\n\n";iptLog.value += "This is an HID Passthrough device read/write Tool.\n\n";iptLog.value += "Device must have one collection with one input and one output.\n\n";iptLog.value += "For more detail see below:\n\n";iptLog.value += "https://github.com/NaisuXu/HID_Passthrough_Tool\n\n";iptLog.value += "《STM32 USB使用记录:HID类设备(后篇)》\nhttps://blog.csdn.net/Naisu_kun/article/details/131880999\n\n";iptLog.value += "《使用 Web HID API 在浏览器中进行HID设备交互(纯前端)》\nhttps://blog.csdn.net/Naisu_kun/article/details/132539918\n\n";let device; // 需要连接或已连接的设备let inputDataLength; // 发送数据包长度let outputDataLength; // 发送数据包长度// 打开设备相关操作btnOpen.onclick = async () => {try {// requestDevice方法将显示一个包含已连接设备列表的对话框,用户选择可以并授予其中一个设备访问权限const devices = await navigator.hid.requestDevice({ filters: [] });// const devices = await navigator.hid.requestDevice({//     filters: [{//         vendorId: 0xabcd,  // 根据VID进行过滤//         productId: 0x1234, // 根据PID进行过滤//         usagePage: 0x0c,   // 根据usagePage进行过滤//         usage: 0x01,       // 根据usage进行过滤//     },],// });// let devices = await navigator.hid.getDevices(); // getDevices方法可以返回已连接的授权过的设备列表if (devices.length == 0) {iptLog.value += "No device selected\n\n";iptLog.scrollTop = iptLog.scrollHeight; // 滚动到底部return;}device = devices[0]; // 选择列表中第一个设备if (!device.opened) {// 检查设备是否打开await device.open(); // 打开设备// 下面几行代码和我的自定义的透传的HID设备有关// 我的设备中有一个collection,其中有一个input、一个output// inputReports和outputReports数据是Array,reportSize是8// reportCount表示一包数据的字节数,USB-FS 和 USB-HS 设置的reportCount最大值不同if (device.collections[0].inputReports[0].items[0].isArray && device.collections[0].inputReports[0].items[0].reportSize === 8) {// 发送数据包长度必须和报告描述符中描述的一致inputDataLength = device.collections[0].inputReports[0].items[0].reportCount ?? 0;}if (device.collections[0].outputReports[0].items[0].isArray && device.collections[0].outputReports[0].items[0].reportSize === 8) {// 发送数据包长度必须和报告描述符中描述的一致outputDataLength = device.collections[0].outputReports[0].items[0].reportCount ?? 0;}iptLog.value += `Open device: \n${device.productName}\nPID-${device.productId} VID-${device.vendorId}\ninputDataLength-${inputDataLength} outputDataLength-${outputDataLength}\n\n`;iptLog.scrollTop = iptLog.scrollHeight; // 滚动到底部}// await device.close(); // 关闭设备// await device.forget() // 遗忘设备// 电脑接收到来自设备的消息回调device.oninputreport = (event) => {console.log(event); // event中包含device、reportId、data等内容let array = new Uint8Array(event.data.buffer); // event.data.buffer就是接收到的inputreport包数据了let hexstr = "";for (const data of array) {hexstr += (Array(2).join(0) + data.toString(16).toUpperCase()).slice(-2) + " "; // 将字节数据转换成(XX )形式字符串}iptInput.value += hexstr;iptInput.scrollTop = iptInput.scrollHeight; // 滚动到底部iptLog.value += `Received ${event.data.byteLength} bytes\n\n`;iptLog.scrollTop = iptLog.scrollHeight; // 滚动到底部};} catch (error) {iptLog.value += `${error}\n\n`;iptLog.scrollTop = iptLog.scrollHeight; // 滚动到底部}};// 发送数据相关操作btnSend.onclick = async () => {try {if (!device?.opened) {throw "Device not opened";}const outputData = new Uint8Array(outputDataLength); // 要发送的数据包let outputDatastr = iptOutput.value.replace(/\s+/g, ""); // 去除所有空白字符if (outputDatastr.length % 2 == 0 && /^[0-9a-fA-F]+$/.test(outputDatastr)) {// 检查长度和字符是否正确// 一包长度不能大于报告描述符中规定的长度const byteLength = outputDatastr.length / 2 > outputDataLength ? outputDataLength : outputDatastr.length / 2;// 将字符串转成字节数组数据for (let i = 0; i < byteLength; i++) {outputData[i] = parseInt(outputDatastr.substr(i * 2, 2), 16);}} else {throw "Data is not even or 0-9、a-f、A-F";}await device.sendReport(0, outputData); // 发送数据,第一个参数为reportId,填0表示不使用reportIdiptLog.value += `Send ${outputData.length} bytes\n\n`;iptLog.scrollTop = iptLog.scrollHeight; // 滚动到底部} catch (error) {iptLog.value += `${error}\n\n`;iptLog.scrollTop = iptLog.scrollHeight; // 滚动到底部}};// 全局HID设备插入事件navigator.hid.onconnect = (event) => {console.log("HID connected: ", event.device); // device 的 collections 可以看到设备报告描述符相关信息iptLog.value += `HID connected:\n${event.device.productName}\nPID ${event.device.productId} VID ${event.device.vendorId}\n\n`;iptLog.scrollTop = iptLog.scrollHeight; // 滚动到底部};// 全局HID设备拔出事件navigator.hid.ondisconnect = (event) => {device = null; // 释放当前设备iptLog.value += `HID disconnected:\n${event.device.productName}\nPID ${event.device.productId} VID ${event.device.vendorId}\n\n`;iptLog.scrollTop = iptLog.scrollHeight; // 滚动到底部};// 清空数据接收窗口btnClear.onclick = () => {iptInput.value = "";};</script></body></html>

注意事项

Web HID API 目前还处于实验性质,只有电脑上的Chrome、Edge、Opera等浏览器支持:
在这里插入图片描述

另外还需要注意的是从网页操作设备是比较容易产生安全风险的,所以这个API只支持本地调用或者是HTTPS方式调用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/76100.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MVVM中wpf设置控件是否可见

背景&#xff1a;某个页面基本一样&#xff0c;但是又有点不一样的地方&#xff0c;设置是否可见就可以实现页面的共用 样例Lable 步骤一&#xff1a;资源字典中添加转换器 <Window.Resources><ResourceDictionary><!--用来判断是否隐藏--><BooleanTo…

数据库实现学生管理系统

1.QT将数据库分为三个层次&#xff1a; 1> 数据库驱动层&#xff1a;QSqlDriver、QSqlDriverCreator、QSqlDriverCreatorBase、QSqlDriverPlugin 2> sql接口层&#xff1a;QSqlDatabase、QSqlQuery、QSqlRecord、QSqlError 3> 用户接口层&#xff1a;提供一些模型QSql…

linux非root安装特定版本的cuda

由于一些代码实现&#xff08;cuda写的外部扩展包&#xff09;对cuda版本要求比较高&#xff0c;因此&#xff0c;我在实验室linux系统下默认的cuda版本上&#xff0c;没办法编译扩展包。需要重新安装特定版本的cuda。 一. 首先&#xff0c;需要查看系统版本&#xff1a; lsb…

Python模板注入

概念 发生在使用模板引擎解析用户提供的输入时。模板注入漏洞可能导致攻击者能够执行恶意代码或访问未授权的数据。 模板引擎可以让&#xff08;网站&#xff09;程序实现界面与数据分离&#xff0c;业务代码与逻辑代码分离。即也拓宽了攻击面&#xff0c;注入到模板中的代码可…

LabVIEW利用人工神经网络辅助进行结冰检测

LabVIEW利用人工神经网络辅助进行结冰检测 结冰对各个领域构成重大威胁&#xff0c;包括但不限于航空航天和风力涡轮机行业。在起飞过程中&#xff0c;飞机机翼上轻微积冰会导致升力降低25%。研究报告称&#xff0c;涡轮叶片上的冰堆积可在19个月的运行时间内造成29MWh的功率损…

【微服务部署】四、Jenkins一键打包部署NodeJS(Vue)前端项目步骤详解

本文介绍使用Jenkins一键将NodeJS&#xff08;Vue&#xff09;前端项目打包并上传到生产环境服务器&#xff0c;这里使用的是直接打包静态页面&#xff0c;发送到远程服务器Nginx配置目录的方式&#xff0c;首先确保服务器环境配置好&#xff0c;安装Nginx&#xff0c;运行目录…

【线上问题】linux部署docker应用docker-compose启动报端口占用问题(感觉上没有被占用)

目录 一、问题说明二、排查过程 一、问题说明 1.linux服务器使用的不是root用户权限 2.docker应用服务没有关闭的情况下&#xff0c;做了些重装docker&#xff0c;重启docker等操作 3.docker-compose up -d然后docker logs查看日志报端口被占用 4.netstat -ntpl | grep 端口 也…

解决Ubuntu无法安装pycairo和PyGObject

环境&#xff1a;虚拟机Ubuntu20.04&#xff0c;vscode无法安装pycairo和PyGObject 虚拟机Ubuntu20.04&#xff0c;vscode中运行Anaconda搭建的vens 的Python3.8.10 首先在vscode中点击ctrlshiftp&#xff0c;选择Python3.8.10的环境&#xff0c;自动激活Python 最近在搞无人…

LeetCode474. 一和零

474. 一和零 文章目录 [474. 一和零](https://leetcode.cn/problems/ones-and-zeroes/)一、题目二、题解方法一&#xff1a;01背包方法二&#xff1a;01背包三维数组 一、题目 给你一个二进制字符串数组 strs 和两个整数 m 和 n 。 请你找出并返回 strs 的最大子集的长度&…

第 362 场 LeetCode 周赛题解

A 与车相交的点 数据范围小直接暴力枚举 class Solution { public:int numberOfPoints(vector <vector<int>> &nums) {unordered_set<int> vis;for (auto &p: nums)for (int i p[0]; i < p[1]; i)vis.insert(i);return vis.size();} };B 判断能否…

DC/DC开关电源学习笔记(六)开关电源电路集成及封装工艺

(六)开关电源电路集成及封装工艺 1.集成工艺2.模块化3.新方向开关电源电路集成及封装工艺涉及到将电源电路的各个组成部分集成在一个芯片中,并对芯片进行封装的工艺过程。 1.集成工艺 在集成电路设计中,通常会将开关电源的主要功能模块如开关管、变压器、滤波电容、电感等…

普中 51 单片机点亮LED灯

普中 51 单片机 &#xff08;STC89C52RC&#xff09; LED / IO 将LED1进行闪烁操作 为啥要进行延时操作&#xff1f;依据人的肉眼余晖效应&#xff0c; 延时时间不能太短&#xff0c;否则就无法观察到 LED 闪烁 #include "reg52.h" typedef unsigned int u16; //对…

React中父子组件参数传递讲解

文章目录 结合案例&#xff1a;github搜索案例1.父容器代码2.搜索Search子模块代码3.展示Lisi子模块代码 父子参数传递分析1.子(Search)传父(App)2.父(App)传子(List) 结合案例&#xff1a;github搜索案例 案例结果展示如下图 1.父容器代码 import React, { Component } fr…

自己封装的reduce、map、foreach、filter、bind等方法

自己封装一些方法 reduce 方法map 方法forEach方法filter方法的封装bind方法的封装 reduce 方法 在这个自定义的 reduce 方法中&#xff0c;我们遵循了原生 reduce 方法的工作原理。我们接受三个参数&#xff1a;数组 arr&#xff0c;回调函数 callback 和初始值 initialValue…

【GO语言基础】前言

系列文章目录 【Go语言学习】ide安装与配置 【GO语言基础】前言 【GO语言基础】变量常量 【GO语言基础】数据类型 文章目录 系列文章目录一、基础知识包和函数函数声明语法简洁性 括号成对出现GO常用DOS命令命名规则项目目录结构注释 总结 一、基础知识 包和函数 //声明本代…

Python中使用item()方法遍历字典的例子

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 这篇文章主要介绍了Python中使用item()方法遍历字典的例子, for…in这种是Python中最常用的遍历字典的方法了,需要的朋友可以参考下 Python字典的遍历方法有好几种&#xff0c;其中一种是for…in&#xff0c;这个我就…

记录docker 部署nessus

1、开启容器 docker run -itd --nameramisec_nessus -p 8834:8834 ramisec/nessus 2、登录 &#xff1a;注意是https https://ip8843 3、修改admin密码 #进入容器 docker exec -it ramisec_nessus /bin/bash#列出用户名 /opt/nessus/sbin/nessuscli lsuser#修改密码&a…

Swift使用编解码库Codable

Codable 是 Swift 引入的全新的编解码库&#xff0c;使开发者更方便的解析JSON 或 plist 文件。支持枚举、结构体和类。 Codable协议定义 Codable代表一个同时符合 Decodable 和 Encodable 协议的类型&#xff0c;即可解码且可编码的类型。 typealias Codable Decodable &a…

python模块之 aiomysql 异步mysql

mysql安装教程 mysql语法大全 python 模块pymysql模块&#xff0c;连接mysql数据库 一、介绍 aiomysql 是一个基于 asyncio 的异步 MySQL 客户端库&#xff0c;用于在 Python 中与 MySQL 数据库进行交互。它提供了异步的数据库连接和查询操作&#xff0c;适用于异步编程环境 …

计算机网络(一):基础篇

文章目录 1. TCP/IP网络模型有哪几层并做简要介绍&#xff1f;2. 键入网址到网页显示&#xff0c;期间发生了什么&#xff1f;3. 介绍一下域名解析的工作流程&#xff1f;4. MAC发送方和接收方如何确认&#xff1f;5. 路由器和交换机的区别&#xff1f;6. Linux系统是如何收发网…