Switch node with loops until 1 of 2 conditions is met

I have a dispensing system that uses a vibratory scale to "shake off" the material over a set period of time. The key is to know the initial weight of the scale in order to calculate how many kg were shaken off.

I am exploring using only Node-RED to accomplish this for a future system that will be built. My first thought is to use one switch node with multiple conditions (kg dispensed = 12 kg? and countdown timer = 0?) and tested in the order presented. If the answer is FALSE (no) to both conditions, would I just loop back the wire to the "Turn on vibratory unit.." node (which would be a command to send the ON signal to the unit)?

You might want to add a short delay before connecting to the 'Turn on vibrator unit...' node

I think maybe you are thinking about this in a non-node-red way. Thinking about it in terms of node-red messages:
Will there be a regular message coming in showing the current weight?
Is there going to be dashboard button or similar to start it going?
How will the max time be set? Fixed or dashboard or something else?

[Edit] Also where does the amount to be dispensed come from?

Yes, I am indeed thinking about the routine in a non-NR way, so your questions are very valid.

  1. Yes, a weigh scale hooked up via a Serial Node will transmit the current weight. It's not ultra critical, so we can transmit a reading every second or so.
  2. Yes, I would build a simple dashboard to start the flow, and also give the user the ability to set the countdown time and/or target weight to be dispensed.
  3. Time would be set by the dashboard.

The flow might look something like this

The Start button starts it and the trigger will stop it a pre-determined time later (unless the trigger is reset)

The amount to dispense, the regular weight values from the sensor and the current state are joined by Join node (using key/value method) and passed to a function node which tracks what is going on. It will see a state change when the vibrator is started and can capture the current weight. Then at each new weight value it can decide if enough has gone and if so stop the vibrator and the same message will reset the trigger.

Thanks for the insight. I like the approach of using the Join node to take the weight from the scale and the state. What are the two Switch nodes (weight and state) in your screenshot supposed to do?

They are Change nodes (the icons are very similar) setting the topic to state and weight for the Join node. Depending on the incoming data the weight one might not be necessary as it may already have a topic.

I started coding up the function

/* Determines whether vibration is complete and sends "Off" when it is
 * msg.payload.state contains vibration state "On" or "Off"
 * msg.payload.weight contains current weight measurement
 * msg.payload.dispense contains amount to dispense
 * 
*/
let data = context.get("data") || {state: "Off", initial: msg.payload.weight}
// if not running just keep track of current weight
if (msg.payload.state === "Off") {
    data.initial = msg.payload.weight
    msg = null      // so nothing will be sent at the end
} else {
    // have we weighed out enough yet?

}


context.set("data", data)
return msg;

