日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

串口使用stream_使用SerialPort库进行Node物联网项目开发

發(fā)布時間:2025/3/20 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 串口使用stream_使用SerialPort库进行Node物联网项目开发 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

如果說Nodejs將JavaScript的應用從網(wǎng)頁端擴展到了服務器和操作系統(tǒng)端,Electron為JavaScript實現(xiàn)了跨平臺應用的能力,那么SerialPort就是打通JavaScript軟件與硬件的關(guān)鍵部件。著名的Johnny-Five物聯(lián)網(wǎng)平臺開發(fā)包的核心部件就是SerialPort,而Mozilla的WebThings

Gateway物聯(lián)網(wǎng)關(guān)也是在SerialPort基礎上實現(xiàn)的。這是因為,雖然已經(jīng)歷經(jīng)了幾十年光陰,串口在通訊傳輸速度上已經(jīng)遠遠跟不上現(xiàn)代的通訊手段,但由于其廉價、簡便、穩(wěn)定可靠且經(jīng)歷時間驗證的特點,在當前的工業(yè)與民生中仍然具有相當重要的地位,而SerialPort正是作為串口與計算機系統(tǒng)連接的中樞,成為互聯(lián)網(wǎng)開發(fā)利器的JavaScript打通軟件與硬件系統(tǒng)的關(guān)鍵。

更準確來說,SerialPort是運行在Node平臺上的開發(fā)包,其安裝也是通過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 ONLINE

SerialPort包由SerialPort,Bindings,Interfaces和Parsers幾部分組成,并且提供了一些比如串口列表等命令行工具(這些工具在舊版本里是SerialPort的一部分,但是目前版本中都可以獨立運行了)。

1. Bindings

Bindings(綁定)是SerialPort連接軟件與硬件平臺的基礎,一般來說,SerialPort庫會自動探測并與平臺綁定(Binding),不需要人為去調(diào)用Bindings來綁定Linux、Windows或是mac平臺。當然,SerialPort也提供了一個修改Bindings的路徑,這是通過內(nèi)置的Stream包實現(xiàn)的(按照開發(fā)者的意圖,用戶永遠不需要直接操作Bindings包)。

var SerialPort = require('@serialport/stream'); SerialPort.Binding = MyBindingClass;

通常來說,人為修改綁定在使用中并沒有太大便利,但對于調(diào)試則非常重要,這是因為,在調(diào)試時用戶可以修改綁定來調(diào)用一個仿真的串口。

2. Stream Interfaces

SerialPort的接口界面是通過流(Stream)實現(xiàn)的,流也是Nodejs的核心部件之一。在新建SerialPort時,需要提供串口的常規(guī)參數(shù),包括portName端口號,baudRate波特率等等,主要包括下面這些屬性。

/*** @typedef {Object} openOptions * @property {boolean} [autoOpen=true] 此選項為真時會在自動打開串口.* @property {number=} [baudRate=9600] 波特率110~115200,支持自定義(這一點太強大了)* @property {number} [dataBits=8] 數(shù)據(jù)位,可以是8, 7, 6, or 5.* @property {number} [highWaterMark=65536] 數(shù)據(jù)緩沖區(qū)大小,最大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 默認為靜態(tài)屬性`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,使用時可通過監(jiān)聽不同事件處理任務。
一個SrialPort對象支持open,update,close,read,write等方法,用于實現(xiàn)各種串口功能。
需要注意的是,隨著版本的不斷更迭,SerialPort的接口內(nèi)容也不斷有所調(diào)整,編程時要格外小心。例如,通常在新建串口前需要先獲取可用的串口列表。官方提供了靜態(tài)方法SerialPort.list()可以返回當前所有串口列表。但隨著版本更新,官方文檔中的SerialPort已經(jīng)升級為Promise方式,文檔中直接獲取的方法會返回錯誤,需要修改為類似以下形式才能使用。

SerialPort.list().then((ports) => {ports.forEach( (port)=> {console.log(port.comName);console.log(port.pnpId);console.log(port.manufacturer);});}).catch((err)=>{console.log(err);});

3. Parsers

Parsers包繼承自Nodejs Transform Stream,提供了一些實用的串口協(xié)議解析接口,比如對收到的大量串口數(shù)據(jù),要根據(jù)特定的標識進行分割、解析的時候,就可以用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大部分都是分割長數(shù)據(jù),在實際使用中遇到了一個數(shù)據(jù)不足的問題,即由于傳輸速度低,一條消息被分割成好幾條發(fā)送,在收到之后需要組合在一起(有特定的字符表示完整信息的結(jié)尾),在這種情況下,官方的Parsers均不合適,為了實現(xiàn)這一功能,自己手動寫了一個ConcatParser,來實現(xiàn)將幾段數(shù)據(jù)拼接的功能,ConcatParser同時提供了超時和超出緩沖區(qū)長度兩個選項,以保證端口不會處于無限等待的狀態(tài)。ConcatParser的實現(xiàn)如下。

'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 用來列出系統(tǒng)中所有串口,接受格式化、版本等選項,可以通過-h 查看幫助。@serialport/repl提供了一個可以直接通過命令行操作串口的接口,可以通過命令行直接進行串口讀寫等操作。@serialport/terminal提供了一個簡單的接口可以獲取連接在串口上的終端設備的基礎信息。

5. Mock串口仿真器

Mock是SerialPort庫中最好用的功能之一,它通過模擬的硬件串口接口,讓開發(fā)工作可以脫離硬件來完成測試。這在是DD或者TDD開發(fā)過程中是必不可少的。一個簡單的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串口仿真器可以實現(xiàn)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包,用來模擬硬件串口的實現(xiàn)*/ class MockBinding extends AbstractBinding {//如果record為真,這個緩存Buffer中會存入所有寫到虛擬串口中的內(nèi)容,可以檢查串口傳出的數(shù)據(jù)readonly recording: Buffer// 最后一次寫入串口的緩存Bufferreadonly lastWrite: null | Buffer//靜態(tài)方法,用于創(chuàng)建一個虛擬串口static createPort(path: string, opt: { echo?: boolean, record?: boolean, readyData?: Buffer}): void//靜態(tài)方法,用于復位所有虛擬串口static reset(): void// 靜態(tài)方法,用于列出所有虛擬串口static list(): Promise<PortInfo[]>// 從一個虛擬串口Emit(發(fā)出)指定數(shù)據(jù)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> }

總結(jié)

以上是生活随笔為你收集整理的串口使用stream_使用SerialPort库进行Node物联网项目开发的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。