Skip to content

electron+node-serialport串口通信

公司项目需求给客户开发一个能与电子秤硬件通信的Win7客户端,提供数据读取、处理和显示功能。项目为了兼容win7使用了electron 22.0.0版本,串口通信使用了serialport 12.0.0版本

serialport文档 electron文档

js
 //serialport的基本使用方法
 //安装 npm i serialport
 import { SerialPort } from 'serialport'
 SerialPort.list()//获取串口列表
 /** 创建一个串口连接
     path(必需):串口设备的路径。例如,'/dev/robot' 或 'COM1'。
     baudRate(必需):波特率,即每秒传输的比特数。常见值有 9600、19200、38400、57600、115200 等。
     autoOpen(可选):是否在创建对象时自动打开串口。默认为 true。如果设置为 false,你需要手动调用 port.open() 来打开串口。
     dataBits(可选):每字节的数据位数。可以是 5、6、7、8。默认值是 8。
     stopBits(可选):停止位的位数。可以是 1 或 2。默认值是 1。
     parity(可选):校验位类型。可以是 'none'、'even'、'odd'、'mark'、'space'。默认值是 'none'。
     rtscts(可选):是否启用硬件流控制(RTS/CTS)。布尔值,默认值是 false。
     xon(可选):是否启用软件流控制(XON)。布尔值,默认值是 false。
     xoff(可选):是否启用软件流控制(XOFF)。布尔值,默认值是 false。
     xany(可选):是否启用软件流控制(XANY)。布尔值,默认值是 false。
     highWaterMark(可选):用于流控制的高水位标记。默认值是 16384(16KB)。
     lock(可选):是否锁定设备文件,防止其他进程访问。布尔值,默认值是 true。
 **/
 const serialport = new SerialPort({ path: '/dev/example', baudRate: 9600 })
 
 serialport.open()//打开串口
 serialport.write('ROBOT POWER ON')//向串口发送数据
 serialport.on('data', (data) => {//接收数据
    //data为串口接收到的数据
 })

获取串口列表

js
 //主进程main.ts
 import { SerialPort, SerialPortOpenOptions } from 'serialport'
 //初始化先获取串口列表,提供给页面选择
 ipcMain.on('initData', async () => {
   const portList = await SerialPort.list()
   mainWindow.webContents.send('initData', {portList})
 })
js
 //渲染进程
 window.electron.ipcRenderer.once('initData', (_,{portList}) => {
   //获取串口列表后存入本地,登录页直接做弹窗给客户选择串口,配置波特率
   window.localStorage.setItem('portList', JSON.stringify(portList))
 })

串口选择

x.jpg

波特率配置

b.jpg

读取数据

公司秤和客户的秤串口配置不一样,所以做了model1和model2区分

js
 //主进程main.ts
 let P: SerialPort | undefined
 ipcMain.on('beginSerialPort', (_, { path, baudRate }) => {
   //区分配置
   const portConfig: SerialPortOpenOptions<AutoDetectTypes> =
     import.meta.env.VITE_MODE == 'model1'
       ? {
           path: path || 'COM1',
           baudRate: +baudRate || 9600, //波特率
           autoOpen: true,
           dataBits: 8
         }
       : {
           path: path || 'COM1',
           baudRate: +baudRate || 115200, //波特率
           autoOpen: true,
           dataBits: 8,
           stopBits: 1,
           parity: undefined
         }
   if (P) {
     P.close((error) => {
       if (error) {
         console.log('关闭失败:', error)
       } else {
         P = new SerialPort(portConfig)
         P?.write('SIR\r\n', 'ascii')//告诉秤端开始发送信息,具体看每个秤的配置,有的不需要
         P.on('data', (data) => {
           //接收到的data为Buffer类型,直接转为字符串就可以使用了
           mainWindow.webContents.send('readingData', data.toString())
         })
       }
     })
   } else {
     P = new SerialPort(portConfig)
     P?.write('SIR\r\n', 'ascii')
     P.on('data', (data) => {
       mainWindow.webContents.send('readingData', data.toString())
     })
   }
 })

解析数据

html
 <!--渲染进程解析数据-->
 <template>
   <div class="weight-con">
     <div class="weight-con-main">
       <div>
         <el-text class="wei-title" type="primary">毛<br />重</el-text>
       </div>
       <div class="weight-panel">
         <el-text id="wei-num">{{ weightNum!.toFixed(2) }}</el-text>
         <div class="weight-con-footer">当前最大称重:600公斤</div>
       </div>
       <div>
         <el-text class="wei-title" type="primary">公<br />斤</el-text>
       </div>
     </div>
   </div>
 </template>
 ​
 <script setup lang="ts">
 import { weightModel } from '@/utils/WeightReader'
 const emits = defineEmits(['zeroChange'])
 const weightNum = defineModel<number>()
 window.electron.ipcRenderer.on('readingData', (_, data: string) => {
   //渲染进程接收到主进程数据,根据环境变量解析数据
   weightNum.value = weightModel[import.meta.env.VITE_MODE](data)
   if (weightNum.value == 0) {
     emits('zeroChange')
   }
 })
 </script>
js
 //weightReader.ts 解析配置
 export type Mode = 'model1' | 'model2'
 let str = ''
 export const weightModel = {
   model1: (weightStr: string) => {
     const rev = weightStr.split('').reverse().join('')
     return +rev.replace('=', '')
   },
   module2: (weightStr: string) => {
     str += weightStr
     if (str.match(/S\s+[A-Za-z]\s*(-?\d*.?\d+)\s*kg/g)) {
       const num = str.match(/S\s+[A-Za-z]\s*(-?\d*.?\d+)\s*kg/m)![1]
       str = ''
       return Number(num)
     } else {
       return 0
     }
   }
 }

67cd365b0ec45d2ca47e4eb0b597c33f.gif

完活~下班!