前端与硬件交互的三个api

  • 文档说明
  • web usb Api
  • web Serial Api
  • web Hid Api
  • web Hid vue技术栈 使用
  • web Hid 原生js 使用


文档说明

好久没有更新文档了,闲下来没事的时候,更新一篇最近开发的一个与硬件交互的文档心得。

请注意看下面的描述,可能对你选择哪种开发是比较有用处的
该文档主要讲述了,前端开发人员也是可以直接通过一些web Api与硬件进行交互。
webSerial (串口,下文中说到的串口,就是这个Api) 与 webHid(hid,同串口一样,下文用hid替代)这两个Api是基于webUsb进一层的封装,这些都是网页封装好的,只需要知道,现在与硬件交互的有这三种Api即可。串口 和 hid 这两个都是需要usb线连接硬件的,所以说,这两个Api属于usb的分支,为什么分出这两个分支,可能是因为更多的硬件是使用这两个通信的比较多。

web usb Api

直接通过usb数据线进行数据传输,写入到对应的硬件中。具体Api文档详见链接: MDN webUsb

web Serial Api

前端通过连接到 串口设备 进行交互,该api是硬件的交互一种实现方式,小编在选技术的时候,考虑到硬件同事他们选择的是hid设备,因此没有选择该方法。下面是 串口api 的文档 链接: MDN webSerial 后续,小编会把使用 串口设备 开发的文档发出来

web Hid Api

本文最主要的部分来了,先上链接: MDN webHid

小编采用的技术是vue,如果使用原生方法的请略过这一段。

web Hid vue技术栈 使用

对于vue而言,其实就是将原生js的代码进行转成vue的代码即可。话不多说,先上代码看看:

async mounted() {
    // 存储一下webAPi,挂载的时候就先把对应的api获取到
    this.navigator = window.navigator
    navigator.hid.addEventListener('connet', this.connectRFIDs)
    navigator.hid.addEventListener('disconnet', this.disconnectRFIDs)
 },
 methods: {
  // 由于HID这个api 并没有在所有的浏览器上面适用,所以需要先判别一下对应浏览器
    ReadCrad() {
      const browserName = this.getBrowserName()
      const browsertype = this.getBrowserNameVersion().split(' ')[1].split('.')[0]
      if (!('hid' in this.navigator) && !['Edge', 'Opera', 'Chrome'].includes(browserName)) {
        this.$message({
          message: '当前浏览器不支持发卡,请换成最新谷歌或者Edge浏览器再使用',
          type: 'error',
          duration: 30 * 1000,
          showClose: true
        })
        return false
      }
      if (['Edge', 'Opera', 'Chrome'].includes(browserName)) {
        if (['Edge', 'Chrome'].includes(browserName) && browsertype < 89) {
          this.$message({
            message: `当前${browserName}浏览器版本过低,请更新浏览器版本`,
            type: 'error',
            duration: 3 * 1000
          })
          return false
        }
        if (browserName === 'Opera' && browsertype < 75) {
          this.$message({
            message: `当前${browserName}浏览器版本过低,请更新浏览器版本`,
            type: 'error',
            duration: 3 * 1000
          })
          return false
        }
      }
      // 走完判断,走链接设备
      this.connectRFID()
    },
    async connectRFID() {
        // 下面的就是要调用webHid的api获取对应选择的设备 
        // 下面的代码是,展示所有的连接pc端有关HID设备列表,如下:
        // const devices = await this.navigator.hid.requestDevice({ filters: [] })
        // filters: [] 数组里面的都是一个个对象,对象形式: {vendorId: '', productId: ''}, vendorId是厂商id,productId是产品的id,一般硬件协议里面都会有,没有的话,找一下硬件同事问一下即可
        const devices = await this.navigator.hid.requestDevice({ filters: [{vendorId: '', productId: ''}] })
        // 返回的是一个数组,没有选择则是0
        if (devices.length === 0) {
          // 没有选择
          this.$message({
            message: '写自己对应的提示', // 我这边是与读卡器进行交互,我会给其提示:还未选择任何设备,请选择设备
            type: 'error',
            duration: 3 * 1000
          })
          return false
        }
        // 存储一下这个设备,也可以不存储,但是使用起来,需要你每次都要找数据,小编建议:最好存一下
        this.device = devices[0]
        // 打开对应的设备
        await this.device.open()
        // 走到这里,这个设备就打开了,下面完全就是与硬件的交互,由于web端Hid的交互都是异步进行,所以还是需要了解一点 Promise
        if (this.device.opened) {
          this.connect = true
          // 链接到对应的读卡器,然后给读卡器发送指令,下面这些都是为了测试,下面的命令说明的是,让读卡器蜂鸣100ms
          // 为了代码的简洁性,最好还是将requestRFID方法封装到mixin里面去
          await this.requestRFID('Sys_SetBuzzer', '0001', '0A')
        } else {
          this.connect = false
          this.$message({
            message: '读卡器初始化失败,请重新点击',
            type: 'error',
            duration: 3 * 1000
          })
          return false
        }
    },
    // 断开重新连接上的时候会触发这个方法
    async connectRFIDs(e) {
      // 之所以判断,是因为有可能,有可能设备之前关闭了,你需要连接上重新打开,但是界面上面没有任何变动
      if (!e.device.opened) {
        this.device = e.device
        await this.device.open()
      }
    },
    async disconnectRFIDs(e) {
      // 这是断开链接时,将没有关闭的设备关闭
      if (e.device.opened) {
        this.device = null
        await e.device.close()
      }
    },
    // 这个方法就是控制与硬件的交流
    async requestRFID(command, lengthWord = '0001', data = '', len = 0, type = 'string') {
    // 这是一个假的数据,因为数据上面,没有任何设备是相同的
      const requestData = 'AACC00000001000A11'
      // 自己封装一下,告诉自己成功还是失败,因为硬件返回的数据都是16进制的数字,所以,转换成自己看的懂的数据
      const resolveData = { message: '', data: '', code: 200, type: 'success' }
      // 这一步是给设备发送数据
      await this.device.sendReport(0, _that.fromHexString(requestData))
      return new Promise(async(resolve, reject) => {
      // 这里是监听设备返回给咱们的数据。主要还是oninputreport这个方法调用,每次发送指令最好是写一下这个方法,因为,硬件那边接受指令是异步的,每次返回,你会不知道是发送的那个指令返回的。
        this.device.oninputreport = inputreportRFIDs
        function inputreportRFIDs(e) {
        // 这里是hid设备返回给的数据,切割的数据是协议规定好的,所以,还是看一下对应的协议,再写下面的判断逻辑,然后再返回对应的数据
          const result = _that.toHexString(new Uint8Array(e.data.buffer)).slice(16, 18)
          if (result.toUpperCase() === '00') {
            resolve(resolveData)
          } else {
            reject(resolveData)
          }
        }
      })
    },
    // 16进制字符串转unit8Array数组,数组中一个位置就是一个字节
    fromHexString(hexString) {
      return new Uint8Array(hexString.toString().match(/.{1,2}/g).map(byte => parseInt(byte, 16)))
    },
    // unit8Array 数组,转换成16进制字符串,2 个长度的字符 = 1 个字节
    toHexString(bytes) {
      return bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '')
    }
 },
 // 切换页面的时候,将其读卡器置空
  async beforeDestroy() {
    // this.device 是你选择的设备
    if (this.device && this.device.opened) {
      await this.device.close()
      this.device = null
      this.connect = false
    }
  },

