Web Serial API串口通信,实现web和electron扫码枪读取数据

王柳  金牌会员 | 2024-6-21 13:33:03 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 539|帖子 539|积分 1617


媒介

本文将报告Web Serial API简单应用,以扫码枪为示例,通过代码实现web端读取扫码枪扫码内容。

一、Serial API是什么?

Serial API为浏览器提供的一些接口函数,能实现与USB串行端口的硬件设备进行通信。诸如扫码枪或者打印机等
二、API使用步骤

   以扫码枪为例子实现读取扫码数据
  1.navigator.serial.requestPort()

   作用:弹窗让用户选择一个串行端口设备,类似授权
  1. if ('serial' in navigator) {
  2.                 try {
  3.                     const port = (await navigator.serial.requestPort()) || null;
  4.                         console.log('选择串口设备成功');
  5.                        
  6.                 } catch (e) {
  7.                         console.log('选择串口设备失败');
  8.                 }
  9.         } else {
  10.                 console.log('浏览器不支持serial API');
  11.         }
复制代码

2.port.open(options)

   作用:打开并连接到指定串行端口
  1. await port.value.open({ baudRate: 9600 })
复制代码
参数option属性:
baudRate:波特率,9600
dataBits:数据位,7或8
stopBits:停止位,1或2
parity:奇偶校验模式,默认none
flowControl:流控制类型,none或hardware
3.port.readable.getReader();

   作用:获取读取流对象
  4.reader.read()

   作用:开始读取流数据,开始后流处于锁住状态,不能进行其他操纵,直到取消读取
  返回2个字段value和done,value为读取的数据,类型为Uint8Array,需要通过其他方式转换为字符串
done为true表示没有正在接收数据或者已取消读取。要实现不间断监听扫码设备数据输入可以在while循环中不断执行reader.read()
如下所示:
  1. const reader = port.readable.getReader();
  2. while (true) {
  3.   const { value, done } = await reader.read();
  4.   if (done) {
  5.     // 解锁流
  6.     reader.releaseLock();
  7.     break;
  8.   }
  9.   //处理数据
  10.   console.log(value);//数据Uint8Array类型
  11. }
复制代码
2.也可以利用 port.readable.pipeTo和TextDecoderStream实现数据解析为字符串
  1. const textDecoder = new TextDecoderStream();
  2. const readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
  3. const reader = textDecoder.readable.getReader();
  4.    while (true) {
  5.       const { value, done } = await reader.read();
  6.       if (done) {
  7.        // 解锁流
  8.         reader.releaseLock();
  9.         break;
  10.       }
  11.       // 处理数据
  12.       console.log(value);//数据String类型
  13.    }
  14.   }
复制代码
while循环读取数据不是一次性读取的,有可能是多次,需要拼接和判断是否读取结束,一般以"\n"末端来判断。
二维码数据为{name:“张三”,age:20}通过测试三次获取到value数据如下:
  1. console.log(value.includes('\r'),'value',value)
复制代码

通过测试发现有时候一次性读完有时候分2次读完,不确定,但是读取完毕都是以"\r"末端,所以我们可以以此为判断依据改进如下:
  1. const textDecoder = new TextDecoderStream();
  2. const readableStreamClosed = port.readable.pipeTo(textDecoder.writable);
  3. const reader = textDecoder.readable.getReader();
  4. let data= ''; //扫码数据
  5.    while (true) {
  6.       const { value, done } = await reader.read();
  7.       if (done) {
  8.        // 允许稍后关闭串口
  9.         reader.releaseLock();
  10.         break;
  11.       }
  12.       // 处理数据
  13.       data=`${data}${value}`
  14.            if(value.includes('\r')){//读取结束
  15.                         console.log(`二维码数据:${data}`)
  16.            }
  17.    }
  18.   }
复制代码
4.reader.cancel()

   作用:取消读取流数据,调用后done 变为true跳出循环
  4.reader.releaseLock()

   作用:解锁流使其处于释放(非锁住)状态,调用后可以对流进行关闭等操纵,当流正在读取锁住状态不能调用,需先reader.cancel()
  5.port.close()

   作用:关闭串口,关闭前流要处于释放(非锁住)状态
  其他常见API:

port.write(data):数据写入串口
navigator.serial.getPorts():获取用户之前授权过的所有端口,返回port数组
完备代码

