串口使用stream_使用SerialPort库进行Node物联网项目开发
如果說Nodejs將JavaScript的應用從網頁端擴展到了服務器和操作系統端,Electron為JavaScript實現了跨平臺應用的能力,那么SerialPort就是打通JavaScript軟件與硬件的關鍵部件。著名的Johnny-Five物聯網平臺開發包的核心部件就是SerialPort,而Mozilla的WebThings
Gateway物聯網關也是在SerialPort基礎上實現的。這是因為,雖然已經歷經了幾十年光陰,串口在通訊傳輸速度上已經遠遠跟不上現代的通訊手段,但由于其廉價、簡便、穩定可靠且經歷時間驗證的特點,在當前的工業與民生中仍然具有相當重要的地位,而SerialPort正是作為串口與計算機系統連接的中樞,成為互聯網開發利器的JavaScript打通軟件與硬件系統的關鍵。
更準確來說,SerialPort是運行在Node平臺上的開發包,其安裝也是通過npm install完成的。在SerialPort官方首頁有一段簡單的應用例程。裝完SerialPort后,用這段程序就可以立即上手(當然,你還需要一個串口終端設備并且編寫了終端部分的程序,如果沒有,也可以通過本文后面的仿真器模擬出來一個。)
const SerialPort = require('serialport') const Readline = require('@serialport/parser-readline') const port = new SerialPort(path, { baudRate: 256000 })const parser = new Readline() port.pipe(parser)parser.on('data', line => console.log(`> ${line}`)) port.write('ROBOT POWER ONn') //> ROBOT ONLINESerialPort包由SerialPort,Bindings,Interfaces和Parsers幾部分組成,并且提供了一些比如串口列表等命令行工具(這些工具在舊版本里是SerialPort的一部分,但是目前版本中都可以獨立運行了)。
1. Bindings
Bindings(綁定)是SerialPort連接軟件與硬件平臺的基礎,一般來說,SerialPort庫會自動探測并與平臺綁定(Binding),不需要人為去調用Bindings來綁定Linux、Windows或是mac平臺。當然,SerialPort也提供了一個修改Bindings的路徑,這是通過內置的Stream包實現的(按照開發者的意圖,用戶永遠不需要直接操作Bindings包)。
var SerialPort = require('@serialport/stream'); SerialPort.Binding = MyBindingClass;通常來說,人為修改綁定在使用中并沒有太大便利,但對于調試則非常重要,這是因為,在調試時用戶可以修改綁定來調用一個仿真的串口。
2. Stream Interfaces
SerialPort的接口界面是通過流(Stream)實現的,流也是Nodejs的核心部件之一。在新建SerialPort時,需要提供串口的常規參數,包括portName端口號,baudRate波特率等等,主要包括下面這些屬性。
/*** @typedef {Object} openOptions * @property {boolean} [autoOpen=true] 此選項為真時會在自動打開串口.* @property {number=} [baudRate=9600] 波特率110~115200,支持自定義(這一點太強大了)* @property {number} [dataBits=8] 數據位,可以是8, 7, 6, or 5.* @property {number} [highWaterMark=65536] 數據緩沖區大小,最大64k.* @property {boolean} [lock=true] 鎖定串口禁止其他設備使用,在windows平臺下只能為true.* @property {number} [stopBits=1] 停止位: 1 or 2.* @property {string} [parity=none] 校驗位:'none','even','mark','odd','space'.* @property {boolean} [rtscts=false] 流(flow)控制設置,以下幾個都是* @property {boolean} [xon=false] * @property {boolean} [xoff=false] * @property {boolean} [xany=false]* @property {object=} bindingOptions 綁定選項,一般不需要設置* @property {Binding=} Binding 默認為靜態屬性`Serialport.Binding`.* @property {number} [bindingOptions.vmin=1] 參見linux下 termios命令* @property {number} [bindingOptions.vtime=0] */一個SerialPort對象支持的屬性包括baudRate,binding,isOpen和path,其中除了baudRate可以通過update方法修改為,其他屬性均為只讀。
一個SerialPort對象支持的事件包括open,error,close,data,drain,使用時可通過監聽不同事件處理任務。
一個SrialPort對象支持open,update,close,read,write等方法,用于實現各種串口功能。
需要注意的是,隨著版本的不斷更迭,SerialPort的接口內容也不斷有所調整,編程時要格外小心。例如,通常在新建串口前需要先獲取可用的串口列表。官方提供了靜態方法SerialPort.list()可以返回當前所有串口列表。但隨著版本更新,官方文檔中的SerialPort已經升級為Promise方式,文檔中直接獲取的方法會返回錯誤,需要修改為類似以下形式才能使用。
3. Parsers
Parsers包繼承自Nodejs Transform Stream,提供了一些實用的串口協議解析接口,比如對收到的大量串口數據,要根據特定的標識進行分割、解析的時候,就可以用Delimiter解析器,類似的解析器還包括Readline(分行讀取),ByteLength(按長度截取),InterByteTimeout(超時)以及功能強大的Regex(正則表達式)解析器等等。
const SerialPort = require('serialport') const Readline = require('@serialport/parser-readline') const port = new SerialPort('/dev/tty-usbserial1') const parser = new Readline() port.pipe(parser) parser.on('data', console.log) port.write('ROBOT PLEASE RESPONDn') //也可以簡化為 const parser = port.pipe(new Readline());SerialPort的Parsers大部分都是分割長數據,在實際使用中遇到了一個數據不足的問題,即由于傳輸速度低,一條消息被分割成好幾條發送,在收到之后需要組合在一起(有特定的字符表示完整信息的結尾),在這種情況下,官方的Parsers均不合適,為了實現這一功能,自己手動寫了一個ConcatParser,來實現將幾段數據拼接的功能,ConcatParser同時提供了超時和超出緩沖區長度兩個選項,以保證端口不會處于無限等待的狀態。ConcatParser的實現如下。
'use strict'; const { Transform } = require('stream');class ConcatParser extends Transform {constructor(options = {}) {super(options);try {if (typeof options.boundary === 'undefined') {throw new TypeError('"boundary" is not a bufferable object');}if (options.boundary.length === 0) {throw new TypeError('"boundary" has a 0 or undefined length');}this.includeBoundary = typeof options.includeBoundary !== 'undefined' ? options.includeBoundary : true;this.interval = typeof options.interval !== 'undefined' ? options.interval : 3000;this.maxBufferSize = typeof options.maxBufferSize !== 'undefined' ? options.maxBufferSize : 65535;this.intervalID = -1;this.boundary = Buffer.from(options.boundary);this.buffer = Buffer.alloc(0);} catch (error) {throw new Error('Init concatparser error');}}_transform(chunk, encoding, cb) {clearTimeout(this.intervalID);let data = Buffer.concat([this.buffer, chunk]),dataLength = data.length,position;if (dataLength >= this.maxBufferSize) {this.buffer = data.slice(0, this.maxBufferSize);data = Buffer.alloc(0);this.emitData();} else if ((position = data.indexOf(this.boundary)) !== -1) {this.buffer = data.slice(0, position + (this.includeBoundary ? this.boundary.length : 0));data = Buffer.alloc(0);this.emitData();}this.buffer = data;this.intervalID = setTimeout(this.emitData.bind(this), this.interval);cb();}emitData() {clearTimeout(this.intervalID);if (this.buffer.length > 0) {this.push(this.buffer);}this.buffer = Buffer.alloc(0);}_flush(cb) {this.emitData();cb();} }module.exports = ConcatParser;4. 命令行接口
SerialPort庫提供了幾個命令行接口,可以通過npx直接運行。幾個接口分別是SerialPort List,SerialPort REPL和SerialPort Terminal,使用方法為:
npx @serialport/list [options] //可能需要先安裝 npm @serialport/list npx @serialport/repl <port> npx @serialport/terminal -p <port> [options]@serialport/list 用來列出系統中所有串口,接受格式化、版本等選項,可以通過-h 查看幫助。@serialport/repl提供了一個可以直接通過命令行操作串口的接口,可以通過命令行直接進行串口讀寫等操作。@serialport/terminal提供了一個簡單的接口可以獲取連接在串口上的終端設備的基礎信息。
5. Mock串口仿真器
Mock是SerialPort庫中最好用的功能之一,它通過模擬的硬件串口接口,讓開發工作可以脫離硬件來完成測試。這在是DD或者TDD開發過程中是必不可少的。一個簡單的Mock串口仿真器使用方法如下:
const SerialPort = require('@serialport/stream') const MockBinding = require('@serialport/binding-mock')SerialPort.Binding = MockBinding// Create a port and enable the echo and recording. MockBinding.createPort('/dev/ROBOT', { echo: true, record: true }) const port = new SerialPort('/dev/ROBOT')Mock串口仿真器可以實現SerialPort庫中所有功能的測試,通過Mock的源碼可以看出這些接口及測試時檢測的途徑。
const AbstractBinding = require('@serialport/binding-abstract') const debug = require('debug')('serialport/binding-mock')let ports = {} let serialNumber = 0function resolveNextTick(value) {return new Promise(resolve => process.nextTick(() => resolve(value))) }/*** Mock包,用來模擬硬件串口的實現*/ class MockBinding extends AbstractBinding {//如果record為真,這個緩存Buffer中會存入所有寫到虛擬串口中的內容,可以檢查串口傳出的數據readonly recording: Buffer// 最后一次寫入串口的緩存Bufferreadonly lastWrite: null | Buffer//靜態方法,用于創建一個虛擬串口static createPort(path: string, opt: { echo?: boolean, record?: boolean, readyData?: Buffer}): void//靜態方法,用于復位所有虛擬串口static reset(): void// 靜態方法,用于列出所有虛擬串口static list(): Promise<PortInfo[]>// 從一個虛擬串口Emit(發出)指定數據emitData(data: Buffer | string | number[])// 其他支持的標準串口接口方法open(path: string, opt: OpenOpts): Promise<void>close(): Promise<void>read(buffer: Buffer, offset: number, length: number): Promise<Buffer>write(buffer: Buffer): Promise<void>update(options: { baudRate: number }): Promise<void>set(options): Promise<void>get(): Promise<Flags>getBaudRate(): Promise<number>flush(): Promise<void>drain(): Promise<void> }總結
以上是生活随笔為你收集整理的串口使用stream_使用SerialPort库进行Node物联网项目开发的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 能直接挂在iis的动静态网站_网站优化思
- 下一篇: python递归出口怎么写_Python