How to build a flow variable array in a function

I have managed to implement most things I needed ,just got stuck on one:

if(msg.payload == 0){
    var i=0;
    //node.warn(flow.get(individual_items))
    var items = flow.get('individual_items')
    var number = flow.get('counter_temp')
    node.warn("COUNTER 0 DETECTED")
    node.warn(items)
    node.warn(number)
    for(i;i<number;i++)
        msg.topic[i] = items[i]
        node.warn(msg.topic[i])
}
return msg

[{"id":"6bdf19ef.77f2a8","type":"debug","z":"df177cb8.27882","name":"status","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":770,"y":1880,"wires":},{"id":"88eb8b89.498378","type":"function","z":"df177cb8.27882","name":"-1 from counter when status=DONE","func":"var temp_var;\nif (msg.payload == "DONE"){\n msg.topic = "global/counter"\n flow.set('counter',flow.get('counter')-1)\n msg.payload = flow.get('counter')\n node.warn("COUNTER DECREMENTED")\n return msg\n}\nelse {\n return msg\nnode.warn("STATUS NOT DONE")\n}\n\n\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1020,"y":1820,"wires":[["cef1a3c2.340f5","aa9503a7.c2c02"]]},{"id":"aa9503a7.c2c02","type":"debug","z":"df177cb8.27882","name":"decremented counter","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1360,"y":1820,"wires":},{"id":"72f7e508.9a6dfc","type":"mqtt in","z":"df177cb8.27882","name":"","topic":"+/status","qos":"2","datatype":"auto","broker":"2727c5a5.a4fb6a","x":770,"y":1820,"wires":[["88eb8b89.498378"]]},{"id":"cef1a3c2.340f5","type":"function","z":"df177cb8.27882","name":"function to make msg.payload to flow.counter","func":"if(msg.payload == 0){\n var i=0;\n //node.warn(flow.get(individual_items))\n var items = flow.get('individual_items')\n var number = flow.get('counter_temp')\n node.warn("COUNTER 0 DETECTED")\n node.warn(items)\n node.warn(number)\n for(i;i<number;i++)\n msg.topic[i] = items[i]\n node.warn(msg.topic[i])\n}\nreturn msg","outputs":1,"noerr":0,"initialize":"","finalize":"","x":1110,"y":1720,"wires":[]},{"id":"9be853bd.5da3c","type":"debug","z":"df177cb8.27882","name":"GOOD JOB","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"topic","targetType":"msg","statusVal":"","statusType":"auto","x":1410,"y":1720,"wires":},{"id":"2727c5a5.a4fb6a","type":"mqtt-broker","z":"","name":"","broker":"localhost","port":"1883","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthRetain":"false","birthPayload":"","closeTopic":"","closeQos":"0","closeRetain":"false","closePayload":"","willTopic":"","willQos":"0","willRetain":"false","willPayload":""}]


In the function, I am checking if the payload is 0 ( thats the counter value that I decrement).
When the value reaches 0, I want to set the msg.topic to the Individual item array as following:

msg.topic[0] = Individual_items[0]
msg.topic[1] = Individual_items[1] .....

![2020-07-23-152919_1920x1080_scrot|690x388](upload://gijBjzOcoSw1XDA0ZTsxwvTytj9.png) 


I Used node.warn to print what are the individual_items and I sucessfully get an debug message that Individual_items=[device1,device2]  which is correct.

However, it wont let me set the msg.topic[0] = individual_items[0] like I have tried

Since msg.topic is not an array, you can't set msg.topic[0] to anything. You would have to do something like
msg.topic = []
before the start of the for loop.

Also note that you have not used { } round the for loop contents, so the code you have shown is actually

    for(i;i<number;i++)
        msg.topic[i] = items[i]
    node.warn(msg.topic[i])

with the node.warn statement after the loop. This is one of the commonest coding errors made (by experienced coders and beginners alike) and is why I would never indent code without using {..}. It is just too easy to start with

    for(i;i<number;i++)
        msg.topic[i] = items[i]

and then realise that you need an extra line in the loop and just add it. The same error can also be easily made with if statements etc.

Edit Are you sure you want msg.topic to be an array? That is very unusual and you might find that another node you feed it into has a nervous breakdown and crashes node red when it finds that topic is an array. That would be a bug of course, but it is just the sort of edge case that few would think of testing for.

Thank you very much for detailed response!

The reason why I am trying to make my msg.topic an array is because I will be using this function node output as an input to MQTT publish node which takes msg.topic and msg.payload as input parameters.

I need to publish a mqtt message to multiple devices, for example if my number is 3, and device names are device1, device4, device6,
I want to publish a mqtt message to each device ( keep in mind that these device names math the topic names as well) so device1/stauts, device4,status, device6/status is what I need to build

I havent tried whether having an msg.topic as an array as an input to MQTT publish node works but I assumed it will.

I suspect not, unless it specifically mentions that in the node. You might be better to send a number of separate messages, each with the appropriate topic and payload. You can do that using node.send() in your for loop. Also I just noticed you are not initialising i in your for statement but relying on it being set at the top, it is better to remove that and use

    ...
    for(let i= 0;  i<number;  i++) {
        msg.topic = items[i]
        node.send(msg)
    }
    msg = null
}
return msg

The line msg=null causes no message to be sent at the end (in the case where payload is zero).

That is great idea. I will do that tommorow. Thanks a lot

I have managed to do as you suggested - sending a node.send(msg) in a loop and that works fine, however, I am facing another issue now:

This is whats inside my function node ( it is doing many things: checking if the global flag(task_started_ is set, updating MYSQL database, setting the counter based on how many individual items I have.


if(flow.get('task_started')==0){
    var items_array =  flow.get("individual_items")|| []
    flow.set("individual_items",items_array);
    var internal_counter = flow.get("internal_counter")||0;
    node.warn("task STARTED")
}
else
    node.warn("task already started")



var quantity = msg.payload
    var device_id = msg.topic.split("/");// split the whole topic to devicex ( untill /)
    msg.payload = device_id[0];
    msg.topic=`UPDATE pack_to_light SET Quantity = '${quantity}' WHERE Device = '${device_id[0]}'`



if(quantity >0){// only increment when quantity is more than 0,
// when quantity is 0, that means we finished picking items from that box
    flow.set("individual_items["+internal_counter+"]",msg.payload);
    flow.set('internal_counter',internal_counter+1)
}
return msg;

The problem lies somewhere withing those lines:

var items_array =  flow.get("individual_items")|| []
flow.set("individual_items",items_array);

So if i understand this correctly, It only sets the items arrray to an empty array during the first launch (deployment) of the flow. If I am doing multiple tasks withing single deployment, this will not be reinitialized which causes some trouble.

As I mentioned, this sets the flow.counter variable to the number of individual items, I have another function node which decrements the counter and when counter reaches 0, I need to send some data to my devices :
msg.payload refers to counter here

if(msg.payload == 0){
   msg.topic=0;
   msg.payload = "GOOD_JOB"
   //node.warn(flow.get(individual_items))
   var items = flow.get('individual_items')
   var number = flow.get('counter_temp')
   node.warn("COUNTER 0 DETECTED")
   node.warn(items)
   node.warn(number)
   for(let i=0;i<number;i++){
       msg.topic = items[i] + "/status"
       //node.warn(msg.topic)
       node.send(msg)
   }
   flow.set('task_started',0);
   flow.set('individual_items',0);// HERE I NEED TO COMPLETELY RESET INDIVIDUAL_ITEMS INSTEAD OF SETTING TO 0
   //return msg
}
return msg=null

Here, when counter reaches 0, I need to completely reset individual_items array, so I dont need to redeploy the flow everytime. I have tried doing so:
flow.set('individual_items',0);
but this does not reset the variable, instead, just sets the elements of the array to 0.

In the function node above, I do:

   var items = flow.get('individual_items')
   var number = flow.get('counter_temp')
   node.warn("COUNTER 0 DETECTED")
   node.warn(items)

I create a variable "items" in which I save data from individual_items flow and I print it to debug using node.warn(items).

During the first task, this is fine:

GOOD_JOB to all active devices
function : (warn)
array[2]
0: "device1"
1: "device3"

However, during the second task, items variable looks like that:

[GOOD_JOB to all active devices]
function : (warn)
array[4]
0: null
1: null
2: "device1"
3: "device3"

Could someone suggest me a way how do I completely clear flow.individual_items variable after counter gets to = 0 ( msg.payload refers to counter in 2nd function)

It sets it to an empty array - if it does not exist. If you re-deploy it still exists. And if you have enabled the storage parameter it will survive a reboot as well.

I see a lot of code in this thread but it is not entirely clear what you are trying to accomplish (ie what is the purpose?), it looks complicated.

What I am trying to do is to control multiple remote devices from the main controller ( raspberry PI). What happens is a raspberry PI initiates a task and assigns multiple devices some number. I need to save those device names and counter to a flow type variable because I use it onther functions.

So for example raspberry PI initiate a task for device1, device4, device5

I need to save those items in "individual_items" array which is what I do.

When remote devices finished their task, they send a singal back to raspberry PI which decrements counter. When counter reaches 0, raspberry PI knows that all assigned devices finished their tasks and I send them a message "GOOD_JOB". Also, when task is finished, I need to reset "individual_items" array so when the next task starts, I start from a clear array ( Now it does not reset, it saves the values from the last task and just appends which is not good).

So in short, I am designing a system simmilar to "pick_to_light" where I have many remote devices assigned to a box which lights up an led to show an operator that he needs to grab some items from this box .

I could set a new array in this function :


if(flow.get('task_started')==0){
    var items_array =  flow.get("individual_items")|| []
    flow.set("individual_items",items_array);
    var internal_counter = flow.get("internal_counter")||0;
    node.warn("task STARTED")
}
else
    node.warn("task already started")



var quantity = msg.payload
    var device_id = msg.topic.split("/");// split the whole topic to devicex ( untill /)
    msg.payload = device_id[0];
    msg.topic=`UPDATE pack_to_light SET Quantity = '${quantity}' WHERE Device = '${device_id[0]}'`



if(quantity >0){// only increment when quantity is more than 0,
// when quantity is 0, that means we finished picking items from that box
    flow.set("individual_items["+internal_counter+"]",msg.payload);
    flow.set('internal_counter',internal_counter+1)
}
return msg;

However, this function gets called many times during a single task ( it gets called once per each activated device). So if I need to activate 3 devices, this function will be called 3 times so I cannot reinitialize the array everytime :confused:

I believe the best solution would be to somehow clear the array when the counter value reaches 0 ( as I mentioned on my previous post) or I need to somehow change the way my array is initialized in the first place

  • to completely delete a context var set it to undefinded:
    flow.set("your_var", undefined);
  • to set it to an empty array simply do:
    flow.set("your_var", []); when your counter reaches 0

Thank you as always sir. You helped a lot.
I actually managed to find the root cause of the error:


if(flow.get('task_started')==0){
    var items_array =  flow.get("individual_items")|| []
    flow.set("individual_items",items_array);
    
    var internal_counter = flow.get("internal_counter")||0;

    node.warn("task STARTED")
    node.warn(internal_counter)
}
else
    node.warn("task already started")



var quantity = msg.payload
    var device_id = msg.topic.split("/");// split the whole topic to devicex ( untill /)
    msg.payload = device_id[0];
    msg.topic=`UPDATE pack_to_light SET Quantity = '${quantity}' WHERE Device = '${device_id[0]}'`



if(quantity >0){// only increment when quantity is more than 0,
// when quantity is 0, that means we finished picking items from that box
    flow.set("individual_items["+internal_counter+"]",msg.payload);
    flow.set('internal_counter',internal_counter+1)
}
return msg;

In this function, I am setting "individual_items" to an array which is controlled by "internal_counter". We only initialize internal_counter once, and I realized that I do not reset it, so when the first task starts, it increments by a few numbers, when the second task starts, it continues to increment, that is why it appends to an individual_items array.

I just have to set internal_counter to 0 when counter reaches 0 and that fixes the issue. Anyway it is probabaly a good idea to clear the array after the counter reaches 0

Curiosity question - are you actually saving an array of data for each device (i.e device1 [1,2,3..]) or an array of devices with one value? If an array of devices with one value, why not use an object?
{"device1" : 1, "device2" : 2, "device3" : 3}
Here is a little flow demonstrating what I mean

[{"id":"81ea6f6d.49c9a8","type":"inject","z":"16b1321b.7fa8d6","name":"","topic":"device/1","payload":"data for device 1","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":230,"y":100,"wires":[["fb6499fa.5adff"]]},{"id":"ab9e8d40.e5d5e8","type":"inject","z":"16b1321b.7fa8d6","name":"","topic":"device/2","payload":"data for device 2","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":230,"y":160,"wires":[["fb6499fa.5adff"]]},{"id":"cf6db212.1e57c8","type":"inject","z":"16b1321b.7fa8d6","name":"","topic":"device/3","payload":"data for device 3","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":230,"y":240,"wires":[["fb6499fa.5adff"]]},{"id":"fb6499fa.5adff","type":"function","z":"16b1321b.7fa8d6","name":"","func":"\nvar data = msg.payload\nvar device_part = msg.topic.split(\"/\")\nvar device_id = device_part[0]+device_part[1]\nvar devices = flow.get('devices')||{}\n\ndevices[device_id] = msg.payload\nflow.set('devices', devices)\nreturn msg;","outputs":1,"noerr":0,"x":450,"y":160,"wires":[["e4b11b58.54463"]]},{"id":"e4b11b58.54463","type":"debug","z":"16b1321b.7fa8d6","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":610,"y":160,"wires":[]}]
1 Like

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.