Optimizing Node Red Flow

I have a Node Red Flow that is doing a couple dozen writes to scratch pad per second. (or is supposed to…) It seems to have trouble getting all of them done. I suspect writes are getting queued, but over running the write buffer or something. Here is a screenshot of the Node Red. I’m working to optimize the Flow by combining all scratchpad reads, then combining all writes (Write to multiple RIO scratchpad using 1 groov i/o write node), then perhaps using fewer time injects. Is there anything else I should be doing to this Flow to optimize it?

I will pull your flow and take a look in a moment, but Terrys answer here is the biggest change you can make to improve your reliably.

If you can pare your flow down to 1 inject and then do everything in a single flow, that would make a huge difference.

1 Like

Ok, found another example post that had the same issue…

The top post, while cut off, has a bunch of injects all at the same frequency. This was causing the same issue you have where (in this case, the modbus device) was overloaded, in your case, its the scratchpad node.

As you can see, once he changed to doing them in sequence, he got up and running.

1 Like

Switched to all one inject, but now it seems like nothing is writing to scratchpad. I’m going to try figuring out an api call that does multiple writes to scratch pad in one node. It would be handy if there was a node already built for doing multiwrites to groove IO. Or a way to use the existing groov i/o write node and send an array to it.

Here is my flow right now:

I’m not able to import your flow JSON, could you try posting it surrounded by the ` character so that it gets interpreted as code text like this?

Using delay nodes set to rate limit may be one way to help, and if possible reducing the inject time would certainly help with overloading the queue. Is this running on an EPIC, RIO, or a different system? Using a control strategy would make running this type of application much easier.

Either way, I strongly recommend you look into using the dynamic groov node settings to do multiple read/writes with just one node. There’s another forum post to check out that should help you get started on how to approach that: Two ways to loop dynamic write objects for Opto nodes

Here is current code:

[{"id":"d1117df142d0a677","type":"tab","label":"Flow 1","disabled":false,"info":"","env":[]},{"id":"4ca5625dc0732e0a","type":"groov-io-write","z":"d1117df142d0a677","device":"4e9fa67a17225a9e","dataType":"channel-digital","moduleIndex":"0","channelIndex":"8","mmpAddress":"0xF0D81000","mmpType":"int32","mmpLength":"1","mmpEncoding":"ascii","value":"cmd","valueType":"msg","name":"CMD","x":570,"y":280,"wires":[["08af0b6268a9e1fc"]]},{"id":"53ad6dc8c6c139c6","type":"groov-io-read","z":"d1117df142d0a677","device":"4e9fa67a17225a9e","dataType":"channel-analog","moduleIndex":"0","channelIndex":"3","mmpAddress":"0xF0D81004","mmpType":"int32","mmpLength":"1","mmpEncoding":"ascii","value":"","valueType":"msg.payload","itemName":"","name":"Read Well Level","x":200,"y":280,"wires":[["27b66f186e972aed"]]},{"id":"4733a47c8ab6da57","type":"inject","z":"d1117df142d0a677","name":"Every Second","props":[{"p":"now","v":"","vt":"date"},{"p":"topic","vt":"str"}],"repeat":"1","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":140,"y":80,"wires":[["2b1c2d4a26d1df58"]]},{"id":"2b1c2d4a26d1df58","type":"groov-io-read","z":"d1117df142d0a677","device":"4e9fa67a17225a9e","dataType":"mmp-address","moduleIndex":"","channelIndex":"","mmpAddress":"0xF0D81000","mmpType":"int32","mmpLength":"30","mmpEncoding":"ascii","value":"","valueType":"msg.payload","itemName":"","name":"Load All Scratchpad Values from HMI","x":430,"y":80,"wires":[["7dd524962a7a771d"]]},{"id":"7dd524962a7a771d","type":"function","z":"d1117df142d0a677","name":"Save HMI Values to flow.*","func":"// Functions for Bits from Stack Overflow\nfunction bit_test(num, bit){\n    return ((num>>bit) % 2 != 0)\n}\n\nfunction bit_set(num, bit){\n    return num | 1<<bit;\n}\n\nfunction bit_clear(num, bit){\n    return num & ~(1<<bit);\n}\n\nfunction bit_toggle(num, bit){\n    return bit_test(num, bit) ? bit_clear(num, bit) : bit_set(num, bit);\n}\n\n// HMI Counter:\nvar HMI_Count = msg.payload[0];\nflow.set('HMI_Count',HMI_Count);\n\n// HOA:\nvar HOA = msg.payload[1];\nflow.set('HOA',HOA);\n\n// ON_SP:\nvar ON_SP = msg.payload[2]/10.0;\nflow.set('ON_SP',ON_SP);\n\n// OFF_SP:\nvar OFF_SP = msg.payload[3]/10.0;\nflow.set('OFF_SP',OFF_SP);\n\n// Speed_SP:\nvar Speed_SP = msg.payload[4]/10.0;\nflow.set('Speed_SP',Speed_SP);\n\n// Pump_Resets:\nvar Pump_Resets = msg.payload[5];\nflow.set('Pump_Resets',Pump_Resets);\n\n// Flow_Resets:\nvar Flow_Resets = msg.payload[6];\nflow.set('Flow_Resets',Flow_Resets);\n\n// 7-9 are Spare\n\n// PLC_Count:\nvar PLC_Count = flow.get('PLC_Count')||0;\nPLC_Count = msg.payload[10];\n\n// 11: Pump Info is Write only. no need to read.\n// 12-14: Same for Well Level, VFD Speed, and Flow.\n\n// Hours_R0:\nvar Hours_R0 = msg.payload[15];\nflow.set('Hours_R0',Hours_R0);\n\n// Hours_R1:\nvar Hours_R1 = msg.payload[16];\nflow.set('Hours_R1',Hours_R1);\n\n// Hours_R2:\nvar Hours_R2 = msg.payload[17];\nflow.set('Hours_R2',Hours_R2);\n\n// Flow_T0:\nvar Flow_T0 = msg.payload[18];\nflow.set('Flow_T0',Flow_T0);\n\n// Flow_T1:\nvar Flow_T1 = msg.payload[19];\nflow.set('Flow_T1',Flow_T1);\n\n// Flow_T2:\nvar Flow_T2 = msg.payload[20];\nflow.set('Flow_T2',Flow_T2);\n\n\nmsg.sp_vars = [HMI_Count,HOA,ON_SP,OFF_SP,Speed_SP,Pump_Resets,Flow_Resets]\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":810,"y":80,"wires":[["97eefd31a39273e2"]]},{"id":"27b66f186e972aed","type":"function","z":"d1117df142d0a677","name":"Run Pump?","func":"// Level:\nvar Level = flow.get('Level')||1.0;\nvar HOA = flow.get('HOA');\nvar VFD_Fault = flow.get('VFD_Fault')||false;\nvar CMD = flow.get('CMD')||false;\nvar Call = flow.get('Call')||false;\nvar ON_SP = flow.get('ON_SP')||17.0;\nvar OFF_SP = flow.get('OFF_SP')||5.0;\nvar Speed_SP = flow.get('Speed_SP')||100.0;\nLevel = msg.payload;\nflow.set('Level',Level);\n\nswitch(HOA) {\n    case 0: \n        CMD = false;\n        break;\n        \n    case 1:\n        if(Level > ON_SP) {\n            CMD = true;\n        } else if(Level < OFF_SP) {\n            CMD = false;\n        } \n        break;\n        \n    case 2:\n        CMD = true;\n        break;\n    \n    default:\n        break;\n}\n\n\n// if(HOA == 0) {\n//     CMD = false;\n// } else if(VFD_Fault) {\n//     CMD = false;\n// } else if(HOA == 2) {\n//     CMD = true;\n// } else if(HOA == 1) {\n//     if(Level > ON_SP) {\n//         CMD = true;\n//     } else if(Level < OFF_SP) {\n//         CMD = false;\n//     }\n// } \nif (Level > ON_SP) {\n    Call = true;\n} else if (Level < OFF_SP) {\n    Call = false;\n}\nflow.set('CMD',CMD);\nflow.set('Call',Call);\n\nif (CMD) { \n    msg.speed = Speed_SP\n} else {\n    msg.speed = 0.0\n}\n\nmsg.hoa = HOA;\nmsg.cmd = CMD;\nmsg.level = Level * 10;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":410,"y":280,"wires":[["4ca5625dc0732e0a"]]},{"id":"9684982098190457","type":"groov-io-read","z":"d1117df142d0a677","device":"4e9fa67a17225a9e","dataType":"channel-digital","moduleIndex":"0","channelIndex":"6","mmpAddress":"0xF0D81000","mmpType":"int32","mmpLength":"1","mmpEncoding":"ascii","value":"","valueType":"msg.payload","itemName":"","name":"Read VFD Fault","x":200,"y":180,"wires":[["cd948352edc1db67"]]},{"id":"cd948352edc1db67","type":"function","z":"d1117df142d0a677","name":"VFD Fault","func":"// Functions for Bits from Stack Overflow\nfunction bit_test(num, bit){\n    return ((num>>bit) % 2 != 0)\n}\n\nfunction bit_set(num, bit){\n    return num | 1<<bit;\n}\n\nfunction bit_clear(num, bit){\n    return num & ~(1<<bit);\n}\n\nfunction bit_toggle(num, bit){\n    return bit_test(num, bit) ? bit_clear(num, bit) : bit_set(num, bit);\n}\n\n// Pump_Resets:\nvar Pump_Resets = flow.get('Pump_Resets')||0;\n\n\n// VFD_Fault:\nvar VFD_Fault = flow.get('VFD_Fault')||false;\nVFD_Fault = msg.payload;\nflow.set('VFD_Fault',VFD_Fault);\n// Reset VFD Fault\nvar VFD_Reset = flow.get('VFD_Reset')||false;\nVFD_Reset = bit_test(Pump_Resets, 0)\n\n// Pump_Info\nvar Pump_Info = flow.get('Pump_Info')||0;\nif(VFD_Fault) {\n    bit_set(Pump_Info,1);\n}\nif(VFD_Fault == 0) {\n    bit_clear(Pump_Info,1);\n    Pump_Resets = bit_clear(Pump_Resets,0);\n}\n\nflow.set('Pump_Resets',Pump_Resets);\n\nmsg.fault = VFD_Fault;\nmsg.pump_resets = Pump_Resets;\nmsg.payload = VFD_Reset;\n\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":400,"y":180,"wires":[["e195f945d013c80a"]]},{"id":"97eefd31a39273e2","type":"function","z":"d1117df142d0a677","name":"PLC Counter","func":"// PLC_Count:\nvar PLC_Count = flow.get('PLC_Count');\n\nPLC_Count++;\n\nif (PLC_Count > 2000000000) {\n    PLC_Count = 0;\n}\n\nflow.set('PLC_Count',PLC_Count);\n\nmsg.payload = PLC_Count;\n\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1030,"y":80,"wires":[["4f2732ebfca6a3f8"]]},{"id":"4f2732ebfca6a3f8","type":"groov-io-write","z":"d1117df142d0a677","device":"4e9fa67a17225a9e","dataType":"mmp-address","moduleIndex":"","channelIndex":"","mmpAddress":"0xF0D81028","mmpType":"int32","mmpLength":"1","mmpEncoding":"ascii","value":"","valueType":"msg.payload","name":"Write PLC Count","x":1250,"y":80,"wires":[["9684982098190457"]]},{"id":"08af0b6268a9e1fc","type":"groov-io-write","z":"d1117df142d0a677","device":"4e9fa67a17225a9e","dataType":"mmp-address","moduleIndex":"","channelIndex":"","mmpAddress":"0xF0D81030","mmpType":"int32","mmpLength":"1","mmpEncoding":"ascii","value":"level","valueType":"msg","name":"Level to HMI","x":750,"y":280,"wires":[["50608db94ce33d6e"]]},{"id":"2e8ff1d8ac30f535","type":"groov-io-read","z":"d1117df142d0a677","device":"4e9fa67a17225a9e","dataType":"channel-digital","moduleIndex":"0","channelIndex":"5","mmpAddress":"0xF0D81004","mmpType":"int32","mmpLength":"1","mmpEncoding":"ascii","value":"status","valueType":"msg","itemName":"","name":"Read Pump Run Status","x":230,"y":380,"wires":[["be00435fe055d5e4"]]},{"id":"be00435fe055d5e4","type":"function","z":"d1117df142d0a677","name":"Pump Timers","func":"function bit_test(num, bit){\n    return ((num>>bit) % 2 != 0)\n}\n\nfunction bit_set(num, bit){\n    return num | 1<<bit;\n}\n\nfunction bit_clear(num, bit){\n    return num & ~(1<<bit);\n}\n\nfunction bit_toggle(num, bit){\n    return bit_test(num, bit) ? bit_clear(num, bit) : bit_set(num, bit);\n}\n\n// Status:\nvar S_Status = flow.get('Status')||false;\nvar P_Status = msg.status;\nvar Status = msg.status;\nvar StartTime = flow.get('StartTime');\nvar AccTime = 0;\n\n// Pump_Resets:\nvar Pump_Resets = flow.get('Pump_Resets')||0;\n\n// Reset Pump Runtime 1\nvar Pump_R1 = flow.get('Pump_R1')||0;\nPump_R1 = bit_test(Pump_Resets, 1);\nflow.set('Pump_R1',Pump_R1);\n\n// Reset Pump Runtime 2\nvar Pump_R2 = flow.get('Pump_R2')||0;\nPump_R2 = bit_test(Pump_Resets, 2);\nflow.set('Pump_R2',Pump_R2);\n\n// Other Vars\nvar P_Seconds = flow.get('P_Seconds')||0.0;\nvar THours_R0 = flow.get('Hours_R0')||0;\nvar THours_R1 = flow.get('Hours_R1')||0;\nvar THours_R2 = flow.get('Hours_R2')||0;\n\n\nif (StartTime > 1) {\n    AccTime = msg.payload - StartTime\n}\n\nif (!S_Status && P_Status) {\n    StartTime = msg.payload;\n    flow.set('StartTime',StartTime);\n    S_Status = true;\n} else if (S_Status && P_Status) {\n    P_Seconds = P_Seconds + AccTime / 1000;\n} else if (S_Status && !P_Status) {\n    flow.set('P_Seconds',P_Seconds);\n    StartTime = 1;\n    S_Status = false;\n}\nif (P_Seconds > 360.0) {\n    THours_R0++;\n    THours_R1++;\n    THours_R2++;\n    P_Seconds = P_Seconds - 360.0;\n    StartTime = msg.payload;\n    flow.set('THours_R0',THours_R0);\n    flow.set('THours_R1',THours_R1);\n    flow.set('THours_R2',THours_R2);\n    flow.set('P_Seconds',P_Seconds);\n    flow.set('StartTime',StartTime);\n} else if (THours_R0 > 2000000000) {\n    THours_R0 = 0;\n    flow.set('THours_R0',THours_R0);\n} else if (Pump_R1 || THours_R1 > 2000000000) {\n    THours_R1 = 0;\n    flow.set('THours_R1',THours_R1);\n    Pump_Resets = bit_clear(Pump_Resets, 1);\n} else if (Pump_R2 || THours_R2 > 2000000000) {\n    THours_R2 = 0;\n    flow.set('THours_R2',THours_R2);\n    Pump_Resets = bit_clear(Pump_Resets, 2);\n}\n\nflow.set('Pump_Resets',Pump_Resets);\nflow.set('Status',Status);\n\nmsg.stime = StartTime;\nmsg.pstatus = P_Status;\nmsg.sstatus = S_Status;\nmsg.secs = P_Seconds;\nmsg.hours = [THours_R0, THours_R1, THours_R2]\nmsg.p_resets = Pump_Resets;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":450,"y":380,"wires":[["75461fc90b894bbd"]]},{"id":"50608db94ce33d6e","type":"groov-io-write","z":"d1117df142d0a677","device":"4e9fa67a17225a9e","dataType":"channel-analog","moduleIndex":"0","channelIndex":"4","mmpAddress":"0xF0D81000","mmpType":"int32","mmpLength":"1","mmpEncoding":"ascii","value":"speed","valueType":"msg","name":"Set Pump Speed CMD","x":960,"y":280,"wires":[["2e8ff1d8ac30f535"]]},{"id":"75461fc90b894bbd","type":"groov-io-write","z":"d1117df142d0a677","device":"4e9fa67a17225a9e","dataType":"mmp-address","moduleIndex":"","channelIndex":"","mmpAddress":"0xF0D8103C","mmpType":"int32","mmpLength":"1","mmpEncoding":"ascii","value":"hours[0]","valueType":"msg","name":"Hours","x":630,"y":380,"wires":[["227b4309867593de"]]},{"id":"227b4309867593de","type":"groov-io-write","z":"d1117df142d0a677","device":"4e9fa67a17225a9e","dataType":"mmp-address","moduleIndex":"","channelIndex":"","mmpAddress":"0xF0D81040","mmpType":"int32","mmpLength":"1","mmpEncoding":"ascii","value":"hours[1]","valueType":"msg","name":"Hours","x":770,"y":380,"wires":[["cd1425aecf438fef"]]},{"id":"cd1425aecf438fef","type":"groov-io-write","z":"d1117df142d0a677","device":"4e9fa67a17225a9e","dataType":"mmp-address","moduleIndex":"","channelIndex":"","mmpAddress":"0xF0D81044","mmpType":"int32","mmpLength":"1","mmpEncoding":"ascii","value":"hours[2]","valueType":"msg","name":"Hours","x":910,"y":380,"wires":[["c2cf59c1a615d06a"]]},{"id":"fee8f16df449eb1a","type":"groov-io-read","z":"d1117df142d0a677","device":"4e9fa67a17225a9e","dataType":"channel-analog","moduleIndex":"0","channelIndex":"2","mmpAddress":"0xF0D81004","mmpType":"int32","mmpLength":"1","mmpEncoding":"ascii","value":"payload","valueType":"msg","itemName":"","name":"Read Pump Run Speed","x":250,"y":480,"wires":[["f3fa617ee9d5a10b"]]},{"id":"f3fa617ee9d5a10b","type":"function","z":"d1117df142d0a677","name":"Pump Speed","func":"// Pump_Speed:\nvar Pump_Speed = flow.get('Pump_Speed')||0;\nPump_Speed = msg.payload * 10.0;\nflow.set('Pump_Speed',Pump_Speed);\n\n\nmsg.payload = Pump_Speed;\n\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":470,"y":480,"wires":[["213afdeaa99701ef"]]},{"id":"213afdeaa99701ef","type":"groov-io-write","z":"d1117df142d0a677","device":"4e9fa67a17225a9e","dataType":"mmp-address","moduleIndex":"","channelIndex":"","mmpAddress":"0xF0D81034","mmpType":"int32","mmpLength":"1","mmpEncoding":"ascii","value":"","valueType":"msg.payload","name":"Pump Speed","x":650,"y":480,"wires":[["2fec3772d772f4b4"]]},{"id":"2fec3772d772f4b4","type":"function","z":"d1117df142d0a677","name":"Pump Info Builder","func":"// Pump Bits:\nvar Status = flow.get('Status')||false;\nvar VFD_Fault = flow.get('VFD_Fault')||false;\nvar Call = flow.get('Call')||false;\nvar CMD = flow.get('CMD')||false;\nvar Pump_Info = 0;\n\n\nPump_Info = Status + (VFD_Fault << 1) + (Call << 2) + (CMD << 3)\n\n\nmsg.payload = Pump_Info\n\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":230,"y":560,"wires":[["9237eca872020457"]]},{"id":"9237eca872020457","type":"groov-io-write","z":"d1117df142d0a677","device":"4e9fa67a17225a9e","dataType":"mmp-address","moduleIndex":"","channelIndex":"","mmpAddress":"0xF0D8102C","mmpType":"int32","mmpLength":"1","mmpEncoding":"ascii","value":"","valueType":"msg.payload","name":"Pump Info","x":470,"y":560,"wires":[["3cb04f07142cac5e"]]},{"id":"3cb04f07142cac5e","type":"groov-io-read","z":"d1117df142d0a677","device":"4e9fa67a17225a9e","dataType":"channel-analog","moduleIndex":"0","channelIndex":"1","mmpAddress":"0xF0D81004","mmpType":"int32","mmpLength":"1","mmpEncoding":"ascii","value":"flow","valueType":"msg","itemName":"","name":"Read Flow Meter","x":190,"y":640,"wires":[["2b12fad40dbd568b"]]},{"id":"2b12fad40dbd568b","type":"function","z":"d1117df142d0a677","name":"Flow Totals","func":"// Functions for Bits from Stack Overflow\nfunction bit_test(num, bit){\n    return ((num>>bit) % 2 != 0)\n}\n\nfunction bit_set(num, bit){\n    return num | 1<<bit;\n}\n\nfunction bit_clear(num, bit){\n    return num & ~(1<<bit);\n}\n\nfunction bit_toggle(num, bit){\n    return bit_test(num, bit) ? bit_clear(num, bit) : bit_set(num, bit);\n}\n\n// Flow: (Assuming GPM)\nvar Flow = msg.flow;\nvar Flow_T0 = flow.get('Flow_T0')||1.0;\nvar Flow_T1 = flow.get('Flow_T1');\nvar Flow_T2 = flow.get('Flow_T2');\n\n// HMI_DI:\nvar Flow_Resets = flow.get('Flow_Resets')||0;\nvar Reset1 = bit_test(Flow_Resets, 0);\nvar Reset2 = bit_test(Flow_Resets, 1);\n\nvar Now = msg.payload;\nvar Last = flow.get('Last')||msg.payload;\nvar msecs = Now - Last;\nvar F_Total = flow.get('F_Total');\nvar Counts_Total = 1000.0; // == Galons per Count.\n\nif (Flow > 5.0) {\n    if (msecs > 0) {\n        weight = msecs / 1000.0;\n        sec_Total = Flow * weight / 60.0; // Divide by 60 because we assume GPM. Change for others.\n        F_Total += sec_Total; \n    }\n    if (F_Total > Counts_Total) {\n        Flow_T0++;\n        Flow_T1++;\n        Flow_T2++;\n        F_Total -= Counts_Total;\n    }\n}    \n\nif (Flow_T0 > 2000000000) {\n    Flow_T0 = 0;\n}\nif (Reset1 || Flow_T1 > 2000000000) {\n    Flow_T1 = 0;\n    Flow_Resets = bit_clear(Flow_Resets, 0);\n}\nif (Reset2 || Flow_T2 > 2000000000) {\n    Flow_T2 = 0;\n    Flow_Resets = bit_clear(Flow_Resets, 1);\n}\nmsg.last = Last;\n\nflow.set('Last',Now);\nflow.set('F_Total',F_Total);\nflow.set('Flow_T0',Flow_T0);\nflow.set('Flow_T1',Flow_T1);\nflow.set('Flow_T2',Flow_T2);\nflow.set('Flow_Resets',Flow_Resets);\nmsg.now = Now;\n\nmsg.msecs = msecs;\nmsg.float = F_Total;\nmsg.resets = Flow_Resets;\nmsg.totals = [Flow_T0,Flow_T1,Flow_T2];\nmsg.payload = Flow * 10;\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":430,"y":640,"wires":[["1c6d3caa0ceae154"]]},{"id":"85e3d8f3a0b3c812","type":"debug","z":"d1117df142d0a677","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1310,"y":640,"wires":[]},{"id":"7924106d31c13d86","type":"groov-io-write","z":"d1117df142d0a677","device":"4e9fa67a17225a9e","dataType":"mmp-address","moduleIndex":"","channelIndex":"","mmpAddress":"0xF0D81048","mmpType":"int32","mmpLength":"1","mmpEncoding":"ascii","value":"totals[0]","valueType":"msg","name":"Totals","x":750,"y":640,"wires":[["0739e337abf25863"]]},{"id":"0739e337abf25863","type":"groov-io-write","z":"d1117df142d0a677","device":"4e9fa67a17225a9e","dataType":"mmp-address","moduleIndex":"","channelIndex":"","mmpAddress":"0xF0D8104C","mmpType":"int32","mmpLength":"1","mmpEncoding":"ascii","value":"totals[1]","valueType":"msg","name":"Totals","x":870,"y":640,"wires":[["c01866a67a2dbb25"]]},{"id":"c01866a67a2dbb25","type":"groov-io-write","z":"d1117df142d0a677","device":"4e9fa67a17225a9e","dataType":"mmp-address","moduleIndex":"","channelIndex":"","mmpAddress":"0xF0D81050","mmpType":"int32","mmpLength":"1","mmpEncoding":"ascii","value":"totals[2]","valueType":"msg","name":"Totals","x":1010,"y":640,"wires":[["2d812bd98e3a5a4b"]]},{"id":"1c6d3caa0ceae154","type":"groov-io-write","z":"d1117df142d0a677","device":"4e9fa67a17225a9e","dataType":"mmp-address","moduleIndex":"","channelIndex":"","mmpAddress":"0xF0D81038","mmpType":"int32","mmpLength":"1","mmpEncoding":"ascii","value":"","valueType":"msg.payload","name":"Flow","x":610,"y":640,"wires":[["7924106d31c13d86"]]},{"id":"65eb083f60f299dd","type":"inject","z":"d1117df142d0a677","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"1800","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":110,"y":800,"wires":[["e6987c7af8fe5bfc"]]},{"id":"e6987c7af8fe5bfc","type":"groov-io-write","z":"d1117df142d0a677","device":"4e9fa67a17225a9e","dataType":"mmp-address","moduleIndex":"","channelIndex":"","mmpAddress":"0xF0380000","mmpType":"int32","mmpLength":"1","mmpEncoding":"ascii","value":"03","valueType":"value","name":"Write Scratchpad to Flash","x":790,"y":800,"wires":[["911426daa82ea136"]]},{"id":"911426daa82ea136","type":"debug","z":"d1117df142d0a677","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1130,"y":800,"wires":[]},{"id":"e195f945d013c80a","type":"groov-io-write","z":"d1117df142d0a677","device":"4e9fa67a17225a9e","dataType":"channel-digital","moduleIndex":"0","channelIndex":"9","mmpAddress":"0xF0D81000","mmpType":"int32","mmpLength":"1","mmpEncoding":"ascii","value":"","valueType":"msg.payload","name":"Reset","x":570,"y":180,"wires":[["2ef299ace042eb49"]]},{"id":"2ef299ace042eb49","type":"groov-io-write","z":"d1117df142d0a677","device":"4e9fa67a17225a9e","dataType":"mmp-address","moduleIndex":"0","channelIndex":"","mmpAddress":"0xF0D81014","mmpType":"int32","mmpLength":"1","mmpEncoding":"ascii","value":"pump_resets","valueType":"msg","name":"Pump Resets","x":760,"y":180,"wires":[["53ad6dc8c6c139c6"]]},{"id":"c2cf59c1a615d06a","type":"groov-io-write","z":"d1117df142d0a677","device":"4e9fa67a17225a9e","dataType":"mmp-address","moduleIndex":"0","channelIndex":"","mmpAddress":"0xF0D81014","mmpType":"int32","mmpLength":"1","mmpEncoding":"ascii","value":"p_resets","valueType":"msg","name":"Pump Resets","x":1100,"y":380,"wires":[["fee8f16df449eb1a"]]},{"id":"2d812bd98e3a5a4b","type":"groov-io-write","z":"d1117df142d0a677","device":"4e9fa67a17225a9e","dataType":"mmp-address","moduleIndex":"0","channelIndex":"","mmpAddress":"0xF0D81018","mmpType":"int32","mmpLength":"1","mmpEncoding":"ascii","value":"resets","valueType":"msg","name":"Flow Resets","x":1150,"y":640,"wires":[["85e3d8f3a0b3c812"]]},{"id":"4e9fa67a17225a9e","type":"groov-io-device","address":"localhost","msgQueueFullBehavior":"DROP_OLD"}]

Thanks,
Caleb

Nothing major in that flow is jumping out to me as being a problem. I suggest putting a bunch of debug nodes set to show the complete msg object at a few of both the function and groov node outputs to make sure your data isn’t being overwritten or changed unexpectedly anywhere.
That’s important not just to confirm the output of the node but especially what is going into the input of the next node. Having a dynamic write can help alleviate that a bit, but it’s always worth keeping in mind.

Sorry, I didn’t answer your question. This is running on a RIO. By control strategy, you mean like CODESYS or something similar, right? I would love to be doing this in CODESYS, but its not available yet for RIO. On a RIO only Node Red is available, right?

I installed the node shown in that post, but it looks like that is only for groov view, and I want scratchpad.

I think a better place for me to start is here:

In this post Ben explains how to use a scratch pad API to write multiple values at same time.

Or is there a better way?

Thanks,
-Caleb

I’m not sure how to have a bunch of debug nodes but still keep it as one flow.
Should I be doing a branching flow after a single inject?

-Caleb

The groov-io nodes also support dynamic settings:
image
So the logic in that post still applies, you’ll just be using a different node.

For the debugs you can just have them fork off as a second output wire from any of the function or groov nodes to check the message contents as it moves through the flow like this:

Dynamic settings:
I think I finally understand how this is going to work. Sorry for my thick head.

Debug: Duh. I should have known that.

Ok I have this flow as a test but I don’t get anything in the scratchpad:

[{"id":"d3ecf84d.34d258","type":"inject","z":"055daca6bddedf66","name":"data source","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"alphabet","payload":"[1,2,3,4,5,6,7,8,9]","payloadType":"json","x":430,"y":320,"wires":[["d1c4231a.de17b"]]},{"id":"cbfaf03d.2cec3","type":"debug","z":"055daca6bddedf66","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":810,"y":280,"wires":[]},{"id":"d1c4231a.de17b","type":"function","z":"055daca6bddedf66","name":"for loop","func":"var MMP_addresses = [\"0xF0D81060\",\n\"0xF0D81064\",\n\"0xF0D81068\",\n\"0xF0D8106C\",\n\"0xF0D81070\",\n\"0xF0D81074\",\n\"0xF0D81078\",\n\"0xF0D8107C\",\n\"0xF0D81080\",\n\"0xF0D81084\",\n\"0xF0D81088\"   \n]\n\n\nfor (i = 0; i < msg.payload.length; i++) {\n    \n    \n    node.send({ payload : {\n        mmpAddress      : MMP_addresses[i],\n        value           : msg.payload[i]\n    }})\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":600,"y":320,"wires":[["cbfaf03d.2cec3","67a1c6ac1bbe0cba"]]},{"id":"67a1c6ac1bbe0cba","type":"groov-io-write","z":"055daca6bddedf66","device":"b63010531ffd6c2c","dataType":"mmp-address","moduleIndex":"","channelIndex":"","mmpAddress":"0xF0D81000","mmpType":"int32","mmpLength":"1","mmpEncoding":"ascii","value":"payload.value","valueType":"msg","name":"","x":800,"y":340,"wires":[["7d463b263b0e83a4"]]},{"id":"7d463b263b0e83a4","type":"debug","z":"055daca6bddedf66","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1030,"y":340,"wires":[]},{"id":"b63010531ffd6c2c","type":"groov-io-device","address":"localhost","msgQueueFullBehavior":"DROP_OLD"}]

Am I doing something obviously wrong?

Thanks,
-Caleb

Got it. for loop needs this code:

var MMP_addresses = ["0xF0D81060",
"0xF0D81064",
"0xF0D81068",
"0xF0D8106C",
"0xF0D81070",
"0xF0D81074",
"0xF0D81078",
"0xF0D8107C",
"0xF0D81080",
"0xF0D81084",
"0xF0D81088"   
]


for (i = 0; i < msg.payload.length; i++) {
    
    
    node.send({ 
        mmpAddress      : MMP_addresses[i],
        value           : msg.payload[i]
    })
}

deleting the payload dictionary and putting the other dirrectly in “node send”.

1 Like

Here is what I have now:

[{"id":"d3ecf84d.34d258","type":"inject","z":"055daca6bddedf66","name":"data source","props":[{"p":"payload"}],"repeat":"2","crontab":"","once":true,"onceDelay":0.1,"topic":"","payloadType":"date","x":430,"y":320,"wires":[["d1c4231a.de17b"]]},{"id":"cbfaf03d.2cec3","type":"debug","z":"055daca6bddedf66","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":790,"y":280,"wires":[]},{"id":"d1c4231a.de17b","type":"function","z":"055daca6bddedf66","name":"for loop","func":"var PLC_Count = flow.get('PLC_Count')||0;\n\nvar MMP_addresses = [\"0xF0D81060\",\n\"0xF0D81064\",\n\"0xF0D81068\",\n\"0xF0D8106C\",\n\"0xF0D81070\",\n\"0xF0D81074\",\n\"0xF0D81078\",\n\"0xF0D8107C\",\n\"0xF0D81080\",\n\"0xF0D81084\",\n\"0xF0D81088\",\n\"0xF0D8108C\",\n\"0xF0D81090\",\n\"0xF0D81094\",\n\"0xF0D81098\",\n\"0xF0D8109C\",\n\"0xF0D810A0\",\n\"0xF0D810A4\",\n\"0xF0D810A8\"      \n]\n\n\nfor (i = 0; i < MMP_addresses.length; i++) {\n    \n    \n    node.send({ \n        mmpAddress      : MMP_addresses[i],\n        value           : (i + 10 * PLC_Count)\n    })\n}\n\nPLC_Count++;\nflow.set('PLC_Count', PLC_Count)","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":600,"y":320,"wires":[["cbfaf03d.2cec3","67a1c6ac1bbe0cba"]]},{"id":"67a1c6ac1bbe0cba","type":"groov-io-write","z":"055daca6bddedf66","device":"b63010531ffd6c2c","dataType":"mmp-address","moduleIndex":"","channelIndex":"","mmpAddress":"0xF0D81000","mmpType":"int32","mmpLength":"1","mmpEncoding":"ascii","value":"value","valueType":"msg","name":"","x":800,"y":340,"wires":[["7d463b263b0e83a4"]]},{"id":"7d463b263b0e83a4","type":"debug","z":"055daca6bddedf66","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1010,"y":340,"wires":[]},{"id":"b63010531ffd6c2c","type":"groov-io-device","address":"localhost","msgQueueFullBehavior":"DROP_OLD"}]

I’m just writing a new value every insert, to about two dozen mmp locations. When I put the inserts on every one second, I can watch the some of the MMP locations lag behind the other ones. Do I need to a) slow this down so its not writing so often?, or b) find someway to do multiple writes more efficiently?

Thanks,
-Caleb

Could you try putting a rate limit node in front of the groov node and see how it works? And what is the interval on your data source inject node?

When interval is 1 second, then the total CPU usage is really high:
And just as note, this is only 18 values to the scratch pad, and they’re sequential.

If I raise interval to 2 seconds then total CPU stays lower. does peak some but never hits 100%, and also gets pretty low (like 25%).

Which one is the rate limit node?

Rate limit is a setting in the delay node.


If you go to the Node-RED menu at /manage/local/node-red what is the CPU usage there? I’m wondering if it’s Node-RED putting the load on the CPU or some other software.

@cbohon Do you have a Node-RED dashboard running on this RIO as well as this flow?
I’m just thinking that 24 MMP writes should not spike the RIO CPU like that… If you have a dashboard with a graph (or more than 1 graph) it would be helpful to know.

I don’t have any dashboard. I have a default RIO, and only added one user when first starting, and then added this node red flow. Nothing else has been changed.
image

CPU usage for node red is peeking at 24% (when interval is 1 second)
@jhamlin wanted to know the same thing.

Rate limit makes the queue on the rate limit go up. I have it set to limit to 20 msg/s, and the queue was going up about 10 every other second. Its now staying about 100.

Do you need to write every time? Could you use a RBE node to only write when the value changes to decrease the amount of groov I/O writes you are doing?

I limit the groov I/O writes to 10msg/sec. Much faster than that, then I start having MMP failures. Keep the Node-Red CPU usage below 40% as well - which looks like you are doing. I wish I knew why the API was so slow on these writes. I only have a burst of writes when the flow starts, and much slower once in operation so this works for me.

If you need to write data faster than that, then you may want to look at writing direct using OptoMMP protocol- the performance is significantly higher, but requires more advanced programming and an understanding of the protocol.

4 Likes