Thanks for your help thus far. I am pretty sure I can set the topic of the current weight measurement (here is how I am parsing the data from the weight scale's serial output to a numerical value).

Before we get to the function, I am trying to understand how the Join node would incorporate the topics. I think something like this?

and also trying to understand the Trigger node. I think something like this? (btw, 5.5 represents the amount of volts that would be sent to the vibrator).

You need And Every Subsequent set in the Join and the trigger should send on, then wait, then off after the timeout.

[{"id":"eea0e409542a2f79","type":"inject","z":"84405ff5.25fa6","name":"Start","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Start","payload":"true","payloadType":"bool","x":110,"y":1140,"wires":[["321ec703536672bd"]]},{"id":"b01a51f9a3c09762","type":"inject","z":"84405ff5.25fa6","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"10","payloadType":"num","x":130,"y":1320,"wires":[["d706a75b4a764a88"]]},{"id":"24d28adead012826","type":"inject","z":"84405ff5.25fa6","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"8","payloadType":"num","x":130,"y":1360,"wires":[["d706a75b4a764a88"]]},{"id":"1bae20dcab185f45","type":"inject","z":"84405ff5.25fa6","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"6","payloadType":"num","x":130,"y":1400,"wires":[["d706a75b4a764a88"]]},{"id":"2a4c68178addd08e","type":"inject","z":"84405ff5.25fa6","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"4","payloadType":"num","x":130,"y":1440,"wires":[["d706a75b4a764a88"]]},{"id":"d706a75b4a764a88","type":"change","z":"84405ff5.25fa6","name":"topic: weight","rules":[{"t":"set","p":"topic","pt":"msg","to":"weight","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":360,"y":1280,"wires":[["99e54f71b19f8c09"]]},{"id":"321ec703536672bd","type":"trigger","z":"84405ff5.25fa6","name":"","op1":"On","op2":" Off","op1type":"str","op2type":"str","duration":"10","extend":false,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":330,"y":1140,"wires":[["ae3a1c8ff5fc01bd","f4441d8c2bea8055"]]},{"id":"f4441d8c2bea8055","type":"debug","z":"84405ff5.25fa6","name":"Vibrator","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":760,"y":1140,"wires":[]},{"id":"1e642c702112b226","type":"inject","z":"84405ff5.25fa6","name":"Amount to dispense","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"dispense","payload":"5","payloadType":"num","x":150,"y":1220,"wires":[["99e54f71b19f8c09"]]},{"id":"99e54f71b19f8c09","type":"join","z":"84405ff5.25fa6","name":"","mode":"custom","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":true,"timeout":"","count":"3","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":530,"y":1280,"wires":[["0f0623d75fec407f"]]},{"id":"ae3a1c8ff5fc01bd","type":"change","z":"84405ff5.25fa6","name":"topic: state","rules":[{"t":"set","p":"topic","pt":"msg","to":"state","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":450,"y":1200,"wires":[["99e54f71b19f8c09"]]},{"id":"0f0623d75fec407f","type":"function","z":"84405ff5.25fa6","name":"Done?","func":"/* Determines whether vibration is complete and sends \"Off\" when it is\n * msg.payload.state contains vibration state \"On\" or \"Off\"\n * msg.payload.weight contains current weight measurement\n * msg.payload.dispense contains amount to dispense\n * \n*/\nlet data = context.get(\"data\") || {state: \"Off\", initial: msg.payload.weight}\n// if not running just keep track of current weight\nif (msg.payload.state === \"Off\") {\n    data.initial = msg.payload.weight\n    msg = null      // so nothing will be sent at the end\n} else {\n\n}\n\n\ncontext.set(\"data\", data)\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":670,"y":1280,"wires":[["f4441d8c2bea8055","e05062a3f5971c6a"]]},{"id":"e05062a3f5971c6a","type":"link out","z":"84405ff5.25fa6","name":"Reset trigger","links":["408342ec880d8c52"],"x":715,"y":1340,"wires":[]},{"id":"408342ec880d8c52","type":"link in","z":"84405ff5.25fa6","name":"reset","links":["e05062a3f5971c6a"],"x":255,"y":1080,"wires":[["321ec703536672bd"]]},{"id":"ef1ac3a56c0eb68f","type":"comment","z":"84405ff5.25fa6","name":"Reset trigger","info":"","x":310,"y":1040,"wires":[]},{"id":"1229a29721631129","type":"comment","z":"84405ff5.25fa6","name":"Values from sensor","info":"","x":130,"y":1280,"wires":[]}]

Except that the On and Off presumably need to be 5.5 and 0.

Thank you for your help. I expanded the weight Values from Sensor to include 2 and 0 just to play around with it. I also set the Amount to Dispense at 6. If the initial weight is 10, the vibrator turns on and runs for 10 sec as expected, then keeps repeating. So far, so good. However, as soon as the sensor (scale) sends a weight of 4, I would expect the vibrator to stop immediately since we would have dispensed exactly 6. However, it appears to stay in the "On" state for about 5 seconds before switching to "Off" (from 6:56:07 to 6:56:12). Am I seeing this correctly?

Have you completed the Done function? I had only started it, as I said. It is not functional as I posted.

I had not completed the function node, so went ahead and gave it a try. Still some work to do, but I think I have the IF ELSE statements working. Just one question...Did you intentionally place a space before the string " Off" in the Trigger node? You can see in the debug pane that we see "Off" and " Off". When I remove the space, the flow makes one pass and then stays on Off.

[{"id":"eea0e409542a2f79","type":"inject","z":"941484eec95d7ef4","name":"Start","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Start","payload":"true","payloadType":"bool","x":90,"y":220,"wires":[["321ec703536672bd"]]},{"id":"b01a51f9a3c09762","type":"inject","z":"941484eec95d7ef4","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"16","payloadType":"num","x":110,"y":400,"wires":[["d706a75b4a764a88"]]},{"id":"24d28adead012826","type":"inject","z":"941484eec95d7ef4","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"14","payloadType":"num","x":110,"y":440,"wires":[["d706a75b4a764a88"]]},{"id":"1bae20dcab185f45","type":"inject","z":"941484eec95d7ef4","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"13","payloadType":"num","x":110,"y":480,"wires":[["d706a75b4a764a88"]]},{"id":"2a4c68178addd08e","type":"inject","z":"941484eec95d7ef4","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"12","payloadType":"num","x":110,"y":520,"wires":[["d706a75b4a764a88"]]},{"id":"d706a75b4a764a88","type":"change","z":"941484eec95d7ef4","name":"topic: weight","rules":[{"t":"set","p":"topic","pt":"msg","to":"weight","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":340,"y":360,"wires":[["99e54f71b19f8c09"]]},{"id":"321ec703536672bd","type":"trigger","z":"941484eec95d7ef4","name":"","op1":"On","op2":" Off","op1type":"str","op2type":"str","duration":"10","extend":false,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":310,"y":220,"wires":[["ae3a1c8ff5fc01bd","f4441d8c2bea8055"]]},{"id":"f4441d8c2bea8055","type":"debug","z":"941484eec95d7ef4","name":"Vibrator","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":740,"y":220,"wires":[]},{"id":"1e642c702112b226","type":"inject","z":"941484eec95d7ef4","name":"Amount to dispense","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"dispense","payload":"4","payloadType":"num","x":130,"y":280,"wires":[["99e54f71b19f8c09"]]},{"id":"99e54f71b19f8c09","type":"join","z":"941484eec95d7ef4","name":"","mode":"custom","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":true,"timeout":"","count":"3","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":510,"y":360,"wires":[["0f0623d75fec407f"]]},{"id":"ae3a1c8ff5fc01bd","type":"change","z":"941484eec95d7ef4","name":"topic: state","rules":[{"t":"set","p":"topic","pt":"msg","to":"state","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":430,"y":280,"wires":[["99e54f71b19f8c09"]]},{"id":"0f0623d75fec407f","type":"function","z":"941484eec95d7ef4","name":"Done?","func":"/* Determines whether vibration is complete and sends \"Off\" when it is\n * msg.payload.state contains vibration state \"On\" or \"Off\"\n * msg.payload.weight contains current weight measurement\n * msg.payload.dispense contains amount to dispense\n * \n*/\nlet data = context.get(\"data\") || {state: \"Off\", initial: msg.payload.weight}\n// if not running just keep track of current weight\nif (msg.payload.state === \"Off\") {\n    data.initial = msg.payload.weight\n    msg = null      // so nothing will be sent at the end\n} else if (data.initial - msg.payload.dispense < msg.payload.weight)\n    {msg.payload.state = \"On\"}\nelse {msg.payload.state = \"Off\"}\n\n\n\ncontext.set(\"data\", data)\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":650,"y":360,"wires":[["f4441d8c2bea8055","e05062a3f5971c6a"]]},{"id":"e05062a3f5971c6a","type":"link out","z":"941484eec95d7ef4","name":"Reset trigger","links":["408342ec880d8c52"],"x":695,"y":420,"wires":[]},{"id":"408342ec880d8c52","type":"link in","z":"941484eec95d7ef4","name":"reset","links":["e05062a3f5971c6a"],"x":235,"y":160,"wires":[["321ec703536672bd"]]},{"id":"ef1ac3a56c0eb68f","type":"comment","z":"941484eec95d7ef4","name":"Reset trigger","info":"","x":290,"y":120,"wires":[]},{"id":"1229a29721631129","type":"comment","z":"941484eec95d7ef4","name":"Values from sensor","info":"","x":110,"y":360,"wires":[]},{"id":"3e96e8d6e6bdb086","type":"inject","z":"941484eec95d7ef4","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"10","payloadType":"num","x":110,"y":560,"wires":[["d706a75b4a764a88"]]},{"id":"b8059bbaba8d0ee5","type":"inject","z":"941484eec95d7ef4","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"9","payloadType":"num","x":109.00867462158203,"y":597.001708984375,"wires":[["d706a75b4a764a88"]]}]

There looks like a space before the 'off' in the trigger node.

No, in addition the node should have Reset the trigger if msg.payload is Off. That is so that the trigger is reset if the Done node sends an Off message.

} else if (data.initial - msg.payload.dispense < msg.payload.weight)
    {msg.payload.state = "On"}
else {msg.payload.state = "Off"}

That doesn't work does it? Does it turn the vibrator off when it should? I would have thought it should be

} else if (data.initial - msg.payload.dispense < msg.payload.weight) {
  msg = null        // don't send a message as it is already on, or has been turned off by the trigger
} else {
  msg.payload = "Off"   // enogh lost so stop vibrator
}

Also there needs to be an extra wire from the Link In node to topic:state, so that the Off message gets back to the Join node and into the message.

I have realised that it isn't necessary to save the state in the context so that can be simplified a bit.

Finally, the way you have done the test seems odd to me. To me it seems more logical to do, in words, 'if current weight is greater than initial weight - weight to dispense then vibrator on, else off` rather than the the way you have it. However if your brain is happier the way it is then that is fine.

This seems to work

[{"id":"1600c410e9ef7425","type":"inject","z":"84405ff5.25fa6","name":"Start","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Start","payload":"true","payloadType":"bool","x":170,"y":1580,"wires":[["9377b8a60d5d56b2"]]},{"id":"ca7388684c1ed2b7","type":"inject","z":"84405ff5.25fa6","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"16","payloadType":"num","x":190,"y":1760,"wires":[["d11829039b7b76f6"]]},{"id":"31e3f6d570e61a23","type":"inject","z":"84405ff5.25fa6","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"14","payloadType":"num","x":190,"y":1800,"wires":[["d11829039b7b76f6"]]},{"id":"da6bf3ebe50437fc","type":"inject","z":"84405ff5.25fa6","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"13","payloadType":"num","x":190,"y":1840,"wires":[["d11829039b7b76f6"]]},{"id":"627ef94e971e9a87","type":"inject","z":"84405ff5.25fa6","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"12","payloadType":"num","x":190,"y":1880,"wires":[["d11829039b7b76f6"]]},{"id":"d11829039b7b76f6","type":"change","z":"84405ff5.25fa6","name":"topic: weight","rules":[{"t":"set","p":"topic","pt":"msg","to":"weight","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":420,"y":1720,"wires":[["94dc8259f3faa4f3"]]},{"id":"9377b8a60d5d56b2","type":"trigger","z":"84405ff5.25fa6","name":"","op1":"On","op2":"Off","op1type":"str","op2type":"str","duration":"10","extend":false,"overrideDelay":false,"units":"s","reset":"Off","bytopic":"all","topic":"topic","outputs":1,"x":390,"y":1580,"wires":[["5f352a142459f3bb","7b1d0270c339ebb1"]]},{"id":"7b1d0270c339ebb1","type":"debug","z":"84405ff5.25fa6","name":"Vibrator","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":820,"y":1580,"wires":[]},{"id":"783c58f6ad3c2c18","type":"inject","z":"84405ff5.25fa6","name":"Amount to dispense","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"dispense","payload":"4","payloadType":"num","x":210,"y":1640,"wires":[["94dc8259f3faa4f3"]]},{"id":"94dc8259f3faa4f3","type":"join","z":"84405ff5.25fa6","name":"","mode":"custom","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":true,"timeout":"","count":"3","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":590,"y":1720,"wires":[["ac0e1c7a83d98968","27a01dc09a72f0ec"]]},{"id":"5f352a142459f3bb","type":"change","z":"84405ff5.25fa6","name":"topic: state","rules":[{"t":"set","p":"topic","pt":"msg","to":"state","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":510,"y":1640,"wires":[["94dc8259f3faa4f3"]]},{"id":"ac0e1c7a83d98968","type":"function","z":"84405ff5.25fa6","name":"Done?","func":"/* Determines whether vibration is complete and sends \"Off\" when it is\n * msg.payload.state contains vibration state \"On\" or \"Off\"\n * msg.payload.weight contains current weight measurement\n * msg.payload.dispense contains amount to dispense\n * \n*/\nlet initial = context.get(\"initial\") || msg.payload.weight  // pick up starting weight from saved value\n// if not running just keep track of current weight\nif (msg.payload.state === \"Off\") {\n    initial = msg.payload.weight\n    msg = null      // so nothing will be sent at the end\n} else if (initial - msg.payload.dispense < msg.payload.weight) {\n    msg = null      // don't send anything as it is already on, or has been turned off by the Trigger node\n} else \n{\n    msg.payload = \"Off\"\n}\n\ncontext.set(\"initial\", initial)\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":730,"y":1720,"wires":[["7b1d0270c339ebb1","9542e62b072701a9"]]},{"id":"9542e62b072701a9","type":"link out","z":"84405ff5.25fa6","name":"Reset trigger","links":["04cacefa8a368a73"],"x":775,"y":1780,"wires":[]},{"id":"04cacefa8a368a73","type":"link in","z":"84405ff5.25fa6","name":"reset","links":["9542e62b072701a9"],"x":315,"y":1520,"wires":[["9377b8a60d5d56b2","5f352a142459f3bb"]]},{"id":"a497014f24d79d22","type":"comment","z":"84405ff5.25fa6","name":"Reset trigger","info":"","x":370,"y":1480,"wires":[]},{"id":"8cdfac5c617f1fe0","type":"comment","z":"84405ff5.25fa6","name":"Values from sensor","info":"","x":190,"y":1720,"wires":[]},{"id":"a9f4b546eeee6b88","type":"inject","z":"84405ff5.25fa6","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"10","payloadType":"num","x":190,"y":1920,"wires":[["d11829039b7b76f6"]]},{"id":"5e2ece19ca3c21dc","type":"inject","z":"84405ff5.25fa6","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"9","payloadType":"num","x":189.00867462158203,"y":1957.001708984375,"wires":[["d11829039b7b76f6"]]},{"id":"27a01dc09a72f0ec","type":"debug","z":"84405ff5.25fa6","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":660,"y":1840,"wires":[]}]

The vibrator stops when the weight on the scale reaches the condition in the function, but I was expecting the flow to start again once the 10 seconds elasped. Like you said, the Trigger now has the Reset the trigger if msg.payload equals Off. Maybe this is happening because of the extra wire from the Link In node to topic:state? I will keep playing around with this.

Also, my logical test is probably backwards as you indicated, but it still works ; )

Is that what you want to happen? I didn't think think that was the spec. I thought it was not supposed to start again till the start button was pressed. I think you had better describe your requirement again in more detail.

Yes, the idea is that the same loop of n seconds keeps repeating and repeating until stopped. But I got around it by setting the Start node to repeat every 10.01 seconds, so when the loop with the Trigger node ends at 10 seconds, the Start node injects and starts the process over again. With that modification, the flow works very nicely.

Just a few dashboard-related questions. I would like to display in a Dashboard these 3 items:

  1. a 10-second countdown timer that coincides with 10 seconds in the Trigger node
  2. the weight dispensed (ideally updated once per second), i.e. initial - msg.payload.dispense? Do I need to assign a variable name to this? Eventually I will probably construct a chart that uses this value and plots it over time, so I believe that we can just add a var = initial - msg.payload.dispense line at the end of the Function node.
  3. the context value for the variable initial (not totally necessary, but makes it easier to see how much weight was on the scale at the start of the 10-second routine)

What do you want to happen if the dispensing has not completed at ten seconds?

The program would try again to dispense the required amount in n seconds. In "real life" that means that the mass of material on the vibratory was stuck or jammed or whatever, but a second or third attempt will complete the job.

Also, the program would finish when all the parts are off the scale, msg.payload.weight = 0.

Starting again with a new starting weight or continuing the previous measurement till it gets to the right weight?

Can you explain the purpose of the whole thing, that might let me get a better idea of what is going on.
Another thing that concerns me is that if the weighing took, say, 9.9 seconds, then the next one is going to start immediately with virtually no gap. Is that ok?