代码其实就这么多,更多的是业务上的处理,上面的代码完全可以复制使用,有一些点需要注意一下。替换成自己的东西即可。由于硬件那边接收的是16进制数据,所以需要将咱们知道的数据进行转换,完全转换成16进制数据,再传给硬件那边即可。同理,返给我们的也是16进制数据,不过网页帮咱们处理成了数组,所以咱们只需要再将数组转换成16进制,然后就可以知道我们所需的数据了。

web Hid 原生js 使用

对于原生js,其实,更多的是操作DOM,所以会有很多的方法,但是思想是一样的。都是根据业务而言来发展的,下面就举例看一下,这个可以更好的用于参考,看看你在使用到你的技术栈的时候,有哪些不一致

<button id="id" class="btn first" onclick="onClick();">初始化卡片</button>
<button onclick="onReadClicks();">读卡</button>
async function onClick() {
     let deviceFilter = { vendorId: 0x0416, productId: 0x8020 };
    let requestParams = { filters: [deviceFilter] };
    const dataas = 'AACC0000000100010A11'
    function handleConnectedDevice(e) {
      console.log("Device connected: " + e.device.productName);
    }
    function handleDisconnectedDevice(e) {
      console.log("Device disconnected: " + e.device.productName);
    }
    navigator.hid.addEventListener("connect", handleConnectedDevice);
    navigator.hid.addEventListener("disconnect", handleDisconnectedDevice);
    if (!connect) {
        navigator.hid.requestDevice(requestParams).then(async (devices) => {
          if (devices.length === 0) return;
          connect = true
          device = devices[0]
          await device.open()
          const vanderId = device.vendorId
          device.sendReport(0, fromHexString(dataas)).then((res) => {
            console.log(res, '数据')
          });
          device.addEventListener("inputreport", handleInputReport);
        });
      } else {
        device.sendReport(0, fromHexString(dataas)).then((res) => {});
        device.addEventListener("inputreport", handleInputReport);
      }
    }
    // 这个是硬件返回的数据
    function handleInputReport(e) {
      console.log(e.device.productName + ": got input report " + e.reportId);
      console.log(toHexString(new Uint8Array(e.data.buffer)))
      const result = toHexString(new Uint8Array(e.data.buffer)).slice(16,18)
      if (result === '00') { // 成功

      } else { // 失败

      }
    }
    // 读卡
    async function onReadClicks() {
    // fromHexString(data),其中data就是你发给硬件的数据
      device.sendReport(0, fromHexString('AACC00001002000012')).then((res) => {});
      device.addEventListener("inputreport", handleInputReport);
    }
    const fromHexString = hexString => {
      return new Uint8Array(hexString.toString().match(/.{1,2}/g).map(byte => parseInt(byte,16)))
    };
    const toHexString = bytes => bytes.reduce((str,byte) => str + byte.toString(16).padStart(2,'0'),'');

以上就是 web前端与hid设备进行交互的全部内容了,由于,文章是更多的文字和代码,所以并没有完全的讲述,如果有不懂的,可以留言沟通一下

如果可以的话,点点关注哦~,小编会更努力的更新新的文档哦