1.单个串口设备(无须切换,只连接一次)Demo:

  1. <template>  <button v-if="!port" @click="chooseSerial">选择扫码枪串口</button></template><script setup>import { ref } from 'vue';const port = ref(null);//选择串口设备const chooseSerial = async () => {        if ('serial' in navigator) {                try {                        port.value = (await navigator.serial.requestPort()) || null;                        console.log('选择串口成功');                        port.value && openSerial();                                        } catch (e) {                        console.log('选择串口失败');                }        } else {                console.log('浏览器不支持serial API');        }};//打开串口读取数据const openSerial = async () => {    await port.value.open({ baudRate: 9600 })
  2. ;        try {                const textDecoder = new TextDecoderStream();                port.value.readable.pipeTo(textDecoder.writable);                const reader = textDecoder.readable.getReader();                let data = ''; //扫码数据                while (true) {                        const { value, done } = await reader.read();                        if (done) {                                reader.releaseLock();                                break;                        }                        data=`${data}${value}`                        if(value.includes('\r')){//读取结束                 let codeData=data;                                 data="";//清空下次读取不会叠加                                 console.log(`二维码数据:${codeData}`)                                 //处理拿到数据逻辑                        }                }        } catch (error) {                console.error(error);                port.value = null;        } finally {                try {            reader.releaseLock();            //关闭串口            await port.value.close();        } catch (e) {}            }            port.value = null;};</script>
复制代码
运行:

说明:只要点击按钮选择一次串口设备,选择完隐藏按钮,后续就能不停扫码获取数据,假如中途扫码枪拔出来port属性将变成null,按钮重新显示,再次插入扫码枪需要在点击按钮选择一次串口设备。
2.多设备多串口切换(多把扫码枪或其他串口设备切换)Demo:

  1. <template>  <button @click="chooseSerial">选择扫码枪串口</button></template><script setup>import { ref } from "vue";const port = ref(null);let reader;let readableStreamClosed;let isReading = false; //是否正在读取//选择串口设备const chooseSerial = async () => {  if ("serial" in navigator) {    try {      port.value = (await navigator.serial.requestPort()) || null;      console.log("选择串口成功");      port.value && openSerial();    } catch (e) {      console.log("选择串口失败");    }  } else {    console.log("浏览器不支持serial API");  }};//延迟500msconst delay = () => {  return new Promise((resolve, reject) => {    setTimeout(() => {      resolve();    }, 500);  });};//打开串口读取数据const openSerial = async () => {  //正在读取或者锁住状态先取消流  if (isReading || port.value.readable?.locked) {    reader.cancel();    //延迟等待流取消跳出循环    await delay();  }    await port.value.open({ baudRate: 9600 })
  2. ;  try {    const textDecoder = new TextDecoderStream();    readableStreamClosed = port.value.readable.pipeTo(textDecoder.writable);    reader = textDecoder.readable.getReader();    let data = ""; //扫码数据    isReading = true    while (true) {      const { value, done } = await reader.read();      if (done) {        reader.releaseLock();        break;      }      data = `${data}${value}`;      if (value.includes("\r")) {        //读取结束        let codeData = data;        data = ""; //清空下次读取不会叠加        console.log(`二维码数据:${codeData}`);        //处理拿到数据逻辑      }    }  } catch (error) {    console.error(error);    port.value = null;  } finally {    try {      //捕获异常      await readableStreamClosed.catch(() => {});      //关闭读取      await port.value.close();    } catch (e) {}  }};</script>
复制代码
说明:每次点击按钮选择要连接的串口设备(扫码枪),判断并解锁关闭上一次连接后重新连接。通过reader.cancel取消上一次连接并解锁流,跳出循环后执行port…close关闭流。
   ps:通过navigator.serial.requestPort()授权过的串口设备会被本地记载,可通过navigator.serial.getPorts()取代规复上一次使用的串口设备
  三、electron使用

   electron支持Web Serial API使用,但是有点区别,在于无法弹窗让用户选择串口设备,此时需要在主历程里面授权并获取设备串口ID(portId)返给渲染历程
  主历程main/index.js
  1. function createWindow() {
  2.   // Create the browser window.
  3.   const mainWindow = new BrowserWindow({
  4.     width: 900,
  5.     height: 670,
  6.     show: false,
  7.     backgroundColor :'#ffffff',
  8.     autoHideMenuBar: true,
  9.     ...(process.platform === 'linux' ? { icon } : {}),
  10.     webPreferences: {
  11.       preload: join(__dirname, '../preload/index.js'),
  12.       sandbox: false
  13.     }
  14.   })
  15.   //处理serialApi
  16.     serialApiHandle(mainWindow)
  17.   }
  18. function serialApiHandle(mainWindow){
  19.   mainWindow.webContents.session.on('select-serial-port', (event, portList, webContents, callback) => {
  20.     // Add listeners to handle ports being added or removed before the callback for `select-serial-port`
  21.     // is called.
  22.     mainWindow.webContents.session.on('serial-port-added', (event, port) => {
  23.       console.log('serial-port-added FIRED WITH', port)
  24.       // Optionally update portList to add the new port
  25.     })
  26.     mainWindow.webContents.session.on('serial-port-removed', (event, port) => {
  27.       console.log('serial-port-removed FIRED WITH', port)
  28.       // Optionally update portList to remove the port
  29.     })
  30.     event.preventDefault();
  31.     console.log(portList,'portList')
  32.     if (portList && portList.length > 0) {
  33.     //默认返回第一个串口id
  34.       callback(portList[0].portId)
  35.     } else {
  36.       // eslint-disable-next-line n/no-callback-literal
  37.       callback('') // Could not find any matching devices
  38.     }
  39.   })
  40. //授权
  41.   mainWindow.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin, details) => {
  42.     if (permission === 'serial') {
  43.       return true
  44.     }
  45.     return false
  46.   })
  47. //授权
  48.   mainWindow.webContents.session.setDevicePermissionHandler((details) => {
  49.     if (details.deviceType === 'serial') {
  50.       return true
  51.     }
  52.     return false
  53.   })
  54. }
复制代码
渲染历程写法跟web端一样
ps:demo为方便默认返回第一个串口id,多个串口设备按需返回对应

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

王柳

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表