electron+node+node-serialport 实现串口通信+electron-builder打包完整流程

electron+node+node-serialport 实现串口通信+electron-builder打包完整流程

最近有个项目需要连接电子秤到收银系统,然后需要调用硬件串口,因为收银系统,本来是用vue写好的网页,后面突然加了个需求,所以笔者思考了一下实现思路

  1. 用node js 操作串口 (serialport )
  2. 用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.73.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

  1. 英文文档
  2. 翻译的文档 (翻译中有许多使用有误,不过可以帮助阅读文档,)
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() {
            win.webContents.openDevTools();
          }

        },
        {
          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

打包配置

  1. 基础配置
"build": {  // 这里是electron-builder的配置
    "productName":"xxxx",//项目名 这也是生成的exe文件的前缀名
    "appId": "com.xxx.xxxxx",//包名  
    "copyright":"xxxx",//版权  信息
    "directories": { // 输出文件夹
      "output": "build"
    }, 
    // windows相关的配置
    "win": {  
      "icon": "xxx/icon.ico"//图标路径 
    }  
  }

在配置文件中加入以上的文件之后就可以打包出来简单的文件夹,文件夹肯定不是我们想要的东西。下一步我们来继续讲别的配置。

  1. 打包目标配置

要打包成安装程序的话我们有两种方式,

  1. 使用NSIS工具对我们的文件夹再进行一次打包,打包成exe
  2. 通过electron-builder的nsis直接打包成exe,配置如下
"win": {  // 更改build下选项
    "icon": "build/icons/aims.ico",
    "target": [
      {
        "target": "nsis" // 我们要的目标安装包
      }
    ]
  },
  1. 其他平台配置
  "dmg": { // macOSdmg
    "contents": [
      ...
    ]
    },
    "mac": {  // mac
      "icon": "build/icons/icon.icns"
    },
    "linux": { // linux
      "icon": "build/icons"
    }
  1. 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

如果打包报错,先看看你的文件路径上是否有中文命名,这个报错不会有提示(这个是笔者写demo时,创建在了我的笔记文件夹里,然后0-0)

第二是下载还是慢,卡住,这个网速硬伤还是有解决方法,那就是手动下载

electron-builder只有第一次打包需要下载打包依赖,就很棒,所以我们只需要,手动把依赖下好放在对应的文件就ok了

参考资料

七、项目中遇到的坑

  1. 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)

在这里插入图片描述

最好安装一个nvm用于切换版本,

  1. python环境问题?
    在这里插入图片描述
    在这里插入图片描述
    如果启动报错第一个,就是编译失败,都是和环境有关,配置环境变量即可

  2. 打包时没把dist中的东西删除因为文件被程序占用中删除不掉(打开任务管理器找到占用的进程清空dist就好了)
    在这里插入图片描述

  3. 这是builder貌似不能打包最新的electron版本,这里吧electron版本改为7.1.9,再下载一次就好了,

在这里插入图片描述
在这里插入图片描述

  1. 找不到@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’) 就行了

发布了52 篇原创文章 · 获赞 82 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/marendu/article/details/104767773