项目名称 | Node SerialPort |
---|---|
主页 | https://serialport.io/ |
开源协议 | MIT |
github | https://github.com/serialport/node-serialport |
github Star | 5.2k stars(2022-6-30) |
github Fork | 989 forks(2022-6-30) |
github git地址 | https://github.com/serialport/node-serialport.git |
通过一个例子来介绍一下Node的串口通信库【SerialPort】。同时也是Electron应用程序与硬件设备通过串口进行通信的例子。文末附有完整代码。本例不会涉及任何UI框架,只使用HTML、CSS、JavaScript编写代码。重点关注就是如何进行通信。
硬件:
软件:
本例子主要有以下功能:
请先看下方的程序示意图,讲解程序将根据示意图从上往下说明如何实现的。
先放上预加载脚本preload.js
的代码,此脚本是渲染器进程与主进程IPC通信的关键。
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('aeIo', {
/**
* 获取串口设备列表
*/
async list() { return await ipcRenderer.invoke('aeio-list') },
/**
* 打开串口设备
* @param {串口地址} path
* @param {波特率} baudRate
*/
open: (path, baudRate = 9600) => { ipcRenderer.send('aeio-open', path, baudRate) },
/**
* 关闭设备
*/
close:()=>{ipcRenderer.send('aeio-close')},
/**
* 发送数据
* @param {向arduino发送的数据} data
*/
write: (data) => { ipcRenderer.send('aeio-write', data) },
/**
* 从arduino接收数据
* @param {*} callback
*/
read: (callback) => { ipcRenderer.on('aeio-read', callback) },
/**
* 接收消息 这个消息内容大部分都是main.js发出的,主要是用作提示
* @param {*} callback
*/
message: (callback) => { ipcRenderer.on('aeio-message', callback) },
});
index.html
<div><button id="mBtnList" onclick="getList()">获取串口设备列表</button>设备列表:<span id="dListInfo"></span></div>
<script>
/**
* 向主进程发送获取串口设备的消息,并等待串口设备集合
**/
function getList() {
window.aeIo.list().then((list) => {
let dListInfo = document.getElementById('dListInfo');
dListInfo.innerHTML = JSON.stringify(list);
});
}
</script>
main.js
/**
* 获取串口列表
*/
ipcMain.handle('aeio-list', (ev, args) => {
return SerialPort.list().then((info, err) => {
if (err) {
sendToRenderer(error(`设备列表获取失败:${err}`))
return [];
}
//将集合返回给界面
return info;
})
})
SerialPort.list()
方法会返回已连接的串口设备。返回的是一个集合。返回的数据集合中,每一个代表串口设备的对象都有一个path
属性,这个属性的值就是要传给打开方法。
index.html
<div>串口设备:<input type="text" id="mInputDevPath" placeholder="请从设备列表上复制" width="200px" />波特率:<input type="text"
id="mInputDevPort" placeholder="波特率" width="200px" value="115200" /><button id="mBtnOpen"
onclick="openDevice()">打开设备</button><button id="mBtnClose" onclick="closeDevice()">关闭设备</button></div>
<script>
/**
* 打开设备
**/
function openDevice() {
let mInputDevPath = document.getElementById('mInputDevPath').value;// path 设备路径 我测试的时候是COM4
let mInputDevPort = document.getElementById('mInputDevPort').value;// 波特率 我设置的是115200 如果这个和arduino设置的不一致是无法通信的
window.aeIo.open(mInputDevPath, mInputDevPort);
}
/**
* 关闭设备
**/
function closeDevice() {
window.aeIo.close();
}
</script>
main.js
/**
* 打开设备
*/
let receiveData='';//接收到的数据
ipcMain.on('aeio-open', (ev, ...args) => {
console.log(args);
const options = {
path: args[0],
baudRate: parseInt(args[1]),
}
//实例化设备之后此时已经打开设备了
serialPort = new SerialPort(options);
//监听数据,当单片机发送数据时,会回调此方法
serialPort.on('data',(data)=>{
console.log(JSON.stringify(data.toString('utf8')));
receiveData += data.toString('utf8');
console.log(receiveData);
//我是同过起始标志和结束标志来判断单片机是否已经将所有的数据发送完成
//这个可以自定义开始与结束标志
//本例中使用*表示结束
if(receiveData.startsWith('I am Arduino')&&receiveData.endsWith('*')){
read(receiveData);
receiveData='';
}
})
})
/**
* 关闭设备
*/
ipcMain.on('aeio-close', (ev, ...args) => {
if (serialPort!=null&&serialPort.isOpen) {
serialPort.close((err) => {
if(err){
error(`设备关闭失败:${err}`)
}else{
error('设备已关闭');
}
});
} else {
error(`设备不存在,或设备未打开`)
}
})
说明:
options
对象:
path
属性:在获取串口设备方法返回的集合中,代表串口设备的对象也有一个path
属性,这个属性值就是要传给options
对象的path
,本例中是通过输入框输入的,在实际开发中可以使用下拉列表选择串口设备。baudRate
属性:波特率 ,这个值需要和Arduino设置的波特率一致。文末有Arduino的源码,请看注释。
index.js
<div>数据:<input type="text" id="mInputData" placeholder="请输入要发送的数据" width="200px" /><button id="mBtnWrite"
onclick="writeData()">发送数据</button></div>
<script>
/**
* 发送数据
**/
function writeData() {
let mInputData = document.getElementById('mInputData').value;
if (mInputData.length === 0) {
return;
}
window.aeIo.write(mInputData);
}
</script>
main.js
/**
* 向arduino发送数据
*/
ipcMain.on('aeio-write', (ev, ...args) => {
if (serialPort!=null&&serialPort.isOpen) {
let sendData = args[0]+"#";//以#号结尾 arduino 读取到#号的时候就代表 已经接收到全部数据了
serialPort.write(Buffer.from(sendData),(err)=>{
if(err){
error(`设备发送失败:${err}`)
}else{
error('数据发送成功');
}
})
} else {
error(`设备不存在,或设备未打开,无法发送数据`)
}
})
arduino.cc
//loop中接收数据
void loop() {
// put your main code here, to run repeatedly:
if (Serial.available() > 0) {
// read the incoming byte:
String data = Serial.readStringUntil('#');//遇到#号结束
Serial.print("I am Arduino,I get data:"+data+"*");
}
delay(10);
}
接收设备的数据我们要反过来了,从arduino到主进程,再到渲染进程。
arduino.cc
//loop中接收数据
void loop() {
// put your main code here, to run repeatedly:
if (Serial.available() > 0) {
// read the incoming byte:
String data = Serial.readStringUntil('#');//遇到#号结束
//本例是当接收完数据,就把数据返回
Serial.print("I am Arduino,I get data:"+data+"*"); //发送数据,并将接收到的数据返回过去
}
delay(10);
}
main.js
//监听数据,当单片机发送数据时,会回调此方法
serialPort.on('data',(data)=>{
console.log(JSON.stringify(data.toString('utf8')));
receiveData += data.toString('utf8');
console.log(receiveData);
//我是同过起始标志和结束标志来判断单片机是否已经将所有的数据发送完成
//这个可以自定义开始与结束标志
//本例中使用*表示结束
if(receiveData.startsWith('I am Arduino')&&receiveData.endsWith('*')){
read(receiveData);
receiveData='';
}
})
index.html
/**
* 接收数据
**/
window.aeIo.read((_event, value) => {
dListInfo.innerHTML = `[${new Date()}]: ${value} <br/>` + dListInfo.innerHTML;
})
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>arduino-electron-io</title>
<style>
body {
padding: 50px;
}
div {
margin-top: 10px;
margin-bottom: 10px;
}
button {
margin-left: 5px;
margin-right: 5px;
background-color: rgb(14, 153, 247);
color: white;
border: 1px solid rgb(14, 153, 247);
height: 32px;
}
.messageClass {
border: 1px solid gray;
background-color: black;
color: white;
font-size: 16px;
height: 400px;
margin-top: 50px;
overflow-y: auto;
}
</style>
</head>
<body>
<div><button id="mBtnList" onclick="getList()">获取串口设备列表</button>设备列表:<span id="dListInfo"></span></div>
<div>串口设备:<input type="text" id="mInputDevPath" placeholder="请从设备列表上复制" width="200px" />波特率:<input type="text"
id="mInputDevPort" placeholder="波特率" width="200px" value="115200" /><button id="mBtnOpen"
onclick="openDevice()">打开设备</button><button id="mBtnClose" onclick="closeDevice()">关闭设备</button></div>
<div>数据:<input type="text" id="mInputData" placeholder="请输入要发送的数据" width="200px" /><button id="mBtnWrite"
onclick="writeData()">发送数据</button></div>
<div id="mMessage" class="messageClass"></div>
<script>
let dListInfo = document.getElementById('mMessage');
/**
* 接收数据
**/
window.aeIo.read((_event, value) => {
dListInfo.innerHTML = `[${new Date()}]: ${value} <br/>` + dListInfo.innerHTML;
})
/**
* 接收消息提示
**/
window.aeIo.message((_event, value) => {
dListInfo.innerHTML = `[${new Date()}]: ${value} <br/>` + dListInfo.innerHTML;
})
/**
* 向主进程发送获取串口设备的消息,并等待串口设备集合
**/
function getList() {
window.aeIo.list().then((list) => {
let dListInfo = document.getElementById('dListInfo');
dListInfo.innerHTML = JSON.stringify(list);
});
}
/**
* 打开设备
**/
function openDevice() {
let mInputDevPath = document.getElementById('mInputDevPath').value;// path 设备路径 我测试的时候是COM4
let mInputDevPort = document.getElementById('mInputDevPort').value;// 波特率 我设置的是115200 如果这个和arduino设置的不一致是无法通信的
window.aeIo.open(mInputDevPath, mInputDevPort);
}
/**
* 关闭设备
**/
function closeDevice() {
window.aeIo.close();
}
/**
* 发送数据
**/
function writeData() {
let mInputData = document.getElementById('mInputData').value;
if (mInputData.length === 0) {
return;
}
window.aeIo.write(mInputData);
}
</script>
</body>
</html>
main.js
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
const { SerialPort } = require('serialport')
let win
const createWindow = () => {
win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
win.maximize()
win.webContents.openDevTools()
win.loadFile('index.html')
}
let serialPort;
app.whenReady().then(() => {
createWindow()
})
/**
* 获取串口列表
*/
ipcMain.handle('aeio-list', (ev, args) => {
return SerialPort.list().then((info, err) => {
if (err) {
sendToRenderer(error(`设备列表获取失败:${err}`))
return [];
}
//将集合返回给界面
return info;
})
})
/**
* 打开设备
*/
let receiveData='';//接收到的数据
ipcMain.on('aeio-open', (ev, ...args) => {
console.log(args);
const options = {
path: args[0],
baudRate: parseInt(args[1]),
}
//实例化设备之后此时已经打开设备了
serialPort = new SerialPort(options);
//监听数据,当单片机发送数据时,会回调此方法
serialPort.on('data',(data)=>{
console.log(JSON.stringify(data.toString('utf8')));
receiveData += data.toString('utf8');
console.log(receiveData);
//我是同过起始标志和结束标志来判断单片机是否已经将所有的数据发送完成
//这个可以自定义开始与结束标志
//本例中使用*表示结束
if(receiveData.startsWith('I am Arduino')&&receiveData.endsWith('*')){
read(receiveData);
receiveData='';
}
})
})
/**
* 关闭设备
*/
ipcMain.on('aeio-close', (ev, ...args) => {
if (serialPort!=null&&serialPort.isOpen) {
serialPort.close((err) => {
if(err){
error(`设备关闭失败:${err}`)
}else{
error('设备已关闭');
}
});
} else {
error(`设备不存在,或设备未打开`)
}
})
/**
* 向arduino发送数据
*/
ipcMain.on('aeio-write', (ev, ...args) => {
if (serialPort!=null&&serialPort.isOpen) {
let sendData = args[0]+"#";//以#号结尾 arduino 读取到#号的时候就代表 已经接收到全部数据了
serialPort.write(Buffer.from(sendData),(err)=>{
if(err){
error(`设备发送失败:${err}`)
}else{
error('数据发送成功');
}
})
} else {
error(`设备不存在,或设备未打开,无法发送数据`)
}
})
/**
* 向渲染器进程发送消息
* @param {*} channel
* @param {*} args
*/
function sendToRenderer(channel, args) {
win.webContents.send(channel, args);
}
function read(data) {
sendToRenderer('aeio-read', data)
}
function error(err) {
sendToRenderer('aeio-message', err)
}
preload.js
本文上半段已经展示了此源码。
arduino.cc
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);//设置波特率
Serial.setTimeout(10000);
}
//loop中接收数据
void loop() {
// put your main code here, to run repeatedly:
if (Serial.available() > 0) {
// read the incoming byte:
String data = Serial.readStringUntil('#');//遇到#号结束
//本例是当接收完数据,就把数据返回
Serial.print("I am Arduino,I get data:"+data+"*"); //发送数据,并将接收到的数据返回过去
}
delay(10);
}
![在这里插入图片描述](https://img-blog.csdnimg.cn/c9cae57417c74fef962e3e7ac36bdaeb.jpeg#pic_center)