查看: 2021|回复: 14
收起左侧

[综合教程经验] 【一起摸鱼】1.3Node.js上位机应用开发-Electron可视化交互

mingiii 2022-8-14 11:45:29 | 显示全部楼层 |阅读模式
邀请回答

马上注册,享受更多特权

您需要 登录 才可以下载或查看,没有帐号?立即注册   

x
本帖最后由 mingiii 于 2022-8-14 12:02 编辑

1 创建 Electron 项目

上一章我们创建了能与 H5U 进行 Modbus TCP 通讯的程序,这一章将尝试在 Electron 内嵌的 Node.js 中实现与可视化界面的通讯。

注意:汇川论坛的markdown解析器有时候会把小于号 < 解析成 < 大于号 > 解析成 > 复制代码的话要记得替换

1.1 初始化项目

首先我们和上一章一样,创建一个项目文件夹,在终端中进入该文件夹,然后输入 npm init,随后按照下图配置项目。(# 后为注释)

> npm init
Debugger attached.
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (3-electron) h5u-modbus-tcp-client # 项目名
version: (1.0.0) # 版本
description: This is a modbus client for inovance h5u plc # 描述
entry point: (main.js) # 入口文件
test command: electron . # 测试命令
git repository: # git仓库
keywords:
author: mingiii # 作者
license: (ISC)
About to write to package.json:
{
  "name": "h5u-modbus-tcp-client",
  "version": "1.0.0",
  "description": "This is a modbus client for inovance h5u plc",
  "main": "main.js",
  "scripts": {
    "test": "electron ."
  },
  "author": "mingiii",
  "license": "ISC"
}
Is this OK? (yes)

自此项目初始化完成

1.2  下载  Electron

这里我们下载依赖与我们系统中 Node.js 版本相同的 Electron 14.2.9 版本,首先我们需要切换至国内镜像,在终端中输入

npm config set ELECTRON_MIRROR="https://npmmirror.com/mirrors/electron/"

然后输入 npm install --save-dev electron@14.2.9 完成下载

> npm config set ELECTRON_MIRROR="https://npmmirror.com/mirrors/electron/"
> npm install --save-dev electron@14.2.9

+ electron@14.2.9
added 91 packages from 98 contributors in 48.163s

10 packages are looking for funding
  run `npm fund` for details

1.3  运行 Electron

接下来我们新建一个 main.js 作为 Electron 主入口:

console.log(`mingiii: hello`)

然后我们在终端运行 npm test , 得到如下输出,Electron 成功运行

> electron .
mingiii: hello

2 将网页装载至窗体

在 Electron 中,每个窗口展示一个页面,该页面可以来自本地的 HTML,也可以来自远程 URL。 在本例中将会装载本地的文件。 接下来在项目的根目录中创建一个 index.html 文件,并写入下面的内容:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self'"
    />
    <meta
      http-equiv="X-Content-Security-Policy"
      content="default-src 'self'; script-src 'self'"
    />
    <title>一起摸鱼</title>
  </head>
  <body>
    <h1>【一起摸鱼】Node.js上位机应用开发</h1>
    <p> --mingiii</p>
  </body>
</html>

现在我们得到了一个网页,可以将它装载到 Electron 的窗体上了。 然后我们需要将 main.js 中的内容替换成下列代码。

const { app, BrowserWindow } = require('electron')
// createWindow() 函数将页面加载到新的 BrowserWindow 实例中:
const createWindow = () => {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
  })
  // 窗体载入网页
  win.loadFile('index.html')
}
// 在应用准备就绪时调用函数
app.whenReady().then(() => {
  createWindow()
  // 如果没有窗口打开则打开一个窗口 (macOS)
  app.on('activate', function () {
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})
// 关闭所有窗口时退出应用 (Windows & Linux)
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit()
  }
});

然后我们再次在终端运行 npm test , 得到如下图包含着我们网页的窗体

3 Node.js 与网页通讯

Electron 的 main 和 renderer 进程有不同的职责并且不可互换。这意味着无法直接从渲染器进程(网页)访问 Node.js API,也无法从主进程( Node.js )访问 HTML 文档对象模型 (DOM)。这个问题的解决方案是使用 ElectronipcMainipcRenderer模块进行进程间通信(IPC),详细内容请参考 Electron文档

接下来我们尝试结合上一章讲到的 Modbus TCP 通讯使用 ipcMainipcRenderer 模块实现窗体与 H5U 之间的数据读写。

在这之前,为了将 Electron 的不同进程类型连接在一起,我们需要使用一个称为preload的特殊脚本。

3.1 预加载脚本

我们新建一个preload.js文件,将我们将要使用的读写 API 挂载至渲染进程的 window.electronAPI 下

const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('electronAPI', {
    // 写寄存器
    setRegisters: () => ipcRenderer.send('set-registers'),
    // 读寄存器
    handleData: (callback) => ipcRenderer.on('send-data', callback)
})

然后在main.js中新增代码

/* 新增 引入path
__dirname 字符串指向当前正在执行脚本的路径 (在本例中,它指向你的项目的根文件夹)。
path.join API 将多个路径联结在一起,创建一个跨平台的路径字符串。 */
const path = require('path')
const win = new BrowserWindow({
    width: 800,
    height: 600,
    // 新增:挂载预加载脚本
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
})

这样渲染进程的 window 对象下就成功挂载了读写用的 API

3.2 写入

