Subflow as a function

I try to use a node N that expect its input as msg.payload.
It bothers me because msg.payload already had information when it reaches N.
So I surrounded this node to save the state of msg.payload, and restore it after N. It is ugly because each time I want to use N, I must add the save/restore nodes.
Maybe subflow could solve my problems?
I created the subflow, then I set the "inputs" of the subflow (in Properties / Environment Variables).
I need two of them:

  1. The name where to store the result of N. It is kind of OK:
    • easy if I store it in the root msg (example: "data" to store in msg.data)
    • I have no idea of how to elegantly provide a Json path to allows storage outside of the msg root.
  2. N require a string parameter. I would like to provide it as an input expression accessible from the subflow. Currently, the only type of "input" / Environment Variables are string, number, bool, json, buffer, env variable. None of them is evaluated, so no dynamic input possible.

I looked for the following in the forum (but I didn't find anything relevant to my problem):

  • dynamically set a property
  • jsonata as input for subflow
  • change subflow (If I could get the same input interface as the change node it would solve my problem)
  • Parameter subflow (It leads med to this page where I can see in the screen shot "item type selector" the type expression that could solve my problem, but the feature is marked as reach for the 1.0.0: Milestone Release (Subflow Instance properties), and there is not yet an expression input type in version 1.0.2).

My current solution is to add a change node before my subflow, but it is requiring two nodes instead of one.

Do you have an idea on how to implement point 1 and / or 2 ?

node red version: 1.0.2

Can't you just move the payload into, as you suggest, msg.data before node N and then access it from there afterwards? I don't see what the subflow is doing for you.

I would like it to be more generic: one instance of the subflow read input from msg.input1 and write output in mgs.output1, and another instance of the subflow read input from msg.input2 and write output in mgs.output2.
In my case, I want to get status a multiple device by chaining call to the subflow, so I need to store output in different places.
I want the subflow to be a way to create / adapt node from other nodes.

I know I am going to shoot myself in the foot by joining in here, but I want to help all the same.

I am sort of getting what you mean, but also not quite.

Reading the post, you have resigned yourself to the change node and it works. Kind of. Just you don't like the extra node/s.

I sort of understand that too. Been there. Done that when I was learning.

Here's my take on what you need to do.

I am not getting exactly what you mean there, but you could do something like this:

This is a badly written bit of code, but it is only intended to show you the idea where you can save multiple thing for multiple devices easily.

[{"id":"a74c1fb7.6ae5c8","type":"inject","z":"accbdb61.d75918","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":2500,"wires":[["8e4737db.e94bb"]]},{"id":"8e4737db.e94bb","type":"function","z":"accbdb61.d75918","name":"Construct message","func":"msg.payload = {\n    device: \"device2\",\n    ustatus: \"offline\",\n    extra: \"and more stuff\"\n}\nreturn msg;","outputs":1,"noerr":0,"x":310,"y":2500,"wires":[["fd79a15b.414798"]]},{"id":"fd79a15b.414798","type":"function","z":"accbdb61.d75918","name":"Subflow starts with this node.","func":"//   open the window fully to read all the text easier\nlet device = msg.payload.device;\nlet ustatus = msg.payload.ustatus;\nlet extra = msg.payload.extra;\n\n//  This has established the variables from the payload\n\ncontext.set(device,device);\ncontext.set(device+\"status\",ustatus);\n\n\n\nreturn msg;","outputs":1,"noerr":0,"x":590,"y":2500,"wires":[[]]},{"id":"2626268a.5bccba","type":"function","z":"accbdb61.d75918","name":"Construct message","func":"msg.payload = {\n    device: \"device1\",\n    ustatus: \"example\",\n    extra: \"more stuff\"\n}\nreturn msg;","outputs":1,"noerr":0,"x":310,"y":2450,"wires":[["fd79a15b.414798"]]},{"id":"4d2153fa.9d309c","type":"function","z":"accbdb61.d75918","name":"Construct message","func":"msg.payload = {\n    device: \"device3\",\n    ustatus: \"online\",\n    extra: \"even more stuff\"\n}\nreturn msg;","outputs":1,"noerr":0,"x":310,"y":2550,"wires":[["fd79a15b.414798"]]},{"id":"5fd4cf0e.d12a18","type":"inject","z":"accbdb61.d75918","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":2450,"wires":[["2626268a.5bccba"]]},{"id":"a4b5f895.8061e","type":"inject","z":"accbdb61.d75918","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":2550,"wires":[["4d2153fa.9d309c"]]}]

Walk through:
Click on the subflow starts with this node node. Open the context menu and look at the node's stuff.

Click on the first button.

That will set the context naming device1 with a status.
(I picked this only because that is the road on which I have been)

Click the second button and you will get a new entry for the second device and its status.

This can go on "forever".

I know it isn't the answer to what you want to do, but I hope it helps you understand a trick with how you can send a few packets to the subflow.

It isn't the answer primary answer (it still requires one node before the subflow), but thank you for responding, it is a neat trick.
PS: sorry for my poor English. I am not a native speaker.

Well, it does and it doesn't.

Really it only needs what you put in it.

I'll stick my neck out.

Send me a basic idea of what you are wanting to do and I will see what I can do for you.

Say two input devices with their messages and explain what you want to do.

Up to you.

Here is an example. Please see the description of each node and subflow.

[{"id":"dc1d621d.b6606","type":"subflow","name":"my subflow","info":"I use a node (api-current-state) from a library (node-red-contrib-home-assistant-websocket) that expects its argument in `payload.entity_id`. So I backup `msg.payload` into `msg.payload_backup` during the call to the node api-current-state, and restore the payload after that","category":"","in":[{"x":40,"y":40,"wires":[{"id":"359ddf85.db8608"}]}],"out":[{"x":920,"y":40,"wires":[{"id":"21d5ab1e.9f8524","port":0}]}],"env":[],"color":"#DDAA99"},{"id":"359ddf85.db8608","type":"function","z":"dc1d621d.b6606","name":"save payload, set argument","func":"msg.backup_payload = msg.payload;\nmsg.payload = {entity_id: msg.subflowParameter};\nreturn msg;","outputs":1,"noerr":0,"x":260,"y":40,"wires":[["43e4a3ea.fa60e4"]],"info":"the next node in this sequance expect its input to be in `msg.payload`.\nSo I save the content of `msg.payload` in `msg.backup_payload`, and set `msg.payload` to be the correct input"},{"id":"21d5ab1e.9f8524","type":"function","z":"dc1d621d.b6606","name":"restore payload","func":"msg.payload = msg.backup_payload;\ndelete msg.backup_payload;\nreturn msg;","outputs":1,"noerr":0,"x":760,"y":40,"wires":[[]],"info":"restores the initial `msg.payload`, and remove the backup `msg.backup_payload`"},{"id":"43e4a3ea.fa60e4","type":"function","z":"dc1d621d.b6606","name":"node that I want to encapsulate","func":"/*fake node result*/\nmsg.data = {\n    \"id\": msg.payload,\n    \"output\": \"you are the best\"\n}\nreturn msg;","outputs":1,"noerr":0,"x":530,"y":40,"wires":[["21d5ab1e.9f8524"]],"info":"This node is acctualy the node api-current-state from the\nlibrary node-red-contrib-home-assistant-websocke.\nIt expect a device id in `msg.payload`, and it stores its result in `msg.data`"},{"id":"4559e457.9135ac","type":"inject","z":"6820b1fc.837188","name":"part one of my flow","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":150,"y":220,"wires":[["bd7fd1e4.38d7f8"]]},{"id":"f0b37b84.d80658","type":"inject","z":"6820b1fc.837188","name":"part two of my flow","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":150,"y":280,"wires":[["270e3e23.117a42"]]},{"id":"bd7fd1e4.38d7f8","type":"function","z":"6820b1fc.837188","name":"generation of the message for part one","func":"return {\n    storage: {\n        deviceId: \"device1\"\n    }\n};","outputs":1,"noerr":0,"x":440,"y":220,"wires":[["409ca645.a3408"]],"info":"In this part of my flow, the message has this form.\nThis function is only here to generate it.\nIt is not this node that I want to get rid of."},{"id":"270e3e23.117a42","type":"function","z":"6820b1fc.837188","name":"generation of the message for part two","func":"return {\n    payload: {\n        deviceId: \"device2\"\n    }\n};","outputs":1,"noerr":0,"x":440,"y":280,"wires":[["592be2dc.daa08c"]],"info":"Same as above"},{"id":"409ca645.a3408","type":"change","z":"6820b1fc.837188","name":"I want to get rid of this","rules":[{"t":"set","p":"subflowParameter","pt":"msg","to":"msg.storage.deviceId\t","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":750,"y":220,"wires":[["18248b43.2b6c05"]],"info":"This node is only here to put the input in a field expected by the subflow.\nIn the best-case scenario, I would like to have the properties rules of this `change` node in the properties rules of the subflow, or have a possibility to set an environment variable to an expression."},{"id":"592be2dc.daa08c","type":"change","z":"6820b1fc.837188","name":"I want to get rid of this too","rules":[{"t":"set","p":"subflowParameter","pt":"msg","to":"msg.payload.deviceId\t","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":760,"y":280,"wires":[["77cbf548.9320b4"]],"info":"Same has the other change node"},{"id":"77cbf548.9320b4","type":"subflow:dc1d621d.b6606","z":"6820b1fc.837188","name":"","env":[],"x":960,"y":280,"wires":[["b6664c6.33dc5b"]]},{"id":"a23e5a3f.c36c18","type":"function","z":"6820b1fc.837188","name":"rest of the flow part one","func":"\nreturn msg;","outputs":0,"noerr":0,"x":1170,"y":220,"wires":[],"info":"After the call of `my subflow`, I have access to its output in `msg.data`."},{"id":"b6664c6.33dc5b","type":"function","z":"6820b1fc.837188","name":"rest of the flow part two","func":"\nreturn msg;","outputs":0,"noerr":0,"x":1170,"y":280,"wires":[],"info":"Same as above node."},{"id":"18248b43.2b6c05","type":"subflow:dc1d621d.b6606","z":"6820b1fc.837188","name":"","env":[],"x":960,"y":220,"wires":[["a23e5a3f.c36c18"]]}]

Again, thank you very much for the time you spend for me.

No problem.

The flow you posted works, but I think there is a lot of stuff lost in translation here. Nothing against you not speaking English. It is a phrase used to indicate a failure of communications.

It isn't I want to drag you through this only for the sake of being difficult. Rather I want to help you get what you want done to get done.

I appreciate this is a "basic view" of the flow. But let's pull it down/apart:
A message is created. (First function node)
It sends:

return {
    storage: {
        deviceId: "device1"
    }
};

Fair enough.

The change node is: I want to get rid of this
Which simply moves things around in the message structure.

Basically what was in the message is now in msg.subflowParameter. Again fair enough.

Now we go into the subflow.

Node1:

msg.backup_payload = msg.payload;
msg.payload = {entity_id: msg.subflowParameter};
return msg;

Ok, you backup the msg.payload to msg.backup_payload. I'll get back to this soon.
You then set msg.payload to msg.subflowParameter. Yeah, ok. Confused.

Node2:

/*fake node result*/
msg.data = {
    "id": msg.payload,
    "output": "you are the best"
}
return msg;

Line 1. Ok, this is "fake node result". Which part exactly?

I can't work on it if I don't know what is real and which is faked.

Sorry.

I'm not seeing where you need to preserve data from messages

Can you elaborate a bit more on that please?
Putting aside msg.payload is already in use (etc)

You seem to be tripping over things which may (or not) really be problems, and only thinking they are problems thus making your options more difficult than they need to be.

msg.payload should be (between nodes) only carrying information you want.
(I say should. That is all it can do really - well let's not get bogged down in semantics about that.)

You have a flow.
Node 1: Does what ever it needs to do. msg.payload is constructed ready to send to the next node.
Node 2: Receives msg.payload and acts on that. A new msg.payload is created and is made ready to send to the next node.
And so on.

How are you wanting to access this information which is stored in the subflow?
Does it need to be accessed by anything else or is it just you are wanting to optimise using this piece of code in multiple parts of the flow?

I would suggest you write a flow which does what you want.
Indicate where the subflow starts and ends with comment nodes.

Something like this:

Given the first function node creates the message (if not possible in the inject node).
The second, third and fourth (as per your example) are the nodes doing what you want, and the output node signals the end of what you want to do and shows the user what the msg should look like.

Hoping to hear back soon.

Here is the full context:
My flow can be triggered by two remotes (R1, R2), each one controlling a specific light (real light RL1, real light RL2).

Each light has two id (one for day mode (L1 or L2), and one for night mode (L1N or L2N)), each id correspond to devices in home assistant.

L1 is mutually exclusive from L1N (and L2 is mutually exclusive from L2N). By mutually exclusive, I mean that if the latest instruction received by home assistant was turn on L1, then RL1 is in day mode. To know which mode is active for RL1, I need to get state of both L1 and L1N, and find out which one was the latest to receive a turn on instruction.
I would like to have a single flow for both R1 and R2, and I try to use a sub flow to warp the get state node call.
My subflow needs to get its parameter (L1, L2, L1N or L2N), and output the last date at which the device received a turn on instruction, and save it somewhere.

Here is the flow. It still needs a node before each call to the subflow to set the destination of the subflow result, and to set the input correctly (correctly = the way node that I want to encapsulate = get state expects its input).

[{"id":"af2298fb.54c768","type":"inject","z":"830c2a2d.0b8f5","name":"remote 1","topic":"","payload":"R1","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":340,"y":920,"wires":[["3450799a.69e726"]]},{"id":"6405cdae.d1b0bc","type":"inject","z":"830c2a2d.0b8f5","name":"remote 2","topic":"","payload":"R2","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":340,"y":1000,"wires":[["3450799a.69e726"]]},{"id":"3450799a.69e726","type":"function","z":"830c2a2d.0b8f5","name":"map remote to device","func":"switch (msg.payload) {\n    case \"R1\":\n        msg.payload = {\n                remote: \"R1\",\n                device: \"L1\",\n            };\n        break;\n    case \"R2\":\n        msg.payload = {\n                remote: \"R2\",\n                device: \"L2\"\n            };\n        break;\n}\n\nreturn msg;","outputs":1,"noerr":0,"x":560,"y":960,"wires":[["4ac8f332.e7156c"]]},{"id":"a8be76a9.47c79","type":"function","z":"830c2a2d.0b8f5","name":"restore payload","func":"msg.payload = msg.backup_payload;\nmsg[msg.subflowReturnField] = msg.data\ndelete msg.backup_payload;\ndelete msg.data;\nreturn msg;","outputs":1,"noerr":0,"x":1800,"y":960,"wires":[["957adc8d.18afd8"]],"info":"restores the initial `msg.payload`, and remove the backup `msg.backup_payload`"},{"id":"63055c6b.ce0374","type":"function","z":"830c2a2d.0b8f5","name":"node that I want to encapsulate","func":"function randomDate(start, end) {\n    return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime()));\n}\n\nrandomDate(new Date(2012, 0, 1), new Date())\n\nmsg.data = {\n    \"entity_id\": msg.payload,\n    \"last_updated\": randomDate(new Date(2012, 0, 1), new Date()).valueOf()\n}\nreturn msg;","outputs":1,"noerr":0,"x":1550,"y":960,"wires":[["a8be76a9.47c79"]],"info":"`msg.data` should be a output of an http call to\nmy home assistant server with `entity_id` as parametter.\nI don't know in advence whate `last_updated` will be, so I affect it a random value for this example.\n`entity_id` is the same as the value msg.payload at\nthe start of the subflow."},{"id":"1f1826cc.01e829","type":"function","z":"830c2a2d.0b8f5","name":"save payload, set argument","func":"msg.backup_payload = msg.payload;\nmsg.payload = {entity_id: msg.subflowParameter};\nreturn msg;","outputs":1,"noerr":0,"x":1260,"y":960,"wires":[["63055c6b.ce0374"]],"info":"the next node in this sequance expect its input to be in `msg.payload`.\nSo I save the content of `msg.payload` in `msg.backup_payload`, and set `msg.payload` to be the correct input"},{"id":"7002de7.126f5a","type":"function","z":"830c2a2d.0b8f5","name":"restore payload","func":"msg.payload = msg.backup_payload;\nmsg[msg.subflowReturnField] = msg.data\ndelete msg.backup_payload;\ndelete msg.data;\nreturn msg;","outputs":1,"noerr":0,"x":3240,"y":960,"wires":[["6b8cd7a2.0e92f8"]],"info":"restores the initial `msg.payload`, and remove the backup `msg.backup_payload`"},{"id":"71e4eec4.29f4f8","type":"function","z":"830c2a2d.0b8f5","name":"save payload, set argument","func":"msg.backup_payload = msg.payload;\nmsg.payload = {entity_id: msg.subflowParameter};\nreturn msg;","outputs":1,"noerr":0,"x":2700,"y":960,"wires":[["acd7003f.df365"]],"info":"the next node in this sequance expect its input to be in `msg.payload`.\nSo I save the content of `msg.payload` in `msg.backup_payload`, and set `msg.payload` to be the correct input"},{"id":"5b22834e.ad5dfc","type":"comment","z":"830c2a2d.0b8f5","name":"preprocessing","info":"","x":690,"y":900,"wires":[]},{"id":"4ac8f332.e7156c","type":"function","z":"830c2a2d.0b8f5","name":"set subflowParameter for day device","func":"msg.subflowParameter = msg.device\nmsg.subflowReturnField = \"day\";\nreturn msg;","outputs":1,"noerr":0,"x":870,"y":960,"wires":[["1f1826cc.01e829"]],"info":"I want to get rid of this node"},{"id":"957adc8d.18afd8","type":"function","z":"830c2a2d.0b8f5","name":"set subflowParameter for night device","func":"msg.subflowParameter = msg.device + \"N\"\nmsg.subflowReturnField = \"night\";\nreturn msg;","outputs":1,"noerr":0,"x":2210,"y":960,"wires":[["71e4eec4.29f4f8"]],"info":"I want to get rid of this node"},{"id":"e649b617.703a2","type":"comment","z":"830c2a2d.0b8f5","name":"subflow start","info":"","x":1090,"y":900,"wires":[]},{"id":"991edf4e.a7aee","type":"comment","z":"830c2a2d.0b8f5","name":"subflow start","info":"","x":2470,"y":900,"wires":[]},{"id":"de8d9fa4.e5f078","type":"comment","z":"830c2a2d.0b8f5","name":"subflow end","info":"","x":1990,"y":900,"wires":[]},{"id":"da8c608c.f548e","type":"comment","z":"830c2a2d.0b8f5","name":"subflow end","info":"","x":3430,"y":900,"wires":[]},{"id":"acd7003f.df365","type":"function","z":"830c2a2d.0b8f5","name":"node that I want to encapsulate","func":"function randomDate(start, end) {\n    return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime()));\n}\n\nrandomDate(new Date(2012, 0, 1), new Date())\n\nmsg.data = {\n    \"entity_id\": msg.payload,\n    \"last_updated\": randomDate(new Date(2012, 0, 1), new Date()).valueOf()\n}\nreturn msg;","outputs":1,"noerr":0,"x":2990,"y":960,"wires":[["7002de7.126f5a"]],"info":"same has above"},{"id":"6b8cd7a2.0e92f8","type":"debug","z":"830c2a2d.0b8f5","name":"output","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"{\t    \"device\": $join([msg.payload.device, msg.day.last_updated > msg.night.last_updated ? \"\" : \"N\"]),\t    \"remote\": msg.payload.remote\t}","targetType":"jsonata","x":3610,"y":960,"wires":[]}]

The message expected at the last node might be

{
"device": "L1N",
"remote": "R1"
}

Here is another one. It is almost ok (I used your first idea):

[{"id":"5da3cfd8.8dcb18","type":"inject","z":"33e08bf7.43f924","name":"remote 1","topic":"","payload":"R1","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":340,"y":340,"wires":[["eb69b5a4.bbb268"]]},{"id":"4211ca7f.1531c4","type":"inject","z":"33e08bf7.43f924","name":"remote 2","topic":"","payload":"R2","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":340,"y":420,"wires":[["eb69b5a4.bbb268"]]},{"id":"eb69b5a4.bbb268","type":"function","z":"33e08bf7.43f924","name":"map remote to device","func":"switch (msg.payload) {\n    case \"R1\":\n        msg.payload = {\n                remote: \"R1\",\n                day: \"L1\",\n                night: \"L1N\"\n            };\n        break;\n    case \"R2\":\n        msg.payload = {\n                remote: \"R2\",\n                day: \"L2\",\n                night: \"L2N\"\n            };\n        break;\n}\n\nreturn msg;","outputs":1,"noerr":0,"x":560,"y":380,"wires":[["23fb61a8.a47bc6","2cf6e8ab.9546d8"]]},{"id":"bfddf808.172ba8","type":"function","z":"33e08bf7.43f924","name":"restore payload","func":"msg.payload = msg.backup_payload;\ndelete msg.backup_payload;\nreturn msg;","outputs":1,"noerr":0,"x":1940,"y":300,"wires":[["a0f46ae.a907d18"]],"info":"restores the initial `msg.payload`, and remove the backup `msg.backup_payload`"},{"id":"8722a499.b0184","type":"function","z":"33e08bf7.43f924","name":"node that I want to encapsulate","func":"function randomDate(start, end) {\n    return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime()));\n}\n\nrandomDate(new Date(2012, 0, 1), new Date())\n\nmsg.data = {\n    \"entity_id\": msg.payload,\n    \"last_updated\": randomDate(new Date(2012, 0, 1), new Date()).valueOf()\n}\nreturn msg;","outputs":1,"noerr":0,"x":1710,"y":300,"wires":[["bfddf808.172ba8"]],"info":"`msg.data` should be a output of an http call to\nmy home assistant server with `entity_id` as parametter.\nI don't know in advence whate `last_updated` will be, so I affect it a random value for this example.\n`entity_id` is the same as the value msg.payload at\nthe start of the subflow."},{"id":"c406d9b4.6c635","type":"function","z":"33e08bf7.43f924","name":"save payload, set argument","func":"msg.backup_payload = msg.payload;\nmsg.payload = msg.subflowParameter;\nreturn msg;","outputs":1,"noerr":0,"x":1440,"y":300,"wires":[["8722a499.b0184"]],"info":"the next node in this sequance expect its input to be in `msg.payload`.\nSo I save the content of `msg.payload` in `msg.backup_payload`, and set `msg.payload` to be the correct input"},{"id":"a93a1cb5.4396c8","type":"function","z":"33e08bf7.43f924","name":"restore payload","func":"msg.payload = msg.backup_payload;\ndelete msg.backup_payload;\nreturn msg;","outputs":1,"noerr":0,"x":1940,"y":460,"wires":[["a0f46ae.a907d18"]],"info":"restores the initial `msg.payload`, and remove the backup `msg.backup_payload`"},{"id":"a5023a2a.1d749","type":"function","z":"33e08bf7.43f924","name":"save payload, set argument","func":"msg.backup_payload = msg.payload;\nmsg.payload = msg.subflowParameter;\nreturn msg;","outputs":1,"noerr":0,"x":1440,"y":460,"wires":[["e63eeae.e151518"]],"info":"the next node in this sequance expect its input to be in `msg.payload`.\nSo I save the content of `msg.payload` in `msg.backup_payload`, and set `msg.payload` to be the correct input"},{"id":"73206fda.be91b","type":"comment","z":"33e08bf7.43f924","name":"preprocessing","info":"","x":710,"y":260,"wires":[]},{"id":"23fb61a8.a47bc6","type":"function","z":"33e08bf7.43f924","name":"set subflowParameter for day device","func":"msg.subflowParameter = msg.payload.day\nreturn msg;","outputs":1,"noerr":0,"x":910,"y":300,"wires":[["c406d9b4.6c635"]]},{"id":"2cf6e8ab.9546d8","type":"function","z":"33e08bf7.43f924","name":"set subflowParameter for night device","func":"msg.subflowParameter = msg.payload.night\nreturn msg;","outputs":1,"noerr":0,"x":910,"y":460,"wires":[["a5023a2a.1d749"]]},{"id":"7fa94eb3.08a3","type":"comment","z":"33e08bf7.43f924","name":"subflow start","info":"","x":1190,"y":260,"wires":[]},{"id":"80f6fd4c.588cf8","type":"comment","z":"33e08bf7.43f924","name":"subflow start","info":"","x":1190,"y":420,"wires":[]},{"id":"1abfd28f.5440c5","type":"comment","z":"33e08bf7.43f924","name":"subflow end","info":"","x":2130,"y":240,"wires":[]},{"id":"9844b3ca.7f67b","type":"comment","z":"33e08bf7.43f924","name":"subflow end","info":"","x":2130,"y":400,"wires":[]},{"id":"e63eeae.e151518","type":"function","z":"33e08bf7.43f924","name":"node that I want to encapsulate","func":"function randomDate(start, end) {\n    return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime()));\n}\n\nrandomDate(new Date(2012, 0, 1), new Date())\n\nmsg.data = {\n    \"entity_id\": msg.payload,\n    \"last_updated\": randomDate(new Date(2012, 0, 1), new Date()).valueOf()\n}\nreturn msg;","outputs":1,"noerr":0,"x":1710,"y":460,"wires":[["a93a1cb5.4396c8"]],"info":"same has above"},{"id":"ef835aaf.89ac9","type":"function","z":"33e08bf7.43f924","name":"output","func":"if(msg.data[0].last_updated > msg.data[1].last_updated)\n    return msg.data[0];\nelse\n    return msg.data[1];\n","outputs":1,"noerr":0,"x":2450,"y":360,"wires":[["408329b0.f56af"]]},{"id":"408329b0.f56af","type":"debug","z":"33e08bf7.43f924","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":2640,"y":360,"wires":[]},{"id":"a0f46ae.a907d18","type":"join","z":"33e08bf7.43f924","name":"","mode":"custom","build":"array","property":"data","propertyType":"msg","key":"topic","joiner":"[\\n","joinerType":"str","accumulate":false,"timeout":"1","count":"2","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"num","reduceFixup":"","x":2290,"y":360,"wires":[["ef835aaf.89ac9](file://\\n","joinerType":"str","accumulate":false,"timeout":"1","count":"2","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"num","reduceFixup":"","x":2290,"y":360,"wires":[["ef835aaf.89ac9)"]]}]

The message expected at the last node might be

{
"entity_id": "L1",
"last_updated": 1532152341151
}

There is still one problem:

  • The subflow expects its input to be at a fixed location (msg.subflowParameter), and since the result is in a fixed place (msg.data), I must run the two calls of the subflow in parallel and cannot do it in sequence.

I could merge it with the node map remote to device in this case (two calls close to each other), but I want to be able to use the subflow somewhere else in the flow, and then, I will need to also add a node to set the input.

I would like the subflow interface to accept an input (for example an expression, or a path in the msg (for example msg.payload.day in the last flow), and an output (for example msg.day). Some this like that:
(Jsonata is not available in the input type of an environment variable, I had to edit the html page for the screenshot).
Also, day as a string allows storing the result in msg.day and not in msg.data.device.day for example.
interface

I use msg to accumulate information during the sequence, so I want to keep what is in payload. The node that is encapsulated in the subflow expects msg.payload.entity_id to contain the device id (L1N for example in my example flow). So I backup everything from msg.payload, set the argument for my node that I want to encapsulate = get state, and once the node get state called, I restore my initial payload.

I hop it is clearer in my new flow example. In the first example, msg.data is fake. In my real flow, the get-state node generates msg.data with an http request to my home assistant server (using msg.payload.entity_id as parameter to get the state (on or off) of the light whose id is the value contained in msg.payload.entity_id)

It doesn't show in my example, but I may need information that was in the initial payload (the remote name for example). It is not revalent with the last example (in parallel), but for the sequential example, I construct the entity_id from the initial msg.payload.device twice (first time it is L1, then its L1D). I could store the information somewhere else (like mst.data).

At the end of the sequence (not in my examples), I use the data generated by the subflow.

I want to optimize using this piece of code in multiple parts of the flow.

In my example, the inject nodes / remote does create the initial payload.

Wow.

Thanks.

I shall look at it.

I have imported the first one and am looking at it.

I won't waste too much time now replying as I read your post. I'll get into reading what you said and try to work out how to do it.

The second flow which you say is based on what I suggested doesn't load. There is a syntax error in there and so I can't import it as code. (NR won't let it load.)

I have sort of bitten off a lot as I am not up to speed with what
msg.payload = {entity_id: msg.subflowParameter};
means.
My bad. But I will investigate.

I won't waste too much time in this reply. I will sit down and read what you said.

Running your first flow, a slight problem happened with R2 in that with the first two pressed I get/got L2N as the output. Then it started to toggle as it did with R1.

But that's a small thing. Don't worry.

I hope to get back to you soon.

(Sorry, late addition)
So, if I send two inputs of remote 1, the output is supposed to toggle from:
{"device":"L1N","remote":"R1"}
to
{"device":"L1","remote":"R1"}

But if I send the signal too quickly it doesn't always toggle.
Is the pause supposed to imply a change from daytime to night?

Ok, no promises.

Here are two flows which do what I think you want to do.

I put them together in one import only because two posts of the flow seems silly with how small they are.

[{"id":"67f696df.eefdf","type":"inject","z":"cd24bca6.19d15","name":"remote 1","topic":"","payload":"R1","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":100,"y":700,"wires":[["3fa36ebd.c201ca"]]},{"id":"f9059d78.f6817","type":"inject","z":"cd24bca6.19d15","name":"remote 2","topic":"","payload":"R2","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":100,"y":780,"wires":[["3fa36ebd.c201ca"]]},{"id":"3fa36ebd.c201ca","type":"function","z":"cd24bca6.19d15","name":"map remote to device","func":"switch (msg.payload) {\n    case \"R1\":\n        msg.payload = {\n                remote: \"R1\",\n                device: \"L1\",\n            };\n        break;\n    case \"R2\":\n        msg.payload = {\n                remote: \"R2\",\n                device: \"L2\"\n            };\n        break;\n}\n\nmsg.subflowParameter = msg.device\nmsg.subflowReturnField = \"day\";\n\nreturn msg;","outputs":1,"noerr":0,"x":320,"y":740,"wires":[["4d195895.519b9"]]},{"id":"4d195895.519b9","type":"function","z":"cd24bca6.19d15","name":"This node is the subflow","func":"let remote = msg.payload.remote;\nlet device = msg.payload.device;   //  should not be needed/used.\n\n//node.warn(\"Name \" + device);\n\nlet device_state = device + \"state\";\ndevice_state = context.get('state') || 0;     //  Set to 0 if not assigned.\n\n//node.warn(\"State \" + device_state);\n\ndevice_state = (device_state + 1) % 2;              //  Toggle between 0 and 1\ncontext.set('state',device_state);\n\nmsg = {\"payload\":device,\"state\":device_state};\nreturn msg;","outputs":1,"noerr":0,"x":560,"y":740,"wires":[["e93c21ff.41e2b8"]]},{"id":"e93c21ff.41e2b8","type":"debug","z":"cd24bca6.19d15","name":"C","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":780,"y":740,"wires":[]},{"id":"fa655930.c7819","type":"inject","z":"cd24bca6.19d15","name":"remote 1","topic":"","payload":"R1","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":100,"y":840,"wires":[["6ce037ec.d379b8"]]},{"id":"cafdce.ab2c9a3","type":"inject","z":"cd24bca6.19d15","name":"remote 2","topic":"","payload":"R2","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":100,"y":920,"wires":[["27f0c7fa.4df47"]]},{"id":"6ce037ec.d379b8","type":"function","z":"cd24bca6.19d15","name":"map remote to device","func":"msg.payload = {\n    remote: \"R1\",\n    device: \"L1\",\n};\n\nreturn msg;","outputs":1,"noerr":0,"x":320,"y":840,"wires":[["439334aa.9bf1cc"]]},{"id":"439334aa.9bf1cc","type":"function","z":"cd24bca6.19d15","name":"This node is the subflow","func":"let remote = msg.payload.remote;\nlet device = msg.payload.device;   //  should not be needed/used.\n\n//node.warn(\"Name \" + device);\n\nlet device_state = device + \"state\";\ndevice_state = context.get('state') || 0;     //  Set to 0 if not assigned.\n\n//node.warn(\"State \" + device_state);\n\ndevice_state = (device_state + 1) % 2;              //  Toggle between 0 and 1\ncontext.set('state',device_state);\n\nmsg = {\"payload\":device,\"state\":device_state};\nreturn msg;","outputs":1,"noerr":0,"x":560,"y":880,"wires":[["1033c8e9.12b9af"]]},{"id":"1033c8e9.12b9af","type":"debug","z":"cd24bca6.19d15","name":"C","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":780,"y":880,"wires":[]},{"id":"27f0c7fa.4df47","type":"function","z":"cd24bca6.19d15","name":"map remote to device","func":"msg.payload = {\n    remote: \"R2\",\n    device: \"L2\",\n};\n\nreturn msg;","outputs":1,"noerr":0,"x":320,"y":920,"wires":[["439334aa.9bf1cc"]]},{"id":"ed407d42.2e145","type":"comment","z":"cd24bca6.19d15","name":"One","info":"","x":430,"y":690,"wires":[]},{"id":"b6d35568.0ead18","type":"comment","z":"cd24bca6.19d15","name":"Two","info":"","x":430,"y":800,"wires":[]}]

Now, before you say NO! (wink) from looking at the flow/s please indulge me in reading this.

Yes, I am not fully understanding what you mean. That's just how it is. Neither of us are to blame.

Also please don't be put off by how few nodes I use.
I too am guilty of over complicating things which don't need to be.

Flow ONE:
Press the remote1 button once, see the output.
Press it again. See how the output has changed.
Press it a third time and it will go back to the first output.
(Nominal press it again if you want.)

Now press remote2 button and see how it also changes with subsequent presses.

Both outputs are independent of each other. You can have either in either state and the other one has no effect on it.

Flow TWO:
This is just an alternative way of doing it.
The function node you used to assign the remote to the device isn't really needed.
The flow (upstream/before) there can construct the message.
Sure it may mean another node (as shown) for each input, but all in all, I don't see much advantage in having a dedicated node to assign the device to the remote because I am sure there would be space in the code to add the line/s needed anyway.

I know in the flow/s (this node is the subflow) I say:

let remote = msg.payload.remote;
let device = msg.payload.device; // should not be needed/used.

And I then go and use the device name, it is only to honour how you wrote the code.
There is an advantage (I now see) that using the device name rather than the remote, it allows more than one remote to control one device.

But as I don't know all the things at your end, I wrote it as I did.

So you maybe could get rid of the remote part.

Up to you.

Hope that helps.

Just to add:
Yes, the outputs are not exactly what you posted.

The formatting of the output message is relativily easy to do. Just if it is doing what you want.

Thanks,
First of all, sorry for the time you spend on this. Unfortunately, I was not exhaustive enough.
Indeed, multiple remotes may command one light. Your idea of storing the state is good, but some of the remotes are not connected to node-red. That means the state of one device / light may change without node-red being notified. The node that I use in the subflow is meant to get the state of the device thanks to an api call to home assistant made through a specific node. In my example, I just generated random data instead of static one to show you that you cannot rely on the output of this node. It was not clear, I am sorry for that.

It seems that what I want is not (yet) possible. I want to provide a subflow with a different input type that the ones available. Currently, the inputs of a subflow are static i.e. you can provide a value, but not an expression. I have a programmer background, and I wanted to use sublow like a function. For instance (the add function would be a subflow in a read life use):
Example 1 (what I try to do)

let msg = {
    "payload": {
        "a": 1,
        "b": 2,
        "c": 3
    }
};
function add(arg1, arg2) {
    return arg1 + arg2;
}
msg.result1 = add(msg.payload.a, msg.payload.b);
msg.result1 = add(msg.payload.a, msg.payload.c);

But it seems that subflow can only be with static or with predefined input location like this:
Example 2 (limitation of a subflow)

let msg = {
    "payload": {
        "a": 1,
        "b": 2,
        "c": 3
    }
};
function add(msg) {
    msg.result = msg.payload.a, msg.payload.b;
}
add(msg);

So I have to add a node in so that the msg is properly formated.
Example 3 (do Example 1 with limitation of a subflow)

let msg = {
    "payload": {
        "a": 1,
        "b": 2,
        "c": 3
    }
};
function add(msg) {
    msg.result = msg.payload.a, msg.payload.b;
}
add(msg); // call to the subflow
// put the result in result1 and set the payload for the next call (the tree next lines are one node)
msg.backup_payload = msg.payload;
msg.result1 = msg.result;
msg.payload.b = msg.payload.c;
add(msg); // call to the subflow with the next set of argument (a and c)
// put the result in result2, restore payload (the tree next lines are one node)
msg.result2 = msg.result
msg.payload = msg.backup_payload;
delete msg.backup_payload;

PS:

In javascript you can remove the " around the key of a property. It is equivalent to
msg.payload = {"entity_id": msg.subflowParameter};

This topic reads like there is a quantum physics book being written.
Instead of creating overly complicated functions, did you try to draw the actual flow from a to z (on paper) ?

1 Like

Yes. It only takes two extra nodes per subflow call: one for setting the payload correctly for the subflow, one for moving the result of the sublow graph to the location I want. By extra, I mean that I would prefer to get ride of them if possible.

That is not a big deal, I just wanted to know if it was possible / easy to do.

Not a problem.

Ok, so you have an API (external thingy) that allows "external" control of the switches also.

So, this "thing" reads the state of the switches/devices and can send this to NR.

It also allows external control of the switches/devices outside NR's control/purview (scope).

New attack:

Node-red ONLY sends commands to turn switches on/off. It does NOT store states of switches.

Any time a switch/device changes I would guess that the API sends a message.

If so, read the message from the API and use that to display the states.

Not that complicated.

Ok, reading a bit more of what you said, you may need to send a pulse to the node that reads the home assistant state....
(from)

Possible?

If you want, post the part of the flow which has the API in it and I will see what I can do for you with it.