Help with my flow: generating an email from array

Hi, I am new to node red and JavaScript. I would really appreciate help with a task I need to get working. I am running node red on an Opto 22 groov AR-1 and on the same network I have an Opto 22 Pac controller. I am using the snap pac read node to read an integer table into node red. The values in the table change by logic within the Pac controller based on alarm condition. If there is an alarm condition, an element in the integer array will be 1, or if no alarm the element value will be 0. (I actually have it backwards in the code, but doesn't matter, concept is the same.). I have created an array within the function block to assign text values to each element in the table. So if for example, the third element is in alarm, I want the text field "T1002 low alarm active" to be placed into the body of an email and sent.
Below is as far as I am able to get on this.

[{"id":"d6e25a11.0fc41","type":"inject","z":"29096dc9.e0971a","name":"","topic":"","payload":"","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":568,"y":245,"wires":[["42b90340.1c09dc"]]},{"id":"fb1897e6.a9a87","type":"debug","z":"29096dc9.e0971a","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":1068,"y":245,"wires":[]},{"id":"42b90340.1c09dc","type":"pac-read","z":"29096dc9.e0971a","device":"31b5623c.c2558e","dataType":"int32-table","tagName":"C9_A_Inhibit","tableStartIndex":"0","tableLength":"18","value":"","valueType":"msg.payload","topic":"","topicType":"none","name":"","x":751,"y":245,"wires":[["bea1c2d0.f62f5"]]},{"id":"bea1c2d0.f62f5","type":"function","z":"29096dc9.e0971a","name":"","func":"var R1_LowAlm = [];\nvar R1indextxt = [];\nR1_LowAlm = msg.payload;\nR1indextxt[0] = \"T1000 low alarm active\"\nR1indextxt[1] = \"T1001 low alarm active\"\nR1indextxt[2] = \"T1002 low alarm active\"\n//and so on... up to element 17\n\nif (R1_LowAlm[0] === 0) {\n    msg.payload = R1indextxt[0];\n}\n\nif (R1_LowAlm[1] === 0) {\n    msg.payload = R1indextxt[1];\n}\n\nif (R1_LowAlm[2] === 0) {\n    msg.payload = R1indextxt[2];\n}\n\nreturn msg;","outputs":1,"noerr":0,"x":930,"y":245,"wires":[["fb1897e6.a9a87"]]},{"id":"31b5623c.c2558e","type":"pac-device","z":"","address":"10.0.4.10","protocol":"http"}]

I noticed that no one has tackled your issue. It does help if you post a flow that is not hardware specific.
Take the time to write a function that reproduces the data send out by the Opto 22 groov AR-1. That way I don't have to own one to help. I was able to deduce and reverse figure out what it sends based on the function you wrote.

You need to think about not coding it to a fixed array index length.
Try this out.

[{"id":"3d8b5600.b8529a","type":"inject","z":"32b9138.e8e466c","name":"","topic":"","payload":"","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":370,"y":320,"wires":[["edbb9fbe.c16818"]]},{"id":"9d4b75a8.a7b28","type":"debug","z":"32b9138.e8e466c","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":950,"y":320,"wires":[]},{"id":"5f97342a.9f22f4","type":"function","z":"32b9138.e8e466c","name":"solution","func":"//Starting opening of the email msg\nvar email_msg_payload = 'Warning the following low alarms are actvie' + \"\\n\\r\" + \"\\n\\r\";\n\nvar LowAlm = msg.payload;\n\n//map the array to compare each value stored inside each index\n var format_LowAlm = LowAlm.map((current_element_inside_index_number, index_number) =>{\n    //Check the Value of each LowAlm index and add it to the final email msg payload\n    if (current_element_inside_index_number === 0) \n    {\n        email_msg_payload = email_msg_payload + 'T' + (1000+index_number) + ' low alarm active' + \"\\n\\r\";\n    }\n});\n\n//End of emal, add a signature\nemail_msg_payload = email_msg_payload + \"\\n\\r\" + 'Thank you,' + \"\\n\\r\" + 'Your Friendly Node-Red Aleart System';\n\n//msg.payload - the content inside the email\nmsg.payload = email_msg_payload;\n//msg.topic - subject line of the email\nmsg.topic = 'ALERT, Danger Will Robinson Opto 22 groov AR-1'\n//msg.to - the emails we are sending this to\nmsg.to = [\n    'example1@someemailaddress.com',\n    'example2@someemailaddress.com'\n    ]\n\nreturn msg;","outputs":1,"noerr":0,"x":800,"y":320,"wires":[["9d4b75a8.a7b28"]]},{"id":"edbb9fbe.c16818","type":"function","z":"32b9138.e8e466c","name":"FAKE Opto 22 groov AR-1","func":"\n//mimmic the Opto 22 msg.payload\n//set all events to trigger = of zero\nmsg.payload = \n[\n    0,\n    1,\n    0,\n    0,\n    1,\n    0,\n    0,\n    0,\n    1,\n    0,\n    0,\n    0,\n    0,\n    0,\n    1,\n    0,\n    0,\n    0,\n    0,\n    1,\n    0,\n    0,\n    0\n];\n\nreturn msg;\n","outputs":1,"noerr":0,"x":580,"y":320,"wires":[["5f97342a.9f22f4"]]}]

As you can see It does not care how big the array is.
you might want to add more checks and if statments to handle the logic of it not having a 0 value etc but this should get you on the right path.

CODE IN Function Block "solution"

//Starting opening of the email msg
var email_msg_payload = 'Warning the following low alarms are actvie' + "\n\r" + "\n\r";

var LowAlm = msg.payload;

//map the array to compare each value stored inside each index
 var format_LowAlm = LowAlm.map((current_element_inside_index_number, index_number) =>{
    //Check the Value of each LowAlm index and add it to the final email msg payload
    if (current_element_inside_index_number === 0) 
    {
        email_msg_payload = email_msg_payload + 'T' + (1000+index_number) + ' low alarm active' + "\n\r";
    }
});

//End of emal, add a signature
email_msg_payload = email_msg_payload + "\n\r" + 'Thank you,' + "\n\r" + 'Your Friendly Node-Red Aleart System';

//msg.payload - the content inside the email
msg.payload = email_msg_payload;
//msg.topic - subject line of the email
msg.topic = 'ALERT, Danger Will Robinson Opto 22 groov AR-1'
//msg.to - the emails we are sending this to
msg.to = [
    'example1@someemailaddress.com',
    'example2@someemailaddress.com'
    ]

return msg;

Thanks I really appreciate your response and effort in this. You're right I probably should have put this on the Opto 22 forum, but I felt my challenge was more in JavaScript than in anything hardware specific. This does get me on the right path.

1 Like

no problem @lsadow :slight_smile:

I'm still a JavaScript nubblet compared to many here on the forums.
I could see what ya wanted but you were lacking specific tools in your tool box to get the job done. Nothing worse than knowing what you want to do but lack the word to find it.

array.map is the tool you were looking for

It does all sorts of cool things......works with objects too
handy for things like an an array of objects or objects with arrays inside of them

keep us posted on your success. We like to see the end product when its all working

So here is what I have come up with. The only problem I am left with is that the way it is coded now, an email will be sent with each inject of the flow. I only want the email to go out once when alarms are active. I think maybe the rbe node can be used somehow to do this?
Any ideas?

[{"id":"fae1c9a.3fe49b8","type":"inject","z":"c97af0fa.6fb9","name":"","topic":"","payload":"","payloadType":"num","repeat":"3600","crontab":"","once":false,"onceDelay":0.1,"x":288,"y":258,"wires":[["a84d3449.af0e88"]]},{"id":"85f79ab7.071c58","type":"debug","z":"c97af0fa.6fb9","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":429,"y":498,"wires":[]},{"id":"a84d3449.af0e88","type":"function","z":"c97af0fa.6fb9","name":"simulate I 32 from SNAP S-2","func":"\n//mimmic the Opto 22 msg.payload\n//set all events to trigger = of zero\nmsg.payload = \n[\n    0,\n    0,\n    1,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0\n];\n\nreturn msg;\n","outputs":1,"noerr":0,"x":347,"y":318,"wires":[["8052cd35.8b6aa8"]]},{"id":"ce24e154.f29cf8","type":"function","z":"c97af0fa.6fb9","name":"build email","func":"//Build the email message:\nvar LowAlm = msg.payload; //the I 32 alarm table copied from the Opto controller\nvar R1indextxt = []; //array holding the text for the alarm messages\nvar R1indextxt_final = []; //array holding the alarms that are active\nvar Rindextxt_finalload = []; //array holding the alamrs with the null removed\n\nR1indextxt[0] = \"T1000 low alarm active\"\nR1indextxt[1] = \"T1001 low alarm active\"\nR1indextxt[2] = \"T1002 low alarm active\"\nR1indextxt[3] = \"T1003 low alarm active\"\nR1indextxt[4] = \"T1004 low alarm active and whatever\"\nR1indextxt[5] = \"T1005 low alarm active\"\nR1indextxt[10] = \"T1015 low alarm active and more text\"\n//and so on... up to element 100 or whatever is needed\n\n//map the array to match the read in I 32 table alarm condition to the proper text field\n var format_LowAlm = LowAlm.map((element, index_number) =>{\n    //Check the Value of each index and assign a text alarm if equals one\n    if (element === 1) \n    {\n        R1indextxt_final[index_number] = R1indextxt[index_number];\n    }\n});\n\n//get rid of the null elements \nR1indextxt_finalload = R1indextxt_final.filter(function(i){return i !== null; });\n\n//msg.payload - the content inside the email\nmsg.payload = \"PPXX Process Alarm ||\" + R1indextxt_finalload.join(\" // \");\n//msg.topic - subject line of the email\nmsg.topic = 'PPXX Process Alarm'\n\n//this can be set in the email node\nmsg.to = []\n//msg.includes = Alarm_active;\nmsg.active = msg.payload;\n//msg.active = Alarm_active;\n\nreturn msg;","outputs":1,"noerr":0,"x":448,"y":434,"wires":[["85f79ab7.071c58","fa4b7c4.ad20b8","d9aa46ab.94a198"]]},{"id":"8052cd35.8b6aa8","type":"function","z":"c97af0fa.6fb9","name":"evaluate array","func":"//evaluate alarm array: does an email need to be sent?\nfunction isOne(One) {\n    return One === 1;\n}\n\nvar LowAlm = msg.payload; //the I 32 alarm table copied from the Opto controller\nvar Alarm_active; //boolean to hold status of alarm state: if email should be sent\n\nAlarm_active = LowAlm.some(isOne);\nmsg.active = Alarm_active;\nmsg.payload = LowAlm;\n\nreturn msg;","outputs":1,"noerr":0,"x":311,"y":378,"wires":[["58e52cc1.67958c"]]},{"id":"58e52cc1.67958c","type":"switch","z":"c97af0fa.6fb9","name":"","property":"active","propertyType":"msg","rules":[{"t":"true"},{"t":"false"}],"checkall":"true","repair":false,"outputs":2,"x":297,"y":440,"wires":[["ce24e154.f29cf8"],["85f79ab7.071c58"]]},{"id":"fa4b7c4.ad20b8","type":"e-mail","z":"c97af0fa.6fb9","server":"smtp.gmail.com","port":"465","secure":true,"name":"","dname":"","x":677,"y":434,"wires":[]},{"id":"d9aa46ab.94a198","type":"debug","z":"c97af0fa.6fb9","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":696,"y":491,"wires":[]}]

How much time should elapse before it is valid to send another alarm email?

I like your improvisation with the use of .some

however as soon as .some finds its first true value it will stop searching

So great for finding out if one of the values is 1 bad for tracking how many alarms or what alarm is active and not to send another email.

Example case:

T1001 goes active - send email - track that we are .some true
T1003 goes active - HOLD UP we are already active and sent a email.

so as more go active after T1001 the logic has no idea

Also in the Build Email Function you are all ready doing a check. Why do it twice ?

I don't have time today. Check back tomorrow around 8pm Eastern Standard Time USA I'll have something else to point you in the right direction.

Until then look at https://nodered.org/docs/user-guide/writing-functions#storing-data

This is how I would store the state of an alert
However you can use files, a database, any thing that lives outside the life span of one event.

Here is my latest version. I put a rbe node in, right after the array is read from the device. This allows a new alarm to be passed through, or if all zeroes then the switch node catches it.
Thanks for pointing out the link. That brings me to another question, I will have about a dozen of these flows from about 12 different devices. Will this be an issue with variables? thanks,

[{"id":"d6e25a11.0fc41","type":"inject","z":"29096dc9.e0971a","name":"","topic":"","payload":"","payloadType":"num","repeat":"5","crontab":"","once":false,"onceDelay":0.1,"x":224,"y":186,"wires":[["42b90340.1c09dc"]]},{"id":"fb1897e6.a9a87","type":"debug","z":"29096dc9.e0971a","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":784,"y":354,"wires":[]},{"id":"42b90340.1c09dc","type":"pac-read","z":"29096dc9.e0971a","device":"bc5622e6.56b0c8","dataType":"int32-table","tagName":"PP19Alarm_Array","tableStartIndex":"0","tableLength":"100","value":"","valueType":"msg.payload","topic":"","topicType":"none","name":"","x":274,"y":243,"wires":[["67d7fd7.8b63c84"]]},{"id":"bea1c2d0.f62f5","type":"function","z":"29096dc9.e0971a","name":"evaluate array","func":"//evaluate alarm array: does an email need to be sent?\nfunction isOne(One) {\n    return One === 1;\n}\n\nvar Alm = msg.payload; //the I 32 alarm table copied from the Opto controller\nvar Alarm_active; //boolean to hold status of alarm state: if email should be sent\n\nAlarm_active = Alm.some(isOne);\nmsg.active = Alarm_active;\nmsg.payload = Alm;\n\nreturn msg;\n","outputs":1,"noerr":0,"x":257,"y":301,"wires":[["cca2eddb.07cbb8"]]},{"id":"67d7fd7.8b63c84","type":"rbe","z":"29096dc9.e0971a","name":"","func":"rbe","gap":"","start":"","inout":"out","property":"payload","x":470,"y":243,"wires":[["bea1c2d0.f62f5"]]},{"id":"cca2eddb.07cbb8","type":"switch","z":"29096dc9.e0971a","name":"","property":"active","propertyType":"msg","rules":[{"t":"true"},{"t":"false"}],"checkall":"true","repair":false,"outputs":2,"x":428,"y":301,"wires":[["7cfa37b0.ef27c"],["7cf11937.bb8378"]]},{"id":"7cfa37b0.ef27c","type":"function","z":"29096dc9.e0971a","name":"build email","func":"//Build the email message:\nvar Alm = msg.payload; //the I 32 alarm table copied from the Opto controller\nvar Indextxt = []; //array holding the text for the alarm messages\nvar Indextxt_final = []; //array holding the alarms that are active\nvar Indextxt_finalload = []; //array holding the alarms with the null removed\n\nIndextxt[0] = \"FT5501 low low alarm active\"\nIndextxt[1] = \"T1001 low alarm active\"\nIndextxt[2] = \"T1002 low alarm active\"\nIndextxt[3] = \"T1003 low alarm active\"\nIndextxt[4] = \"T1004 low alarm active and whatever\"\nIndextxt[5] = \"T1005 low alarm active\"\nIndextxt[6] = \"T1015 low alarm active and more text\"\nIndextxt[7] = \"FT5501 low low alarm active\"\nIndextxt[8] = \"T1001 low alarm active\"\nIndextxt[9] = \"T1002 low alarm active\"\nIndextxt[26] = \"T1003 low alarm active\"\nIndextxt[32] = \"T1004 low alarm active and whatever\"\n/*\nIndextxt[5] = \"T1005 low alarm active\"\nIndextxt[10] = \"T1015 low alarm active and more text\"\nIndextxt[0] = \"FT5501 low low alarm active\"\nIndextxt[1] = \"T1001 low alarm active\"\nIndextxt[2] = \"T1002 low alarm active\"\nIndextxt[3] = \"T1003 low alarm active\"\nIndextxt[4] = \"T1004 low alarm active and whatever\"\nIndextxt[5] = \"T1005 low alarm active\"\nIndextxt[10] = \"T1015 low alarm active and more text\"\nIndextxt[0] = \"FT5501 low low alarm active\"\nIndextxt[1] = \"T1001 low alarm active\"\nIndextxt[2] = \"T1002 low alarm active\"\nIndextxt[3] = \"T1003 low alarm active\"\nIndextxt[4] = \"T1004 low alarm active and whatever\"\nIndextxt[5] = \"T1005 low alarm active\"\nIndextxt[10] = \"T1015 low alarm active and more text\"\n//and so on... up to element 100 or whatever is needed\n\n*/\n\n//map the array to match the read in I 32 table alarm condition to the proper text field\n var format_Alm = Alm.map((element, index_number) =>{\n    //Check the Value of each index and assign a text alarm if equals one\n    if (element === 1) \n    {\n        Indextxt_final[index_number] = Indextxt[index_number];\n    }\n});\n\n//get rid of the null elements \nIndextxt_finalload = Indextxt_final.filter(function(i){return i !== null; });\n\n//msg.payload - the content inside the email\nmsg.payload = \"PP19 Process Alarm ||\" + Indextxt_finalload.join(\" // \");\n//msg.topic - subject line of the email\nmsg.topic = 'PP19 Process Alarm'\n\n//this can be set in the email node\nmsg.to = []\n//msg.includes = Alarm_active;\nmsg.active = msg.payload;\n//msg.active = Alarm_active;\n\nreturn msg;","outputs":1,"noerr":0,"x":609,"y":301,"wires":[["c8bf5976.a241c","fb1897e6.a9a87"]]},{"id":"c8bf5976.a241c","type":"e-mail","z":"29096dc9.e0971a","server":"smtp.gmail.com","port":"465","secure":true,"name":"","dname":"","x":784,"y":302,"wires":[]},{"id":"7cf11937.bb8378","type":"debug","z":"29096dc9.e0971a","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":589,"y":372,"wires":[]},{"id":"197d37f1.426ba8","type":"comment","z":"29096dc9.e0971a","name":"PP19 Alarms","info":"","x":232,"y":107,"wires":[]},{"id":"8463362b.0e9918","type":"function","z":"29096dc9.e0971a","name":"simulate I 32 from Opto SNAP S2","func":"\n//mimmic the Opto 22 msg.payload\n//set all events to trigger = of zero\nmsg.payload = \n[\n    0,\n    0,\n    0,\n    1,\n    1,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0,\n    0\n];\n\nreturn msg;\n","outputs":1,"noerr":0,"x":672,"y":141,"wires":[[]]},{"id":"73b9eddb.b3a2ac","type":"comment","z":"29096dc9.e0971a","name":"use simulate as an alternate","info":"","x":647,"y":99,"wires":[]},{"id":"bc5622e6.56b0c8","type":"pac-device","z":"","address":"10.0.19.10","protocol":"http"}]

Checking out your flow now. Let me see what i can do as I now have some time.

OK I really want to throw some code at you but I think I'm not understanding what your trying todo.

You keep wanting to control what msg is sent for each array index example: Indextxt[3] = "T1003 low alarm active"

You then state "I will have about a dozen of these flows from about 12 different devices."

I would avoid hard coding values for the index like this. In order to help you what is this T1003 value represent? I also see a FT5501? you also wrtie "more text here" after some of them.

If it was me I would use a change node after each one of these opto units and send the value I want for naming.

This is not your fault. I'm just lost on what your trying todo.

Side note: I see your using some expensive hardware. I get that you have NDA's (non disclosure agreements) and ya just can't post the whole problem. I feel you, I work in the industry as well. But you are allowed to reframe the issue. I'm working on some fancy object detection service for detecting theft of items for my current client. I can't talk about the Items, I cant talk about the company. I can reframe the entire thing though.

Example: I need help Detecting when kittens not cats are moved from one pen to the other or removed from the area entirely.

Give me some more details even if they are retold in a fun way :slight_smile: It will help me understand your problem. It will help others out here as well.

Thanks for your response, I apologize if I was not forthcoming enough with information on what is being accomplished. That said, the flow right now does exactly what I want it to do although I have not implemented it yet in the "real world".
Yes I can be very specific without giving away proprietary information. I work in an energy / oil research lab. We run pilot plant scale equipment that runs 24 / 7 but we often only have staff on site during business hours. I want the equipment to text me (from gmail) when an alarm condition occurs, with the specific point and alarm text field that has gone into alarm. Taking an example, one piece of equipment has about 100 thermocouples, and associated with each read in thermocouple value (i.e. T1001) for example, is a low low, low, high, and high high alarm. That is 400 alarm points for the thermocouples alone. The tags or labels for these points are not in any sequence, such as T1000 T1001 T1002 as was given in your first example flow. So if T1000 is in low alarm, I want the following text to come to my phone "T1000 low alarm". Right now this happens and works as it should.
I have thought about sending this alarm text field from the Opto controller as you mentioned, and that is a good point and suggestion. However I wanted to limit the memory usage on the controller, since I would have between 400 to 500 text fields stored in memory. Also, if I wanted to make a change to any of this while the equipment is running such as add a new alarm point, I would possibly need to download to the Opto controller which would cause equipment upset. For these reasons I wanted to do this in node red.
Does that make sense? Am I way off base?
Once again I highly value your input! Thanks so much,

Thank you!

If it was me and I wanted this code to work on allot of attached units I'd sort all the data into object's first.
Before I spend a good chunk of time this weekend puting together the code would you be interested in something like this......

So T1001, T1002, and T1003 are in an alarm state.

What we can do is first after your Opto msg we can add a change node where you input the alarm type this way you can edit this one node for each opto output device

Then we sort the values from the Opto array into the object example I showed you above.

Now we can track if a value changes from true/false and send a email.

If this is what your looking for I'll peck at the code this weekend.

Sure that option would be worth trying.