为了在网页上发出写入信息的命令,我们需要在index.html文件中增加一个按钮并挂载脚本

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'self'"
    />
    <meta
      http-equiv="X-Content-Security-Policy"
      content="default-src 'self'; script-src 'self'"
    />
    <title>一起摸鱼</title>
  </head>
  <body>
    <h1>【一起摸鱼】Node.js上位机应用开发</h1>
    <!--一会读取用到的p标签 -->
    D0 - D4:<p id="data"> --mingiii</p>
    <!--增加按钮 -->
    <button id="btn" type="button">Set 1 - 5</button>
    <!--挂载脚本 -->
    <script src="./renderer.js"></script>
  </body>
</html>

然后我们新建renderer.js,为按钮的click事件增加监听器

const setButton = document.getElementById('btn')
setButton.addEventListener('click', () => {
    window.electronAPI.setRegisters()
})

最后我们参照上一章的通讯例程,修改一下main.js

const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
// 引入 Modbus
const ModbusRTU = require("modbus-serial")
const client = new ModbusRTU()
// 窗体移到外部作用域方便调用
let win
// createWindow() 函数将页面加载到新的 BrowserWindow 实例中:
const createWindow = () => {
  win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js')
    }
  })
  // 窗体载入网页
  win.loadFile('index.html')
  // 连接 Modbus TCP 客户端
  client.connectTCP("127.0.0.1", { port: 502 })
  client.setID(1)
}
// 在应用准备就绪时调用函数
app.whenReady().then(() => {
  ipcMain.on('set-registers', handleSetRegisters)
  createWindow()
  // 如果没有窗口打开则打开一个窗口 (macOS)
  app.on('activate', function () {
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})
// 关闭所有窗口时退出应用 (Windows & Linux)
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit()
  }
})
// 将1、2、3、4、5分别写入D0到D4
function handleSetRegisters () {
  client.writeRegisters(0, [1, 2, 3, 4, 5])
}

此时我们使用 npm test运行程序,然后点击按钮,可以看到 PLC D0 - D4 的监控值已经分别变成 1, 2, 3, 4, 5

3.3 读取

实现读取只需要在renderer.js中增加一个回调函数,将回调值写入DOM

const data = document.getElementById('data')
window.electronAPI.handleData((event, value) => {
  data.innerText = value
})

然后在 main.js中增加方法read,并在 createWindow 中增加一个计时器

// 每秒读取一次,追加至 createWindow 函数中的最后
setInterval(read, 1000)

// 读取D0到D4,追加至 main.js 文件的最后
function read () {
  client.readHoldingRegisters(0, 5)
    .then((data) => {
      win.webContents.send('send-data', data.data)
    })
}

此时我们再次使用 npm test运行程序,可以看到如下窗体

然后我们点击按钮,寄存器中的值改变,界面值也随之改变

自此读取、写入功能实现成功。

4 参考文献

  1. electronjs.org / OpenJS 基金会和 Electron 贡献者们, 2021

5 讨饭用附件

附件不包含依赖,使用前请输入npm i安装
给点给点



3-electron.7z (14.47 KB, 下载次数: 3, 售价: 3 )




上一篇:应用问题
下一篇:钻石加工设备

已有 2 人打赏作者

SSSS_AAAA 赏了楼主25水滴 陈婧 赏了楼主25水滴
回复 邀请回答送花

使用道具 举报

mingiii 2022-8-14 11:54:28 | 显示全部楼层
本帖最后由 mingiii 于 2022-8-14 12:00 编辑

回复 送花

使用道具 举报

mingiii 2022-8-14 11:56:24 | 显示全部楼层
本帖最后由 mingiii 于 2022-8-14 12:01 编辑

提醒一下,论坛的markdown解析器有时候会把小于号解析成< 大于号解析成>复制代码的话要注意一下
回复 送花

使用道具 举报

mingiii 2022-8-15 08:24:15 | 显示全部楼层
顶一顶
回复 送花

使用道具 举报

陈婧 2022-8-15 09:03:37 | 显示全部楼层
内容丰富,感谢分享
回复 送花

使用道具 举报

我愿人长久 2022-8-15 09:25:02 | 显示全部楼层
内容详细,感谢分享
回复 送花

使用道具 举报

夏天 2022-8-15 09:35:13 | 显示全部楼层

内容丰富,感谢分享
回复 送花

使用道具 举报

AC800 2022-8-15 09:46:40 | 显示全部楼层
摸鱼能摸成这样也是够卷的
回复 送花

使用道具 举报

jij0365674 2022-8-15 09:52:00 | 显示全部楼层
谢谢大佬分享
回复 送花

使用道具 举报

mingiii 2022-8-15 09:54:10 | 显示全部楼层
i² 发表于 2022-8-15 09:46
摸鱼能摸成这样也是够卷的

只要不是工作那就是快乐摸鱼
回复 送花

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册   

本版积分规则

有技术问题,就上汇川技术社区

INOVANCE汇川技术 公众号

扫码下载掌上汇川APP

全国服务热线:8:30-17:30

4000-300124

苏州地址:江苏省苏州市吴中区越溪友翔路16号

深圳地址:深圳市龙华新区观澜街道高新技术产业园汇川技术总部大厦

Copyright © 2003-2100 汇川技术 Powered by Discuz! X3.4 ( 苏ICP备12002088号 )
快速回复 返回列表 返回顶部