Node-RED編程基礎(chǔ)
【Node-RED與IoT開發(fā)交流】785381620 ,歡迎加入!
前言
Node-RED是一款低代碼編程的平臺, 可以通過可視化編程的方式實現(xiàn)某些特定功能. 但對于許多初次接觸該應(yīng)用的用戶來說, 使用Node-RED編程仍存在一些障礙, 個人認(rèn)為主要是在以下方面:
- 消息模型msg
- 上下文context
- 函數(shù)節(jié)點function.
故在此將以上三點進行詳細(xì)的說明, 希望對各位有所幫助.
在學(xué)習(xí)使用任何軟件/平臺時, 官方文檔永遠(yuǎn)是第一選擇, 你遇到的幾乎所有的問題都可以在官方找到答案, 此外, 對于一些節(jié)點, 你可很方便的從info窗口看到最基礎(chǔ)的指引.
消息模型msg
在Node-RED中, 我們通過連線將不同功能的節(jié)點連接起來.
有些節(jié)點只能接入線, 如debug節(jié)點. 這類節(jié)點只可以接收其它節(jié)點提供的數(shù)據(jù), 而不能直接產(chǎn)生任何數(shù)據(jù).只可以作為輸出使用, 類比揚聲器.
有些節(jié)點只可輸出線, 如inject節(jié)點. 這類節(jié)點, 只可以產(chǎn)生數(shù)據(jù), 而不能接收任何數(shù)據(jù), 只能做輸入使用, 類比麥克風(fēng).
有些節(jié)點即可輸出線, 也可以輸入線, 如function節(jié)點. 此類節(jié)點既可以接收數(shù)據(jù), 也可以輸出數(shù)據(jù), 它一般對輸入數(shù)據(jù)進行處理, 處理完成后, 再從輸出端口輸出.
當(dāng)我們用線將三個節(jié)點進行連接后, 這便成為了一個流.
每一個流通過msg
對象進行傳遞消息, 對象可以理解為一包數(shù)據(jù), 其結(jié)構(gòu)一般如下:
{"_msgid":"e701ad8b.c7bb1","payload":1626087545399,"topic":""}
其中_msgid
指明了消息的ID, payload
指明了消息主體, topic
指明了消息主題.
在這個流中, 我們使用inject節(jié)點產(chǎn)生一個時間戳1626087545399
并將其放到payload的位置(msg.payload = 1626087545399
). 此時消息已經(jīng)變?yōu)?/p>
{"_msgid":"e701ad8b.c7bb1","payload":1626087545399,"topic":""}
該消息作為輸入, 輸入函數(shù)節(jié)點. 函數(shù)
節(jié)點接收到該數(shù)據(jù)后, 進行處理. 函數(shù)
的具體邏輯由我們指定, 在上述流中, 函數(shù)
節(jié)點代碼如下:
return msg;
函數(shù)
節(jié)點中使用JavaScript進行編程, 在該節(jié)點中, 直接將msg
返回, 不做任何修改. 此時消息仍為
{"_msgid":"e701ad8b.c7bb1","payload":1626087545399,"topic":""}
該消息作為函數(shù)
節(jié)點的處理結(jié)果, 輸入debug
節(jié)點. debug節(jié)點在接收到該數(shù)據(jù)后, 將其在調(diào)試窗口輸出.
以上是最為基礎(chǔ)的一個流, 主要是想要說明一點: msg
對象為消息傳遞的載體. 如果想要對數(shù)據(jù)進行處理, 操作這個msg
就可以了.
例如, 我們想要改變上述流程, 通過時間戳獲取當(dāng)前的時間. 我們只需要對函數(shù)
節(jié)點進行修改即可.
例如, 我們將函數(shù)
節(jié)點的內(nèi)容修改如下:
let date = new Date(msg.payload) // 根據(jù)時間戳生成Date對象
let hh = date.getHours() // 通過date對象獲取時分秒
let mm = date.getMinutes()
let ss = date.getSeconds()
msg.payload = hh + ':' + mm + ':' + ss // 拼接時分秒
return msg;
這樣, 時間戳作為輸入, 經(jīng)過以上的處理后就得到對應(yīng)的時分秒了.
但有些時候, 你不是對傳進來的msg
對象進行處理, 或者傳入的msg
不符合你的需要, 那需要對它進行修改.
你可以創(chuàng)造一個新的對象將它返回, 它叫什么名字無所謂.
a = {
a: ' ',
b: ' ',
c: ' '
}
return a;
也可以對原有的msg對象修修補補.
msg.a = 'a' // msg['a'] = 'a' 兩種寫法的作用是相同的
return msg;
那么以一個案例結(jié)束這一部分吧. 我們做過將時間戳轉(zhuǎn)化為dd:mm:ss的格式, 但如果我們也需要返回小時, 分鐘, 秒以供后面的節(jié)點調(diào)用呢. 可以在腦子中簡單的過一下想一下答案. 答案很簡單, 在msg
對象上增添3個鍵值對就可以了.
let date = new Date(msg.payload); // 根據(jù)時間戳生成Date對象
let hh = date.getHours() // 通過date對象獲取時分秒
let mm = date.getMinutes()
let ss = date.getSeconds()
msg.payload = hh + ':' + mm + ':' + ss // 拼接時分秒
msg.hh = hh
msg.mm = mm
msg.ss = ss
return msg;
觸發(fā)后, 你會發(fā)現(xiàn), 我們后面添加的hh,mm,ss并沒有輸出, 這是為什么呢. 這是因為debug
節(jié)點默認(rèn)輸出msg
對象中的payload
鍵值對, 其余的并沒有顯示. 可以通過雙擊debug
節(jié)點修改配置.
這樣就得到我們想要結(jié)果了.
Node-RED的消息模型, 大概就是這樣了, 希望你有所收獲吧, 如果有問題也歡迎在下方提出你的疑問.
上下文Context
對于學(xué)習(xí)過計算機編程的同學(xué)來說, “上下文” 應(yīng)該是個非常非常常見的術(shù)語, 通常用來存儲當(dāng)前操作所處的狀態(tài). 在Node-RED也如此, 并且, Node-RED還將上下文分為了3種作用域, Node/Flow/Global.
Node的作用域是在當(dāng)前節(jié)點, Flow是當(dāng)前流, Global則是全局. 下面展開介紹
Node
你也許會問, 在一個節(jié)點內(nèi)部, 使用Context意義在哪? 直接使用變量不就好了.
我們設(shè)想一個場景, 我們需要記錄某個函數(shù)
節(jié)點的執(zhí)行次數(shù), 當(dāng)達到一定次數(shù)后就不再輸出. 如果用最基礎(chǔ)的變量, 將永遠(yuǎn)不會結(jié)束, 因為每次通過函數(shù)
節(jié)點后, 這個節(jié)點的所有變量都會被清空, 再次執(zhí)行使仍是最初始的狀態(tài), 可以理解為這個節(jié)點是無狀態(tài)的. 這時Context的作用就體現(xiàn)出來了, 我們需要Context去記錄這個節(jié)點的狀態(tài).
具體實現(xiàn)如下:
// 若Context中有count則使用count, 無count使用0
let count = context.get('count')||0;
if(count > 5)
return null
count += 1;
context.set('count',count); // 將count放入Context
msg.count = count;
return msg;
執(zhí)行結(jié)果如下:
在執(zhí)行5次過后, 即使再次使用inject節(jié)點觸發(fā), 也不輸出任何消息.
Flow
與Node類似, Flow存儲整個流的狀態(tài), 這里所指的流并不是幾個節(jié)點串成一條的流, 而是一個面板算作一個流, 例如, 以下5個節(jié)點串成兩條線, 但處一個Tab中, 那么這5個節(jié)點共享同一個Flow的Context.
Flow的應(yīng)用場景很多, 例如, 我們通過MQTT接收到溫濕度傳感器傳來的數(shù)據(jù), 同時, 如果外界通過HTTP協(xié)議訪問溫濕度數(shù)據(jù)時, 我們就可以通過如下Flow去實現(xiàn).
MQTT收到數(shù)據(jù)后, 將會把數(shù)據(jù)放入Flow中, 收到HTTP請求后, 會去Flow中查詢響應(yīng)的數(shù)據(jù)并做返回.
完整的流如下, 可以自行導(dǎo)入查看:
[{"id":"5ae2bdf8.c1b644","type":"mqtt in","z":"77db09d1.403ba8","name":"","topic":"sensor","qos":"2","datatype":"auto","broker":"c97be055.a659d","nl":false,"rap":true,"rh":0,"x":150,"y":4660,"wires":[["16b09f20.3b9281"]]},{"id":"c1aec982.63b988","type":"function","z":"77db09d1.403ba8","name":"","func":"flow.set('humi', msg.payload.humi)\nflow.set('temp', msg.payload.temp)\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":490,"y":4660,"wires":[[]]},{"id":"46e11606.fcc408","type":"http in","z":"77db09d1.403ba8","name":"","url":"/sensor","method":"get","upload":false,"swaggerDoc":"","x":170,"y":4720,"wires":[["66aa5722.6b53f8"]]},{"id":"66aa5722.6b53f8","type":"function","z":"77db09d1.403ba8","name":"","func":"let humi = flow.get('humi') || 0\nlet temp = flow.get('temp') || 0\nmsg.payload = {\n humi, temp\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":330,"y":4720,"wires":[["c24d1af.43c51e8"]]},{"id":"c24d1af.43c51e8","type":"http response","z":"77db09d1.403ba8","name":"","statusCode":"","headers":{},"x":490,"y":4720,"wires":[]},{"id":"16b09f20.3b9281","type":"json","z":"77db09d1.403ba8","name":"","property":"payload","action":"","pretty":false,"x":330,"y":4660,"wires":[["c1aec982.63b988"]]},{"id":"c97be055.a659d","type":"mqtt-broker","name":"","broker":"www.carwasher.com.cn","port":"1883","clientid":"","usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willPayload":"","willMsg":{},"sessionExpiry":""}]
Global
與前兩種類似, Global是解決跨流數(shù)據(jù)共享的. 在函數(shù)
節(jié)點中主要是以下兩個API
let bar = global.get('foo') || 0 //有則獲取, 無則為0
global.set('foo', 'bar') // 將foo寫入值'bar'
除了在函數(shù)
節(jié)點中通過代碼的方式使用Context, 也可以在inject
節(jié)點, change
節(jié)點等節(jié)點中使用.
Node-RED的上下文Context, 為我們的編程提供了很大的便利, 它是實現(xiàn)復(fù)雜應(yīng)用必不可少的一部分, 就如同其它編程語言中的局部變量/全局變量, 少了這個特性, 許許多多的功能幾乎實現(xiàn)不了.
函數(shù)節(jié)點function
在Node-RED中, 很少使用代碼, 所以要寫代碼的地方就成了比較困難的一部分. 函數(shù)節(jié)點使用JavaScript編程, 支持大部分API, 上面有穿插的使用, 但并不系統(tǒng), 下面就對這個節(jié)點進行一個較為系統(tǒng)的梳理. 官方文檔中也有很細(xì)致的說明, 喜歡看官方文檔的可以直接參考.
消息是通過msg對象進行傳遞的, 通常會有一個payload的鍵值對包含消息的主體, 其他節(jié)點會msg對象上添加新的鍵值對.
上文提到, 直接將msg返回, 不做任何處理. 同時這個對象并無強制命名.
return msg;
// 以下等價.
// let message = msg;
// return message;
如果返回null
, 則停止傳遞.
return null;
// 以下等價.
// return ;
并且, 函數(shù)節(jié)點必須返回一個對象或者null, 如果返回字符串/數(shù)字等, 會產(chǎn)生一個錯誤.
多端口輸出
我們可以看到, 函數(shù)節(jié)點是可以設(shè)置多輸出的, 此時返回的對象需要是一個與輸出數(shù)相同長度的數(shù)組. 輸出端口數(shù)也可以通過node.outputCount
獲取
返回的數(shù)組按序依次從對應(yīng)的端口輸出.例如:
if (msg.topic === "banana") {
return [ null, msg ];
} else {
return [ msg, null ];
}
如果msg.topic
為banana
則從2號口輸出, 非banana
則從1號口輸出
異步發(fā)送消息
通常我們使用節(jié)點發(fā)送消息都是同步的, 何為同步呢? 這是一個比較直觀的概念, 單向一車道堵車了, 你只能同步的等待, 前面不走我們就沒辦法走, 這也許損耗了極大的性能. 而現(xiàn)在應(yīng)用更廣的是異步模式, 我們可以不用等待處理結(jié)果, 處理結(jié)束了通知我即可, 就像你安排別人做個事情, 安排完了, 就去忙別的事了, 他做好事情再來通知你. 例如:
setTimeout(()=>{
msg.payload = 'notify'
node.send(msg)
node.done()
}, 1000)
return;
我們使用setTimeout
出啟動一個延時任務(wù), 消息不直接返回, 在1000ms后通過node.send
返回.
同時, 我們還經(jīng)常遇到一個非常常見的需求, 我們需要將一個數(shù)組的內(nèi)容配合MySQL節(jié)點插入到數(shù)據(jù)庫中, 我們就可以使用node.send
來實現(xiàn)這個功能.
data = [25.0, 25.3, 25.1, 25.4, 25.6]
data.forEach((item)=>{
sql = `insert into sensors (temp) values (${item}) `
msg.topic = sql
node.send(msg)
})
return ;
執(zhí)行結(jié)果:
在異步任務(wù)中, 還有一個較為重要的概念則是回調(diào), 回調(diào)即是返回調(diào)用, 別人干完事了回來通知. 但別人通知總需要一個入口, 這個入口稱之為event
, 對event
的處理成為event handler
或者callback
, 此處并不嚴(yán)謹(jǐn), 僅供理解. 例如, 我們在使用Context存儲數(shù)據(jù)的時候, 如果其存儲在文件系統(tǒng)上, IO操作要慢許多, 如果使用同步的方式獲取, 會極大地影響性能, 此時我們就會通過異步的方式獲取. 實現(xiàn)如下:
flow.get(['humi', 'temp'], (err, humi, temp) => { // 獲取多個context
if(err) return
node.send({
payload: {
humi: humi || 0,
temp: temp || 0
}
})
node.done()
})
return ;
其中的arrow function就是一個回調(diào)函數(shù).
節(jié)點狀態(tài)
Node-RED也提供了狀態(tài)顯示的API, 例如調(diào)用
node.status({fill:"green",shape:"dot",text:"完成"});
就會出現(xiàn)如下效果:
具體的API可以參考官方文檔
使用外部模塊
[注] 需要Node-RED版本>=1.3.0
當(dāng)我們遇到一些需求需要使用別人造好的輪子時, 我們就不得不使用外部模塊. 函數(shù)節(jié)點也提供了該功能. 首先需要去.node-red
文件夾下找到settings.js
文件, 如果不知道該文件在哪里, 可以從啟動日志找到, 如下:
在文件中添加functionExternalModules: true,
如圖:
此時啟動Node-RED, 雙擊函數(shù)節(jié)點, 進入Setup就可以看到如下畫面:
你可以通過左下角添加
按鈕, 添加需要使用的模塊, 第三方模塊就會被自動安裝到.node-red/externalModules/
中. 以一個小例子結(jié)束本部分內(nèi)容: 有時我們需要獲取網(wǎng)卡信息, 很多人都通過安裝第三方節(jié)點實現(xiàn), 但其實, 并不需要第三方節(jié)點就可以實現(xiàn).
我們引入了os模塊, os模塊提供了networkInterfaces
API, 我們可以通過該模塊獲取網(wǎng)卡信息.
en0 = os.networkInterfaces().en0
ipv4 = en0[1] // en0[0]為ipv6, 需要根據(jù)自身環(huán)境修改
ip = ipv4.address
mac = ipv4.mac
msg.payload = {
ip, mac
}
return msg;
執(zhí)行結(jié)果如下:
有了這個feature后, Node-RED的功能得到了極大的增強, 舉個例子, 我們可以通過引入johnny-five
, 通過函數(shù)節(jié)點直接對硬件進行編程. 等等.
如果需要全局引入某個模塊可以通過修改functionGlobalContext
實現(xiàn).
例如, 我們同樣在settings.js
中引入os
模塊, 重啟Node-RED, 我們就不需要再函數(shù)節(jié)點的setup中引入os, 僅需要在函數(shù)中通過os = global.get('os')
引入即可.
事件記錄
在函數(shù)節(jié)點中可以使用node.log
/node.warn
/ node.error
記錄某些事件, 方便調(diào)試.文章來源:http://www.zghlxwxcb.cn/news/detail-409035.html
期待與您成為朋友文章來源地址http://www.zghlxwxcb.cn/news/detail-409035.html
到了這里,關(guān)于Node-RED編程基礎(chǔ)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!