electron+node+node-serialport 实现串口通信+electron-builder打包完整流程
最近有个项目需要连接电子秤到收银系统,然后需要调用硬件串口,因为收银系统,本来是用vue写好的网页,后面突然加了个需求,所以笔者思考了一下实现思路
- 用node js 操作串口 (serialport )
- 用electron,建立webSocet 和网页建立通信(用的ws模块)
下面是笔者花了几天时间,排坑查资料书理的心得
过程中遇到问题,请查看文章末尾的问题集合,帮助你解决问题,有时候网速问题,electron丢包,需要耐心卸载再安装(删除nodemodle后最好用cnpm i 安装)
有问题,和更正确的使用请在评论区留言,笔者会及时回复
文章目录
一、 创建一个electron应用
首先安装 一个官方例子
git clone https://github.com/electron/electron-quick-start.git
二、安装依赖
进入 electron-quick-start 到package.json中把electron版本改成7.1.9不然后面打包会有问题
npm 有点慢(容易丢包),推荐使用cnpm i 使用淘宝镜像 下载
安装好依赖后,启动项目 npm start
三、配置环境
配置npm(解决打包和electron下载慢的问题)
全局设置下载源:
npm config set registry https://registry.npm.taobao.org/
下载node源码加速:
npm config set disturl https://npm.taobao.org/mirrors/node
然后将electron包下载地址注册位淘宝的镜像:
npm config set ELECTRON_MIRROR https://npm.taobao.org/mirrors/electron/
或者 找到c盘中的.npmrc 直接写
registry=https://registry.npm.taobao.org/
disturl=https://npm.taobao.org/mirrors/node
ELECTRON_MIRROR=https://npm.taobao.org/mirrors/electron/
electron使用原生模块需要再编译, 文档
我们还需要使用node-gyp,node-gyp,是由于node程序中需要调用一些其他语言编写的 工具 甚至是dll,需要先编译一下,否则就会有跨平台的问题,
npm install --global --production windows-build-tools
安装内容:
1、python(v2.7 ,3.x不支持);
2、visual C++ Build Tools,或者 (vs2015以上(包15))
3、.net framework 4.5.1
如果电脑中存在python,最好先卸载掉不然会冲突,或者set一下
打开命令窗口 输入python 查看一下是否存在python, 如果找不到命令 找到python安装目录
把路径加到环境变量中
再次执行python
安装完成后,安装node-gyp
npm install -g node-gyp
输入 node-gyp list 命令 查看是否安装成功
安装 ws模块(用于开启服务,无法使用node-webSocket模块编译后依旧无效,ws不需要编译且可以使用)
cnpm i ws -S
安装 serialport
cnpm install serialport -S
安装 electron-rebuild(electron用于编译node原生模块)
cnpm install electron-rebuild -D
前置工作做完,输入命令开始重新编译(第一次编译需要点时间,耐心等待git下是这样,cmd下面是反斜杠,有些也是反斜杠,那个能用用那个)
./node_modules/.bin/electron-rebuild.cmd
没有报错看到rebiuld Complete那么就表示ok了,那前面几步就没问题,报错,就看看前面那些有问题
这个时候在启动一下 npm start 编译成功,就ok
四、编写electron
重新编写mian.js
const {
app,
BrowserWindow,
Menu,
MenuItem,
globalShortcut
} = require('electron')
const SerialPort = require('serialport');
const Delimiter = require('@serialport/parser-delimiter')
const WebSocket = require('ws');
// 引用Server类:
const WebSocketServer = WebSocket.Server;
var portName = 'COM3'; //定义默认串口名
var serialPort;
var strs = ''
var wss //webSocet Server
var wsSend
var COMarr = [] //查询到的该电脑在使用的串口
//获取正在使用的串口集合,用于生成切换端口
function getPortArr() {
return new Promise((res, rej) => {
SerialPort.list().then((ports) => {
ports.forEach(function (port) {
COMarr.push(port.comName)
console.log(port.comName);
console.log(port.pnpId);
console.log(port.manufacturer);
res()
});
});
})
}
function startPort() {
if (serialPort) {
try {
serialPort.close();
} catch (err) {
}
}
serialPort = new SerialPort( //设置串口属性
portName, {
baudRate: 9600, //波特率
dataBits: 8, //数据位
parity: 'none', //奇偶校验
stopBits: 1, //停止位
flowControl: false,
autoOpen: false //不自动打开
}, false);
const parser = serialPort.pipe(new Delimiter({
delimiter: '\n'
})) //当端口读取到换行符时,才发送到程序,这是因为电子秤发送的数据是间断的原因
serialPort.on('error', (error) => {//捕获错误
console.log('Error: ', error.message);
})
serialPort.open(function (error) { //手动打开串口
if (error) {
console.log("打开端口" + portName + "错误:" + error);
} else {
if (wss) {
wss.close()
}
// 实例化: 监听本机的27611端口 127.0.0.1:27611
wss = new WebSocketServer({
port: 27611
});
wss.on('connection', function (ws) {
console.log("开启连结")
ws.on("message", function (message) {
console.log('接受到的信息' + message);
if (message === "HeartBeat") {
ws.send("01 收到开始操作")
wsSend = ws
}
})
ws.on("close", function () {
console.log("关闭服务");
})
})
console.log("打开端口成功,正在监听数据中");
//串口设备传来的数据 是buffer对象,用toString转一下码
parser.on('data', function (data) {
console.log(data.toString());
strs = data.toString()
if(wsSend){
wsSend.send(strs)
}
})
}
});
}
startPort() //调用开启串口
app.on('ready', () => {
let win = new BrowserWindow({
width: 800,
height: 600,
title:"webSocet sever",
webPreferences: {
nodeIntegration: true
}
})
// 加载页面
win.loadFile('./index.html')
// 类似浏览器的window 与窗口有关的浏览器内容都是通过下面的属性
// 配置esc退出全屏
globalShortcut.register('ESC', () => {
win.setFullScreen(false);
})
//是否默认打开就全屏显示
// win.setFullScreen(true);
//默认打开开发者工具(调试使用)
win.webContents.openDevTools();
getPortArr().then(() => {
// 创建菜单对象
let menu = new Menu();
// 创建菜单项
let submenu = []
COMarr.map(item => {
submenu.push({
type: "normal",
label: item,
click() {
portName = item
strs = ''
startPort()
}
})
submenu.push( {
type: "separator", //菜单分割符
},)
})
let mil = new MenuItem({
type: "submenu",
label: '切换端口',
submenu: submenu
})
let mil2 = new MenuItem({
type: "submenu",
label: '功能',
submenu: [{
role: "forcereload",
label: "刷新",
},
{
type: "separator", //菜单分割符
},
{
role: "togglefullscreen",
label: "全屏",
},
{
type: "separator", //菜单分割符
},
{
role: "minimize",
label: "最小化",
},
{
type: "separator", //菜单分割符
},
{
type: "normal",
label: "开发者工具",
click: function(item, focusedWindow) {
if (focusedWindow)
focusedWindow.toggleDevTools();
}
},
{
type: "separator", //菜单分割符
},
{
role: "quit ",
label: "退出",
},
]
})
//把菜单添加到指定的菜单对象
menu.append(mil)
menu.append(mil2)
// 菜单位置: 1, 应用程序窗口顶层,
Menu.setApplicationMenu(menu)
})
})
启动一下看看能否开启服务
五、编辑client客户端
打开index.html 复制下列
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
#mess{text-align: center}
</style>
</head>
<body>
<div id="mess">正在连接...</div>
<div id="socket">
</div>
<script>
var mess = document.getElementById("mess");
var socket = document.getElementById("socket");
if(window.WebSocket){
var ws = new WebSocket('ws://127.0.0.1:27611');
ws.onopen = function(e){
console.log("连接服务器成功");
ws.send("HeartBeat");
}
ws.onclose = function(e){
console.log("服务器关闭");
}
ws.onerror = function(){
console.log("连接出错");
}
ws.onmessage = function(e){
console.log(e.data);
socket.innerHTML ="获取到的数据"+ e.data
mess.innerHTML = "连接成功"
}
}
</script>
</body>
</html>
ok启动一下咯, 发现启动成功,服务器却压根没连上 ,这时请先看看 默认串口是否是你连接的,然后把默认串口改成你需要的串口(也可以点击切换端口=>然后点击功能 刷新)如果切换端口为空,那么爱莫能助,你想测试是否能调通只能把和端口有关的都注释了
查看方式(有连接设备的请选择连接的串口)
桌面 => 计算机 =>右键选者管理
到这你就成功一半了,接下来就是打包环节
六、打包 electron-builder
安装
npm install electron-builder -D
打包配置
- 基础配置
"build": { // 这里是electron-builder的配置
"productName":"xxxx",//项目名 这也是生成的exe文件的前缀名
"appId": "com.xxx.xxxxx",//包名
"copyright":"xxxx",//版权 信息
"directories": { // 输出文件夹
"output": "build"
},
// windows相关的配置
"win": {
"icon": "xxx/icon.ico"//图标路径
}
}
在配置文件中加入以上的文件之后就可以打包出来简单的文件夹,文件夹肯定不是我们想要的东西。下一步我们来继续讲别的配置。
- 打包目标配置
要打包成安装程序的话我们有两种方式,
- 使用NSIS工具对我们的文件夹再进行一次打包,打包成exe
- 通过electron-builder的nsis直接打包成exe,配置如下
"win": { // 更改build下选项
"icon": "build/icons/aims.ico",
"target": [
{
"target": "nsis" // 我们要的目标安装包
}
]
},
- 其他平台配置
"dmg": { // macOSdmg
"contents": [
...
]
},
"mac": { // mac
"icon": "build/icons/icon.icns"
},
"linux": { // linux
"icon": "build/icons"
}
- nsis配置
这个要详细的讲一下,这个nsis的配置指的是安装过程的配置,其实还是很重要的,如果不配置nsis那么应用程序就会自动的安装在C盘。没有用户选择的余地,这样肯定是不行的
关于nsis的配置是在build中nsis这个选项中进行配置,下面是部分nsis配置
"nsis": {
"oneClick": false, // 是否一键安装
"allowElevation": true, // 允许请求提升。 如果为false,则用户必须使用提升的权限重新启动安装程序。
"allowToChangeInstallationDirectory": true, // 允许修改安装目录
"installerIcon": "./build/icons/aaa.ico",// 安装图标
"uninstallerIcon": "./build/icons/bbb.ico",//卸载图标
"installerHeaderIcon": "./build/icons/aaa.ico", // 安装时头部图标
"createDesktopShortcut": true, // 创建桌面图标
"createStartMenuShortcut": true,// 创建开始菜单图标
"shortcutName": "xxxx", // 图标名称
"include": "build/script/installer.nsh", // 包含的自定义nsis脚本 这个对于构建需求严格得安装过程相当有用。
},
编写nsis脚本新建一个installer.nsh(提供一个简单的)需要有自定义界面要求的 提供一个脚本文档 中文文档
!macro customInstall
WriteRegStr HKCR "CenDC" "URL Protocol" ""
WriteRegStr HKCR "CenDC" "" "URL:CenDC Protocol Handler"
WriteRegStr HKCR "CenDC\shell\open\command" "" '"$INSTDIR\CenDC.exe" "%1"'
!macroend
下面是笔者使用的,可以用于参考
{
"name": "electron",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"rebuild": "./node_modules/.bin/electron-rebuild.cmd",
"test": "echo \"Error: no test specified\" && exit 1",
"start": ".\\node_modules\\.bin\\electron .",
"build": "electron-builder",
"packager": "electron-packager . wbx --platform=win32 --arch=x64 --out ./dist --app-version=1.0.0"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"electron": "^7.1.9",
"electron-builder": "^22.2.0",
"electron-rebuild": "^1.10.0"
},
"dependencies": {
"node-uuid": "^1.4.8",
"node-websocket": "^0.1.6",
"serialport": "^8.0.8",
"ws": "^7.2.1"
},
"build": {
"appId": "mmm",
"productName": "系统",
"copyright": "Copyright",
"directories": {
"output": "./dist"
},
"win": {
"target": [
{
"target": "nsis",
"arch": [
"ia32"
]
}
],
"icon": "./img/logo.png"
},
"nsis": {
"oneClick": false,
"allowElevation": true,
"allowToChangeInstallationDirectory": true,
"installerIcon": "./img/in.ico",
"uninstallerIcon": "./img/in.ico",
"installerHeaderIcon": "./img/in.ico",
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"include": "./installer.nsh"
}
}
}
如果打包报错,先看看你的文件路径上是否有中文命名,这个报错不会有提示(这个是笔者写demo时,创建在了我的笔记文件夹里,然后0-0)
第二是下载还是慢,卡住,这个网速硬伤还是有解决方法,那就是手动下载
electron-builder只有第一次打包需要下载打包依赖,就很棒,所以我们只需要,手动把依赖下好放在对应的文件就ok了
七、项目中遇到的坑
-
NODE_MODULE_VERSION?
每一个node版本 都有对应的 NODE_MODULE_VERSION
可以在 mian.js 打印当前项目需要的对应版本 如果报错安装对应的node就ok
//获取对应的elctron版本和对应的node版本、模块版本,
console.log("node:",process.versions.node)
console.log("electron:",process.versions.electron)
console.log("modules:",process.versions.modules)
如果找不到对应版本, 那么运行一下这个命令也是可以的
./node_modules/.bin/electron-rebuild.cmd
最好安装一个nvm用于切换版本,
-
python环境问题?
如果启动报错第一个,就是编译失败,都是和环境有关,配置环境变量即可 -
打包时没把dist中的东西删除因为文件被程序占用中删除不掉(打开任务管理器找到占用的进程清空dist就好了)
-
这是builder貌似不能打包最新的electron版本,这里吧electron版本改为7.1.9,再下载一次就好了,
- 找不到@serialport/parser-delimiter 模块
在node-module中找到这个包,然后复制一份,粘贴到node-module中
手动在package.json 的dependencies 中添加 “parser-delimiter”:"^8.0.6" ,原理是webpack打包只会打dependencies 中的包,其他项目遇到也可以这样操作,然后在main.js中将引用改成 const Delimiter = require(’./node_modules/parser-delimiter’) 就行了
- 报错不是一个win32程序?
这个是我打包32位后,再启动,就报的一个错误,导致无法启动,解决方法
先 npm rebuild
之后再执行
./node_modules/.bin/electron-rebuild.cmd
然后就可以启动了
最后提示,如果打包不了请先看你的electron是否正在启动,貌似不能在启动运行中,同时打包