node-red教程5函数节点
发送消息
这个函数既可以使⽤“return”向流中的下⼀个节点传递消息,也可以调⽤(messages)。
它可以返回或是发送
⼀个单⼀的消息对象——传递给连接到第⼀个输出的节点
⼀组消息对象——传递给连接到相应输出的节点
如果数组的任何元素本⾝都是⼀组消息,那么多条消息就会被发送到相应的输出。
如果返回null,⽆论是单独还是作为数组的元素,都不会传递任何消息。
⽇志和错误处理
要记录任何信息或报告错误,可以使⽤以下功能:
(“Logmessage”)
(“Warning”)
(“Error”)
1
2
3
Catch节点也可以⽤来处理错误。为了调⽤Catch节点,将msg作为第⼆个参数传递给:
5.2使⽤函数控件实现多个输出
前边使⽤switch控件时我们已经实现了根据不同的条件,来实现多个输出。Switch控件是⼀种功能控件,⽽函数控件也是功能控件。
甚⾄可以说,函数控件可以通过编程实现所有功能控件的功能。接下来,我们尝试⽤函数控件实现多个输出。
5.2.1最简单的函数控件⽤法
拖⼊⼀个inject、function与debug,⽆需任何修改,直接⽤线连接三个节点。
这⾥写图⽚描述
部署,并点击inject的输⼊按钮,观察调试窗⼝。可以看到debug节点打印的调试信息。
这⾥写图⽚描述
你可能会说,什么嘛,不加这个函数节点,直接连接inject与debug节点,不也是这个效果嘛!这个函数节点有什么⽤?
它的作⽤就是,把消息原封不动输出。原封不动的输出,也是⼀种功能,最简单的功能。
我们双击函数节点,来看⼀下⾥边的内容。
这⾥写图⽚描述
只有⼀⾏代码,returnmsg,返回消息。
回过头来,看函数控件的说明信息。⽤来处理节点接收到的消息,消息作为⼀个名为msg的对象传⼊。可以使⽤“return”向下⼀个节
点传递消息。也就是说,函数控件的输⼊时msg对象,输出也是msg对象。在输出之前,可以对msg对象进⾏处理。所谓对象,意思是这
是⼀个msg实例,是具体的数据,不是抽象的,更不是msg跟异性朋友处对象。
既然可以原封不动返回msg,当然也可以不返回。只要把语句改为returnnull即可。Null的意思是空值,也可以说没有值。修改代码
为:
这⾥写图⽚描述
重新部署,⽆论现在如何点击inject节点的输⼊按钮,debug节点都不会打印出任何消息了。
5.2.2在函数节点中创建并返回多个消息
函数控件中,除了可以处理输⼊的msg对象以外,也可以⾃⼰定义⼀些msg对象。另外,说明⽂件中,有介绍说function可以返回⼀
个对象,或是⽤数组传递⼀组对象。我们先尝试⾃⾏建⽴⼀组对象并传递。
修改函数节点:取个合适的名称,添加如下代码,并把输出改为两路。
varmsg1={payload:“firstoutofoutput1”};
varmsg2={payload:“condoutofoutput1”};
varmsg3={payload:“thirdoutofoutput1”};
varmsg4={payload:“onlymessagefromoutput2”};
return[[msg1,msg2,msg3],msg4];
1
2
3
4
5
这⾥写图⽚描述
拖⼊两个debug节点,命名,并分别接到函数节点的两路输出去。然后点击inject的按钮,观察调试窗⼝的现象。
这⾥写图⽚描述
这⾥写图⽚描述
我们发现,OUT1节点⼀⼝⽓收到了3条消息,OUT2节点收到了⼀条消息,且消息的内容我们刚刚输⼊的代码有关系。跟输⼊的消息没有
任何关系,因为输⼊的是时间戳。
关注最后⼀句并对⽐分析:
returnmsg;//⼀个返回值
return[[msg1,msg2,msg3],msg4];//⼀组返回值
1
2
可以得出结论,如果想得到多个返回值,需要把返回值放到英⽂的中括号⾥。在3.1.3⼩节⾥,恰好在inject的内容⾥,输⼊数组时,也
是放在中括号⾥。这说明,多个返回值需要放到⼀个数组中,数组⽤中括号括起来,⽤逗号间隔开。⽽数组元素的顺序,也就是输出对象的
顺序,⽐如刚刚的OUT1收到的是msg1,msg2与msg3,⽽OUT2收到的msg4。
另外,虽然名为数组(array),但是其边的元素并不是数字,⽽是msg对象,所以,更贴切的叫法应该是:对象组。
这⾥写图⽚描述
这个例⼦代码如下:
[{“id”:“09a8”,“type”:“function”,“z”:“a1f259d6.8791a8”,“name”:“两路输出”,“func”:“var
msg1={payload:“firstoutofoutput1”};nvarmsg2={payload:“condoutofoutput1”};nvarmsg3={
payload:“thirdoutofoutput1”};nvarmsg4={payload:“onlymessagefromoutput2”};nreturn[[msg1,msg2,
msg3],msg4];”,“outputs”:2,“noerr”:0,“x”:360,“y”:180,“wires”:[[“6144cc3f.854e34”],
[“146c”]]},
{“id”:“e49b03a7.395d1”,“type”:“inject”,“z”:“a1f259d6.8791a8”,“name”:"",“topic”:"",“payload”:"",“payloa
dType”:“date”,“repeat”:"",“crontab”:"",“once”:fal,“onceDelay”:0.1,“x”:200,“y”:180,“wires”:
[[“09a8”]]},
{“id”:“6144cc3f.854e34”,“type”:“debug”,“z”:“a1f259d6.8791a8”,“name”:“OUT1”,“active”:true,“tosideb
ar”:true,“console”:fal,“tostatus”:fal,“complete”:“payload”,“x”:510,“y”:160,“wires”:[]},
{“id”:“146c”,“type”:“debug”,“z”:“a1f259d6.8791a8”,“name”:“OUT2”,“active”:true,“tosideb
ar”:true,“console”:fal,“tostatus”:fal,“complete”:“payload”,“x”:510,“y”:220,“wires”:[]}]
1
5.2.3通过判断语句进⾏数据分类
Switch控件可以进⾏数据的分流,它属于功能控件。函数控件是功能控件⾥,功能最强的,它可以实现switch控件的功能。
第3章4节讲解switch控件时,使⽤过判断数字的例⼦,这⾥⽤函数控件来实现它。
输⼊与输出不变,把函数节点替换掉switch节点,并修改代码如下:
这⾥写图⽚描述
分别点击3个inject节点的输⼊按钮。可以再调试窗⼝观察到以下现象。
这⾥写图⽚描述
可以看出,所有数据都根据判断条件正确输出,说明我们的任务完成了。代码如下:
[{“id”:“50a42dcc.f47e44”,“type”:“inject”,“z”:“899b4619.356a88”,“name”:"",“topic”:"",“payload”:“100”,
“payloadType”:“num”,“repeat”:"",“crontab”:"",“once”:fal,“onceDelay”:0.1,“x”:330,“y”:160,“wires”:
[[“d616421c.69733”]]},
{“id”:“ea00b43e.c61ba8”,“type”:“inject”,“z”:“899b4619.356a88”,“name”:"",“topic”:"",“payload”:“10”,“
payloadType”:“num”,“repeat”:"",“crontab”:"",“once”:fal,“onceDelay”:0.1,“x”:330,“y”:220,“wires”:
[[“d616421c.69733”]]},
{“id”:“370ac143.d7c11e”,“type”:“inject”,“z”:“899b4619.356a88”,“name”:"",“topic”:"",“payload”:“1”,“p
ayloadType”:“num”,“repeat”:"",“crontab”:"",“once”:fal,“onceDelay”:0.1,“x”:330,“y”:280,“wires”:
[[“d616421c.69733”]]},
{“id”:“3e98”,“type”:“debug”,“z”:“899b4619.356a88”,“name”:“OUT1”,“active”:true,“toside
bar”:true,“console”:fal,“tostatus”:fal,“complete”:“payload”,“x”:750,“y”:160,“wires”:[]},
{“id”:“22fba15b.22857e”,“type”:“debug”,“z”:“899b4619.356a88”,“name”:“OUT2”,“active”:true,“toside
bar”:true,“console”:fal,“tostatus”:fal,“complete”:“payload”,“x”:750,“y”:220,“wires”:[]},
{“id”:“82f1829b.b1b8a”,“type”:“debug”,“z”:“899b4619.356a88”,“name”:“OUT3”,“active”:true,“tosideb
ar”:true,“console”:fal,“tostatus”:fal,“complete”:“payload”,“x”:750,“y”:280,“wires”:[]},
{“id”:“d616421c.69733”,“type”:“function”,“z”:“899b4619.356a88”,“name”:“判断数字”,“func”:“if
(d>10){nreturn[msg,null,null];n}elif(d===10){nreturn[null,msg,null];n}el{nreturn
[null,null,msg];n}”,“outputs”:3,“noerr”:0,“x”:540,“y”:220,“wires”:[[“3e98”],
[“22fba15b.22857e”],[“82f1829b.b1b8a”]]}]
1
然后看⼀看通过主题来判断的例⼦。跟判断数字没有多⼤的不同,只需要把被判断的条件改为d就可以。
这⾥写图⽚描述
这段代码出现了⼀个indexOf()函数,它的作⽤就是检查⼀下调⽤它的字符串,有没有包含括号⾥的字符(或字符串)。如果包含的
话,返回字符在字符串中⾸次出现的位置。如果没有包含的话,返回-1。所以,if(f(“e”)!=-1)在包含的情况下成
⽴。如图配置并部署。
这⾥写图⽚描述
分别输⼊3个inject节点,结果如下:
这⾥写图⽚描述
这是跟3.4.4⼩节中,在选择“接受第⼀条匹配消息后停⽌”的情况下,结果⼀样。
代码如下:
[{“id”:“e98b2a01.0b6228”,“type”:“function”,“z”:“a1f259d6.8791a8”,“name”:“判断主题”,“func”:“if
(===“apple”){nreturn[msg,null,null];n}nif(===“banana”){nreturn[null,msg,
null];n}nif(f(“e”)!=-1){nreturn[null,null,msg
];n}n”,“outputs”:3,“noerr”:0,“x”:340,“y”:160,“wires”:[[“7cfcaea9.f501f”],[“6997f0e2.7d67d”],
[“95d3ab47.6f79f8”]]},
{“id”:“77880a51.e8f324”,“type”:“inject”,“z”:“a1f259d6.8791a8”,“name”:"",“topic”:“apple”,“payload”:“
apple”,“payloadType”:“str”,“repeat”:"",“crontab”:"",“once”:fal,“onceDelay”:0.1,“x”:130,“y”:100,“wires”:
[[“e98b2a01.0b6228”]]},
{“id”:“98875d47.089c5”,“type”:“inject”,“z”:“a1f259d6.8791a8”,“name”:"",“topic”:“banana”,“payload”:
“banana”,“payloadType”:“str”,“repeat”:"",“crontab”:"",“once”:fal,“onceDelay”:0.1,“x”:140,“y”:160,“wire
s”:[[“e98b2a01.0b6228”]]},
{“id”:“a5a2ecfd.7f28f”,“type”:“inject”,“z”:“a1f259d6.8791a8”,“name”:"",“topic”:“orange”,“payload”:“o
range”,“payloadType”:“str”,“repeat”:"",“crontab”:"",“once”:fal,“onceDelay”:0.1,“x”:140,“y”:220,“wires”:
[[“e98b2a01.0b6228”]]},
{“id”:“7cfcaea9.f501f”,“type”:“debug”,“z”:“a1f259d6.8791a8”,“name”:“OUT1”,“active”:true,“tosidebar
”:true,“console”:fal,“tostatus”:fal,“complete”:“payload”,“x”:530,“y”:100,“wires”:[]},
{“id”:“6997f0e2.7d67d”,“type”:“debug”,“z”:“a1f259d6.8791a8”,“name”:“OUT2”,“active”:true,“tosideba
r”:true,“console”:fal,“tostatus”:fal,“complete”:“payload”,“x”:530,“y”:160,“wires”:[]},
{“id”:“95d3ab47.6f79f8”,“type”:“debug”,“z”:“a1f259d6.8791a8”,“name”:“OUT3”,“active”:true,“tosideb
ar”:true,“console”:fal,“tostatus”:fal,“complete”:“payload”,“x”:530,“y”:220,“wires”:[]}]
1
思考⼀下,apple显然是包括了字符“e”的,但是点击apple时,为什么OUT3没有收到apple的数据?
这是因为,编程的函数有⼀个特点:见到return以后函数就结束了,即便return之后还有没执⾏的代码。if(===
“apple”)成⽴以后就执⾏了return,当然不会管有没有包含“e”的判断条件了。
如果你想实现apple既要输出给OUT1,⼜要输出给OUT3的话,可以使⽤⼀个数组,在满⾜if(===“apple”)时,元素1
储存apple;在满⾜if(f(“e”)!=-1)时,元素3存储apple,只使⽤⼀次return,来返回数组就可以了。你可以尝试⾃
⼰实现它。
后边也会⽤到函数的nd⽅法。
5.3使⽤函数控件处理数据
上⼀⼩节使⽤函数控件进⾏判断与多个输出的情况下,函数控件可能还没有switch好⽤。但是在这⼀节,函数控件的优势就能完全体现
出来。接下来使⽤函数控件可以进⾏数据的处理。
5.3.1单个数字的计算
接下来尝试⽤函数节点对数字进⾏计算。拖⼊inject节点,并把输⼊的内容改为数字;拖⼊debug节点⽤来观察现象,拖⼊函数节点并
做如下修改:
这⾥写图⽚描述
其中,varnewMsg={payload:d*2}语句的意思是,新建⼀个对象,名为newMsg,它有payload属性,并且payload
的值为d的两倍。最后返回的是newMsg,也就是新建的这个对象。
这⾥写图⽚描述
部署并运⾏,可以看到现象是,调试窗⼝显⽰的数值是输⼊的两倍。说明函数节点的功能实现了。
这⾥写图⽚描述
我们知道,消息是通过msg对象传过来的,可不可以不新建⼀个对象,直接修改d呢?当然是可以的。
将函数节点内的代码修改为
d=d*2;
returnmsg;
1
2
部署,可以发现结果⼀样正确。由于这个代码实际的处理顺序是从右向左,也就是d先乘以2,再赋值给d。出
于⽅便理解的⾓度,我们总是⽤⼀个变量来暂时储存⼀下d计算过的值。代码修改为:
vartemp=d*2;
d=temp;
returnmsg;
1
2
3
这段程序保存如下:
[{“id”:“d5052172.91827”,“type”:“inject”,“z”:“a1f259d6.8791a8”,“name”:"",“topic”:"",“payload”:“2333”
,“payloadType”:“num”,“repeat”:"",“crontab”:"",“once”:fal,“onceDelay”:0.1,“x”:140,“y”:180,“wires”:
[[“ecfadc58.b48d4”]]},{“id”:“ecfadc58.b48d4”,“type”:“function”,“z”:“a1f259d6.8791a8”,“name”:“乘以
⼆”,“func”:“vartemp=d*2;d=temp;nreturn
msg;”,“outputs”:1,“noerr”:0,“x”:310,“y”:180,“wires”:[[“65b”]]},
{“id”:“65b”,“type”:“debug”,“z”:“a1f259d6.8791a8”,“name”:"",“active”:true,“tosidebar”:true,
“console”:fal,“tostatus”:fal,“complete”:“fal”,“x”:520,“y”:180,“wires”:[]}]
1
你可以⾃⼰来试⼀下除法该怎么应⽤,将计算的代码分别修改并观察结果。
vartemp=d%100;
vartemp=d/100;
1
2
5.3.2使⽤数组进⾏数据的截取与组装
通常,在实际通信的时候不会只传递⼀个数组,信息的传递依赖于数组,数组中不同位置的数字也会有不同的含义,例如,第⼀个数字
表⽰ID,第⼆个数字表⽰数组长度,第三个数字表⽰温度等等,⼀般会有⼀个通信协议来规定。所以,如何取出有⽤的数据,或者按要求把
数据进⾏组装很重要。
现在,假如收到了⼀串数据,数据共16位,我们只⽤到后8位,前边的8位都不要了,如何来操作?
这⾥写图⽚描述
通过分析可以得知:
ARR2[0]=ARR1[8];
ARR2[1]=ARR1[9];
……
ARR2[7]=ARR1[15];
1
2
3
4
但是写代码的时候⼀般不会这么复制粘贴。这是循环操作,⽤for语句可以增加效率。
vartemp=newBuffer([0,0,0,0,0,0,0,0]);
for(vari=0;i<8;i++)
temp[i]=d[i+8];
d=temp;
returnmsg;
1
2
3
4
5
我们新建⼀个temp数组,有8个元素并初始化为0;
通过for循环,把d[i+8]的值赋给temp[i],i可以是0到7。
然后板temp作为msg的payload返回。
接下来添加inject与debug节点搭⼀个测试环境。Inject节点内容为数组。
这⾥写图⽚描述
函数节点内容
这⾥写图⽚描述
整个流程如下
这⾥写图⽚描述
[{“id”:“d5052172.91827”,“type”:“inject”,“z”:“a1f259d6.8791a8”,“name”:"",“topic”:“数组输
⼊”,“payload”:"
[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]",“payloadType”:“bin”,“repeat”:"",“crontab”:"",“once”:fal,“onceDelay”:0
.1,“x”:140,“y”:180,“wires”:[[“37f18063.f0bdd”]]},
{“id”:“65b”,“type”:“debug”,“z”:“a1f259d6.8791a8”,“name”:"",“active”:true,“tosidebar”:true,
“console”:fal,“tostatus”:fal,“complete”:“fal”,“x”:490,“y”:180,“wires”:[]},
{“id”:“37f18063.f0bdd”,“type”:“function”,“z”:“a1f259d6.8791a8”,“name”:“数据截断”,“func”:“vartemp
=newBuffer([0,0,0,0,0,0,0,0]);nfor(vari=0;i<8;i++)ntemp[i]=d[i+8];d=temp;nreturn
msg;n”,“outputs”:1,“noerr”:0,“x”:300,“y”:180,“wires”:[[“65b”]]}]
1
部署并观察调试窗⼝如下
这⾥写图⽚描述
说明经过函数节点以后,我们成功截取了数组的后8位。
接下来进⾏数据的组装。需求是,我们的原始数据是8位的,通信协议要求的数据是16位的,我们要把原始数据放在通信数据的后8
位。逻辑跟刚刚的组装正好相反。
这⾥写图⽚描述
以下是函数节点的代码
vartemp=newBuffer([170,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0]);
for(vari=0;i<8;i++)
temp[8+i]=d[i];
d=temp;
returnmsg;
1
2
3
4
5
Inject节点的设置与现象,就请你⾃⾏导⼊流来观察吧。
[{“id”:“d5052172.91827”,“type”:“inject”,“z”:“a1f259d6.8791a8”,“name”:"",“topic”:“数组输
⼊”,“payload”:"
[0,1,2,3,4,5,6,7]",“payloadType”:“bin”,“repeat”:"",“crontab”:"",“once”:fal,“onceDelay”:0.1,“x”:140,“y”:180,
“wires”:[[“37f18063.f0bdd”]]},
{“id”:“65b”,“type”:“debug”,“z”:“a1f259d6.8791a8”,“name”:"",“active”:true,“tosidebar”:true,
“console”:fal,“tostatus”:fal,“complete”:“fal”,“x”:490,“y”:180,“wires”:[]},
{“id”:“37f18063.f0bdd”,“type”:“function”,“z”:“a1f259d6.8791a8”,“name”:“数据截断”,“func”:“vartemp
=newBuffer([170,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0]);nfor(vari=0;i<8;i++)ntemp[8+i]=d[i];d=
temp;nreturnmsg;n”,“outputs”:1,“noerr”:0,“x”:300,“y”:180,“wires”:[[“65b”]]}]
1
5.3.3模拟温度数值的计算
由于通信⽅式的限制,数据在发送时,常常要转换为8位的字符来发送。在数字电路⾥,只有⾼电平和低电平两种情况,所以数据在发
送时,就只能使⽤1和0个数字。这个1或0,称之为1位。通常,8位可以传输⼀个字符。2的8次⽅是256,所以⼩于256的数字,可以⽤
8位来传输。⽽⼤于或等于256的数字,就要分成2个8位传输了。另外,8位的数据可以⽤两个⼗六进制的数来表⽰,⽐如01011010可
以表⽰为0x5a,所以通常会⽤⼗六进制来表⽰通信协议中的数。
例如,我们现在想传输⼗进制的数字14859,⼤于256,所以要分包传送。⼗进制转化为⼆进制是1011,在通
过硬件发送数据时,如果不考虑校验或者起始位以及⾼位还是地位在前,就只考虑数字的发送,那么总线上的电平可能是低低⾼⾼,⾼低⾼
低,低低低低,⾼低⾼⾼。把这个数字写成⼗六进制,是0x3a与0x0b,或0x3a0b,0x⽤来表⽰这是个⼗六进制的数字。0x3049可以通
过这种⽅式换算成10进制:数字拆成单个字符来分析,a到f的数对应10到15,在第⼏位上,就乘以⼏个16。0x3a0b=
3×16×16×16+10×16×16+11=14859。或者直接使⽤16进制进⾏计算,0x3a0b=0x3a×256+0x0b
当然,通信协议是可以进⾏特殊的规定,例如表⽰4位的温度,34.56°C,要通过⼗六进制的数字进⾏分包发送,就是
0Xd80/100。我们现在规定,⼗六进制的温度⾼位×256+低位=温度×100。接下来尝试解析温度的数据。
我们使⽤inject节点输⼊数组[0xd,0x80],由于inject节点⾥不⽀持直接输⼊⼗六进制的数字,所以输⼊[13,128]。这两个数组的值是
⼀样的。
这⾥写图⽚描述
函数节点进⾏如下修改
这⾥写图⽚描述
连线并部署,然后点击inject节点的输⼊按钮,可以看到调试窗⼝输出的正是我们期望的结果:34.56。
这⾥写图⽚描述
代码如下:
[{“id”:“d5052172.91827”,“type”:“inject”,“z”:“a1f259d6.8791a8”,“name”:"",“topic”:“数组输
⼊”,“payload”:"
[13,128]",“payloadType”:“bin”,“repeat”:"",“crontab”:"",“once”:fal,“onceDelay”:0.1,“x”:140,“y”:180,“wires
”:[[“37f18063.f0bdd”]]},
{“id”:“65b”,“type”:“debug”,“z”:“a1f259d6.8791a8”,“name”:"",“active”:true,“tosidebar”:true,
“console”:fal,“tostatus”:fal,“complete”:“fal”,“x”:490,“y”:180,“wires”:[]},
{“id”:“37f18063.f0bdd”,“type”:“function”,“z”:“a1f259d6.8791a8”,“name”:“温度计算”,“func”:“vartemp
=(d[0]*256+d[1])/100;d=temp;nreturn
msg;n”,“outputs”:1,“noerr”:0,“x”:300,“y”:180,“wires”:[[“65b”]]}]
1
可以再添加⼀个节点,把数字34.56转为字符34.56摄⽒度。
这⾥写图⽚描述
这⾥写图⽚描述
本文发布于:2022-12-31 21:33:43,感谢您对本站的认可!
本文链接:http://www.wtabcd.cn/fanwen/fan/90/67956.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |