使用 JavaScript,HTML 和 CSS 構建跨平臺的桌面應用程序
只要你會javascript html css 就可以構建自己想要做的PC桌面和MACos app 應用,是不是很強大。
今天的重點是通過它來實現(xiàn)串口通信的功能,想要實現(xiàn)這部分功能不得不做些準備工作
下面跟我一步一步的來操作吧
想構建electron 必須要有支持的基礎環(huán)境,node 和 npm
node想必大家并不陌生,前端的小伙伴太熟悉不過了,Node.js 就是運行在服務端的 JavaScript
檢測你的電腦環(huán)境中是否安裝了node.js
檢測是否安裝node,的命令是
node -v
我這里是win10 開發(fā)環(huán)境
打開命令行工具
我這里已經(jīng)安裝過了,看到有版本信息v10.16.1 說明已經(jīng)安裝成功
接下來再檢查下是否安裝了npm 工具
npm -v
我這里也已經(jīng)安裝了npm ,顯示版本6.9.0
有的同學小伙伴不知道npm是什么
PS:是nodejs內置的軟件包管理器, 在項目開發(fā)中,需要用到說明包就拿這個下載就行了,下面有介紹
好了,有了基礎的環(huán)境,我們就開始構建一個桌面程序吧
在工作的根目錄創(chuàng)建一個文件夾eletest
在創(chuàng)建一個普通的index.html 文件,這樣就有了一個基本的前端界面,electron 在node.js基礎上構建的,下面是應用的基本目錄結構,我們已經(jīng)創(chuàng)建了index.html
eletest/
├── package.json
├── main.js
└── index.html
mian.js文件也是electron的入口文件
const electron = require('electron')
// Module to control application life.
const app = electron.app
// Module to create native browser window.
const BrowserWindow = electron.BrowserWindow
const path = require('path')
const url = require('url')
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow
function createWindow() {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 1920,
height: 1080,
frame:false,
resizable: false,
fullscreen:true,
webPreferences: {
nodeIntegration: true,
// preload: path.join(__dirname, 'preload.js')
}
})
// and load the index.html of the app.
mainWindow.loadURL(url.format({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file:',
slashes: true
}))
// Open the DevTools.
mainWindow.webContents.openDevTools()
// Emitted when the window is closed.
mainWindow.on('closed', function() {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null
})
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)
// Quit when all windows are closed.
app.on('window-all-closed', function() {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
app.quit()
})
app.on('activate', function() {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
createWindow()
}
})
這里的方法 函數(shù)不在過多的解釋了,復制代碼到你創(chuàng)建的main.js中去就可以了,也可以去electron文檔中查看對應的API
package.json 這是一個包構建信息的文件 在eletest文件下運行命令
npm init -y
就會自動生成package.json文件 ,是不是很簡單啊
要想運行你寫的hml界面 打開這個文件修改一處
"scripts": {
"start": "electron ."
},
這樣就完成了幾個基本的配置
下面安裝electron 包了 運行命令
npm i --save-dev electron
你的運行結果和上面的圖片里的信息說明就成功安裝了electron 默認安裝的最新穩(wěn)定的依賴包
前期的工作都做完了,來運行它,看看是否出現(xiàn)我們想要的界面
運行命令
npm start
hello world! 是不是很熟悉,很驚喜,很意外。
出現(xiàn)了平時我們打開windows應用窗口
以上步驟都是構建一個electron的桌面應用的,串口是如何實現(xiàn)的呢?
如果你不熟悉串口是說明,先去補補串口的基本概念和相關信息
串口、COM口是指的物理接口形式(硬件)
你也可以打開設備管理器看到相應的串口,我這里有COM11和COM10 ,串口是成對出現(xiàn)的
了解了說明是串口后,來實現(xiàn)我們的應用串口通信吧
運行命令
npm install serialport
出現(xiàn)serialport 的版本信息 說明已經(jīng)安裝成功
electron 通信或者一些交互都是在node上完成的
查看了文檔后 我們可以在html頁面上
引入serialport包
設置要監(jiān)聽的串口端口 比如COM11
配置寫端口基本信息
serialPost.on 接收發(fā)過來的信息,如果在控制臺上打印出信息,就說明串口通信成功
再次運行electron npm start
打印控臺看到 信息:打印端口成功,正在監(jiān)聽數(shù)據(jù)中,就說明實現(xiàn)了串口的通信最重要一部打開通道
為了驗證是否能通信,我們找個串口精靈 發(fā)送一寫信息 ,再次看控制臺收到了發(fā)送的信息
如圖 在測試串口工具中輸入aaaa, 運行的控制臺收到了aaaa ,說明已經(jīng)成功實現(xiàn)串口通信。
是不是很簡單,是不是很驚喜,是不是你在今后項目當中有需要串口通信的就可以復制粘貼了。
本文將使用 nodejs 的 SerialPort 包來實現(xiàn)串口通訊功能。 Node SerialPort 是一個 JavaScript 庫,用于連接到在 NodeJS 和 Electron 中工作的串行端口,以下是準備環(huán)境:
本文操作過程來自: https://girishjoshi.io/post/access-serialport-from-electron-application-and-creating-gui-for-micropython-repl-on-esp8266/
文檔地址: https://serialport.io/docs/
主對象,使用流式傳輸支持跨平臺的串行端口訪問。
為綁定提供的流式接口。
為nodejs、electron提供跨平臺的綁定支持。
為測試實現(xiàn)模擬綁定功能。
一個typescript 接口用來實現(xiàn)自己的綁定時使用。
解析器用來對原始的二進制數(shù)據(jù)轉換成自己需要的消息格式。 包含以下解析器包,這里不進行詳解: - @serialport/parser-byte-length - @serialport/parser-cctalk - @serialport/parser-delimiter - @serialport/parser-inter-byte-timeout - @serialport/parser-packet-length - @serialport/parser-readline - @serialport/parser-ready - @serialport/parser-regex - @serialport/parser-slip-encoder - @serialport/parser-spacepacket
比較有用的命令行工具,包括: - @serialport/list - @serialport/repl - @serialport/terminal
# Clone the Quick Start repository
$ git clone https://github.com/electron/electron-quick-start
# Go into the repository
$ cd electron-quick-start
# Install the dependencies and run
$ npm install && npm start
npm install --save serialport
因為選擇npm版本不同,這里要對庫進行重編譯,要先安裝 electron-rebuild 工具。
npm install --save-dev electron-rebuild
.\node_modules\.bin\electron-rebuild
npm rebuild
注意原文路徑中用的 / , 我在windows 系統(tǒng),改用 \。
npm install node-gyp electron electron-rebuild serialport --build-from-source
./node_modules/.bin/electron-rebuild
npm start
修改 WebPreferences 如下:
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: true,
contextIsolation: false
}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Electron test serialport</title>
</head>
<body>
<h1>Serial terminal</h1>
<script>
//load serialport module
const {SerialPort} = require('serialport');
SerialPort.list().then(_=>{console.info(_);});
const serialPort = new SerialPort({path:'COM3', baudRate: 115200}, function (err) {
if(err) {
console.error(err);
}
});
console.info(serialPort);
serialPort.write("abc", (err)=>{
if (err) {
return console.log('Error on write: ', err.message)
}
console.log('message written')
});
serialPort.on('error', function(err) {
console.log('Error: ', err.message)
});
serialPort.on('data', function (data){
console.info('data', data);
});
</script>
<script>
require('./renderer.js')
</script>
</body>
</html>
npm start
項目結構:
在這里插入圖片描述
在執(zhí)行electron-rebuild時,可能需要安裝windows-build-tools。新版本的windows-build-tools支持Python3.*版本,但使用Python2.7比較保險。
如果本機沒有Python2.7,則可以使用MiniAnaconda配置虛擬環(huán)境 。 先下載安裝MiniAnaconda工具,然后用命令行創(chuàng)建虛擬環(huán)境 :
conda create -n py27 python=2.7
conda activate py27
直接安裝windows-build-tools大概率會失敗,可按以下流程操作:
在執(zhí)行npm install -g node-gpy后使用命令:
npm install -g --production windows-build-tools@4.0.0
更多關于serialport的功能可參考官方文檔。
串口調試助手是最核心的當然是串口數(shù)據(jù)收發(fā)與顯示的功能,pzh-py-com借助的是pySerial庫實現(xiàn)串口收發(fā)功能,今天痞子衡為大家介紹pySerial是如何在pzh-py-com發(fā)揮功能的。
pySerial是一套基于python實現(xiàn)serial port訪問的庫,該庫的設計者為Chris Liechti,該庫從2001年開始推出,一直持續(xù)更新至今,pzh-py-com使用的是pySerial 3.4。
pySerial的使用非常簡單,可在其官網(wǎng)瀏覽一遍其提供的API: https://pythonhosted.org/pyserial/pyserial_api.html,下面痞子衡整理了比較常用的API如下:
class Serial(SerialBase):
# 初始化串口參數(shù)
def __init__(self, *args, **kwargs):
# 打開串口
def open(self):
# 關閉串口
def close(self):
# 獲取串口打開狀態(tài)
def isOpen(self):
# 設置input_buffer/output_buffer大小
def set_buffer_size(self, rx_size=4096, tx_size=None):
# 獲取input_buffer(接收緩沖區(qū))里的byte數(shù)據(jù)個數(shù)
def inWaiting(self):
# 從串口讀取size個byte數(shù)據(jù)
def read(self, size=1):
# 清空input_buffer
def reset_input_buffer(self):
# 向串口寫入data里所有數(shù)據(jù)
def write(self, data):
# 等待直到output_buffer里的數(shù)據(jù)全部發(fā)送出去
def flush(self):
# 清空output_buffer
def reset_output_buffer(self):
pySerial常用參數(shù)整理如下,需要特別強調的是任何運行時刻對如下參數(shù)進行修改,均是直接應用生效的,不需要重新調用open()和close()去激活,因為參數(shù)的修改在pySerial內部是通過與參數(shù)同名的方法實現(xiàn)的,而這些方法均調用了Serial里的一個叫_reconfigure_port()的方法實現(xiàn)的。
參數(shù)名
功能解釋
備注/可設值
port
設備名
/dev/ttyUSB0 on GNU/Linux or COM3 on Windows
baudrate (int)
波特率
/
bytesize
數(shù)據(jù)位bit個數(shù)
FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS
stopbits
停止位
STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TWO
parity
奇偶校驗位
PARITY_NONE, PARITY_EVEN, PARITY_ODD PARITY_MARK, PARITY_SPACE
timeout (float)
接收超時
None:blocking mode
0: non-blocking mode
x: 超時時間x秒
write_timeout (float)
發(fā)送超時
同timeout
串口功能代碼實現(xiàn)主要分為三大部分:配置功能實現(xiàn)、接收功能實現(xiàn)、發(fā)送功能實現(xiàn)。在實現(xiàn)這些功能之前首先需要import兩個module,分別是serial、threading,serial就是pySerial庫;threading是python自帶線程庫,其具體作用下面代碼里會介紹。
除此以外還定義兩個全局變量,s_serialPort和s_recvInterval,s_serialPort是串口設備object實例,s_recvInterval是線程間隔時間。
import serial
import threading
s_serialPort = serial.Serial()
s_recvInterval = 0.5
串口配置里主要就是實現(xiàn)GUI界面上"Open"按鈕的回調函數(shù),即openClosePort(),軟件剛打開時所有可用Port默認是Close狀態(tài),如果用戶選定了配置參數(shù)(串口號、波特率...),并點擊了"Open"按鈕,此時便會觸發(fā)openClosePort()的執(zhí)行,在openClosePort()里我們需要配置s_serialPort的參數(shù)并打開指定的串口設備。
class mainWin(win.com_win):
def setPort ( self ):
s_serialPort.port = self.m_textCtrl_comPort.GetLineText(0)
def setBaudrate ( self ):
index = self.m_choice_baudrate.GetSelection()
s_serialPort.baudrate = int(self.m_choice_baudrate.GetString(index))
def setDatabits ( self ):
# ...
def setStopbits ( self ):
# ...
def setParitybits ( self ):
# ...
def openClosePort( self, event ):
if s_serialPort.isOpen():
s_serialPort.close()
self.m_button_openClose.SetLabel('Open')
else:
# 獲取GUI配置面板里的輸入值賦給s_serialPort
self.setPort()
self.setBaudrate()
self.setDatabits()
self.setStopbits()
self.setParitybits()
# 打開s_serialPort指定的串口設備
s_serialPort.open()
self.m_button_openClose.SetLabel('Close')
s_serialPort.reset_input_buffer()
s_serialPort.reset_output_buffer()
# 開啟串口接收線程(每0.5秒定時執(zhí)行一次)
threading.Timer(s_recvInterval, self.recvData).start()
上述代碼里需要特別講一下的是串口接收線程,我們知道串口設備s_serialPort一旦打開之后,只要該串口設備的RXD信號線上有數(shù)據(jù)傳輸,pySerial底層會自動將其存入s_serialPort對應的input_buffer里,但并不會主動通知我們。那我們怎么知道input_buffer里有沒有數(shù)據(jù)?此時就需要我們開啟一個定時執(zhí)行的線程,線程里會去查看input_buffer里是否有數(shù)據(jù),如果有數(shù)據(jù)便顯示出來,因此在串口設備打開的同時我們需要創(chuàng)建一個串口接收線程recvData()。
串口接收功能其實在串口配置里已經(jīng)提到了,主要就是串口接收線程recvData()的實現(xiàn),recvData()實現(xiàn)很簡單,只有一個注意點,那就是threading.Timer()的用法,這是個軟件定時器,它只能超時觸發(fā)一次任務的執(zhí)行,如果想讓任務循環(huán)觸發(fā),那么需要在任務本身里添加threading.Timer()的調用。
def clearRecvDisplay( self, event ):
self.m_textCtrl_recv.Clear()
def setRecvFormat( self, event ):
event.Skip()
def recvData( self ):
if s_serialPort.isOpen():
# 獲取input_buffer里的數(shù)據(jù)個數(shù)
num = s_serialPort.inWaiting()
if num != 0:
# 獲取input_buffer里的數(shù)據(jù)并顯示在GUI界面的接收顯示框里
data = s_serialPort.read(num)
self.m_textCtrl_recv.write(data)
# 這一句是線程能夠定時執(zhí)行的關鍵
threading.Timer(s_recvInterval, self.recvData).start()
串口發(fā)送功能相比串口接收功能就簡單多了,串口發(fā)送主要就是實現(xiàn)GUI界面上"Send"按鈕的回調函數(shù),即sendData(),代碼實現(xiàn)比較簡單,不予贅述。
def clearSendDisplay( self, event ):
self.m_textCtrl_send.Clear()
def setSendFormat( self, event ):
event.Skip()
def sendData( self, event ):
if s_serialPort.isOpen():
# 獲取發(fā)送輸入框里的數(shù)據(jù)并通過串口發(fā)送出去
lines = self.m_textCtrl_send.GetNumberOfLines()
for i in range(0, lines):
data = self.m_textCtrl_send.GetLineText(i)
s_serialPort.write(str(data))
else:
self.m_textCtrl_send.Clear()
self.m_textCtrl_send.write('Port is not open')
目前串口收發(fā)與顯示實現(xiàn)均是基于字符方式,即發(fā)送輸入框、接收顯示框里僅支持ASCII碼字符串,關于Char/Hex顯示轉換的功能(setRecvFormat()/setSendFormat())并未加上,后續(xù)優(yōu)化里會進一步做。
至此,串口調試工具pzh-py-com誕生之串口功能實現(xiàn)便介紹完畢了
*請認真填寫需求信息,我們會在24小時內與您取得聯(lián)系。