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

pixle0 2024-06-25 10:33:04 阅读 57

文章目录

前言一、Serial API是什么?二、API使用步骤1.navigator.serial.requestPort()2.port.open(options)3.port.readable.getReader();4.reader.read()4.reader.cancel()4.reader.releaseLock()5.port.close()其他常见API:完整代码1.单个串口设备(无须切换,只连接一次)Demo:2.多设备多串口切换(多把扫码枪或其他串口设备切换)Demo:

三、electron使用


前言

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


一、Serial API是什么?

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

二、API使用步骤

以扫码枪为例子实现读取扫码数据

1.navigator.serial.requestPort()

作用:弹窗让用户选择一个串行端口设备,类似授权

if ('serial' in navigator) {

try {

const port = (await navigator.serial.requestPort()) || null;

console.log('选择串口设备成功');

} catch (e) {

console.log('选择串口设备失败');

}

} else {

console.log('浏览器不支持serial API');

}

在这里插入图片描述

2.port.open(options)

作用:打开并连接到指定串行端口

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()

如下所示:

const reader = port.readable.getReader();

while (true) {

const { value, done } = await reader.read();

if (done) {

// 解锁流

reader.releaseLock();

break;

}

//处理数据

console.log(value);//数据Uint8Array类型

}

2.也可以利用 port.readable.pipeTo和TextDecoderStream实现数据解析为字符串

const textDecoder = new TextDecoderStream();

const readableStreamClosed = port.readable.pipeTo(textDecoder.writable);

const reader = textDecoder.readable.getReader();

while (true) {

const { value, done } = await reader.read();

if (done) {

// 解锁流

reader.releaseLock();

break;

}

// 处理数据

console.log(value);//数据String类型

}

}

while循环读取数据不是一次性读取的,有可能是多次,需要拼接和判断是否读取结束,一般以"\n"结尾来判断。

二维码数据为{name:“张三”,age:20}通过测试三次获取到value数据如下:

console.log(value.includes('\r'),'value',value)

在这里插入图片描述

通过测试发现有时候一次性读完有时候分2次读完,不确定,但是读取完毕都是以"\r"结尾,所以我们可以以此为判断依据改进如下:

const textDecoder = new TextDecoderStream();

const readableStreamClosed = port.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')){ //读取结束

console.log(`二维码数据:${ data}`)

}

}

}

4.reader.cancel()

作用:取消读取流数据,调用后done 变为true跳出循环

4.reader.releaseLock()

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

5.port.close()

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

其他常见API:

port.write(data):数据写入串口

navigator.serial.getPorts():获取用户之前授权过的所有端口,返回port数组

完整代码

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

<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 });

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:

<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");

}

};

//延迟500ms

const 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 });

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

function createWindow() {

// Create the browser window.

const mainWindow = new BrowserWindow({

width: 900,

height: 670,

show: false,

backgroundColor :'#ffffff',

autoHideMenuBar: true,

...(process.platform === 'linux' ? { icon } : { }),

webPreferences: {

preload: join(__dirname, '../preload/index.js'),

sandbox: false

}

})

//处理serialApi

serialApiHandle(mainWindow)

}

function serialApiHandle(mainWindow){

mainWindow.webContents.session.on('select-serial-port', (event, portList, webContents, callback) => {

// Add listeners to handle ports being added or removed before the callback for `select-serial-port`

// is called.

mainWindow.webContents.session.on('serial-port-added', (event, port) => {

console.log('serial-port-added FIRED WITH', port)

// Optionally update portList to add the new port

})

mainWindow.webContents.session.on('serial-port-removed', (event, port) => {

console.log('serial-port-removed FIRED WITH', port)

// Optionally update portList to remove the port

})

event.preventDefault();

console.log(portList,'portList')

if (portList && portList.length > 0) {

//默认返回第一个串口id

callback(portList[0].portId)

} else {

// eslint-disable-next-line n/no-callback-literal

callback('') // Could not find any matching devices

}

})

//授权

mainWindow.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin, details) => {

if (permission === 'serial') {

return true

}

return false

})

//授权

mainWindow.webContents.session.setDevicePermissionHandler((details) => {

if (details.deviceType === 'serial') {

return true

}

return false

})

}

渲染进程写法跟web端一样

ps:demo为方便默认返回第一个串口id,多个串口设备按需返回对应



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。