Gate node using Blockly

One thing I've always thought missing in core Node-RED is a gate node.

A block that can be switched off or on to prevent or pass messages, controlled simply in the editor, without having to re-deploy.

I tried a few solutions previously but when Blocky arrived in Node-RED, I thought - this would make a good project to try out - and here is result.

Cheerlights MQTT and timestamp nodes are just for testing.

If the Gate node receives a true message on topic gate, its sets itself to pass messages, false sets it to block messages.

gate:toggle just switches the state from true->false or false ->true

The gate:init just simply sends an "init" string to trigger the node into displaying its status (Needed when you re-deploy to regenerate the status text - this won't be needed if/when Node-RED is enhanced to provide a system deploy start message)

Any messages on topic gate are not passed through.

Minimally, you need just a gate:toggle inject node but this is my normally used configuration

image

[{"id":"a80a47ce.814a98","type":"inject","z":"ed0f5786.2ba6d8","name":"","topic":"gate","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":420,"y":240,"wires":[["c57c2c6e.d7efa"]]},{"id":"bbcbdea4.7395c","type":"inject","z":"ed0f5786.2ba6d8","name":"","topic":"gate","payload":"false","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":420,"y":280,"wires":[["c57c2c6e.d7efa"]]},{"id":"94c4d029.581f1","type":"inject","z":"ed0f5786.2ba6d8","name":"","topic":"gate","payload":"toggle","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":420,"y":200,"wires":[["c57c2c6e.d7efa"]]},{"id":"1b21a7e9.ed9108","type":"inject","z":"ed0f5786.2ba6d8","name":"","topic":"gate","payload":"init","payloadType":"str","repeat":"","crontab":"","once":true,"onceDelay":0.1,"x":420,"y":160,"wires":[["c57c2c6e.d7efa"]]},{"id":"c57c2c6e.d7efa","type":"Blockly","z":"ed0f5786.2ba6d8","language":"en","func":"var gateMsg;\n\n/**\n * Describe this function...\n */\nfunction setStatus() {\n  if (!(context.get('gate'))) {\n    node.status({fill:\"red\", shape:\"dot\", text:'closed'});\n  } else {\n    node.status({fill:\"green\", shape:\"dot\", text:'open'});\n  }\n}\n\n\nif ((context.get('gate')) != false) {\n  context.set('gate', true);\n}\nif ((msg['topic']) == 'gate') {\n  gateMsg = (msg['payload']);\n  if (gateMsg == true) {\n    context.set('gate', true);\n  } else if (gateMsg == false) {\n    context.set('gate', false);\n  } else if (gateMsg == 'toggle') {\n    context.set('gate', (!(context.get('gate'))));\n  }\n  setStatus();\n} else {\n  if (context.get('gate')) {\n    return msg;\n  } else {\n    setStatus();\n  }\n}\n","workspaceXml":"<xml xmlns=\"http://www.w3.org/1999/xhtml\"><variables><variable type=\"\" id=\"d`=BUYN]|[8b:+QnCvIG\">gateMsg</variable></variables><block type=\"controls_if\" id=\"32ef6ggKH_Uy4XVr)t*H\" x=\"-187\" y=\"-188\"><value name=\"IF0\"><block type=\"logic_compare\" id=\"h:L;a-RTj5*WigvnV-[Y\"><field name=\"OP\">NEQ</field><value name=\"A\"><block type=\"node_object_get\" id=\"18/sklXD`tpFg7QX;*Ve\"><value name=\"object\"><shadow type=\"node_msg\" id=\"~Hd~wjelxUU}%v21r0Qu\"></shadow><block type=\"node_context_memory\" id=\"iGV|Xlw_^:`Tf8vQ3+E*\"></block></value><value name=\"field_name\"><shadow type=\"text\" id=\"gDB@4aU?`X@P{(UFL(:R\"><field name=\"TEXT\">gate</field></shadow></value></block></value><value name=\"B\"><block type=\"logic_boolean\" id=\"d1_Qw[79U.(S{~He%*|#\"><field name=\"BOOL\">FALSE</field></block></value></block></value><statement name=\"DO0\"><block type=\"node_object_set\" id=\"{iGS)N)j0I.p6n]?EH{4\" inline=\"false\"><value name=\"object_field\"><shadow type=\"node_msg\" id=\"5}39@%9{W{rR9xY(iV0V\"></shadow><block type=\"node_context_memory\" id=\"-l0cj0`~k$HnFTESKV8^\"></block></value><value name=\"field_name\"><shadow type=\"text\" id=\"yTSAkB!(^QJGRPW[dUXn\"><field name=\"TEXT\">gate</field></shadow></value><value name=\"value_field\"><shadow type=\"logic_null\" id=\",mjKFv0Dg^RS4Cy?XC6i\"></shadow><block type=\"logic_boolean\" id=\"!(S$f7!}#.a6vQRNN0DQ\"><field name=\"BOOL\">TRUE</field></block></value></block></statement><next><block type=\"controls_if\" id=\"~.:itwduZrf}*w;n$t*j\"><mutation else=\"1\"></mutation><value name=\"IF0\"><block type=\"logic_compare\" id=\"Cj]G08l?x*%LwLmtY!(A\"><field name=\"OP\">EQ</field><value name=\"A\"><block type=\"node_object_get\" id=\"7~/[1#LEPey[JA+V7yzS\"><value name=\"object\"><shadow type=\"node_msg\" id=\"#1_AsUcJPPBeoeG_GWG2\"></shadow></value><value name=\"field_name\"><shadow type=\"text\" id=\"!;pa;Awt6D`3}sf8dts$\"><field name=\"TEXT\">topic</field></shadow></value></block></value><value name=\"B\"><block type=\"text\" id=\"Dm-Uq,Num{~zgpw,TJUP\"><field name=\"TEXT\">gate</field></block></value></block></value><statement name=\"DO0\"><block type=\"variables_set\" id=\"2V%]c!T?_v{u8W_xl;9C\"><field name=\"VAR\" id=\"d`=BUYN]|[8b:+QnCvIG\" variabletype=\"\">gateMsg</field><value name=\"VALUE\"><block type=\"node_object_get\" id=\"SIC_2OBlHzAT=+}Q#|Um\"><value name=\"object\"><shadow type=\"node_msg\" id=\"AD)GyD|pWT,|_c+`4^VC\"></shadow></value><value name=\"field_name\"><shadow type=\"text\" id=\"]D~MMG!kBUXy70n+y1yv\"><field name=\"TEXT\">payload</field></shadow></value></block></value><next><block type=\"controls_if\" id=\";n,9rOKYpJ2{N#5}gm7}\"><mutation elseif=\"2\"></mutation><value name=\"IF0\"><block type=\"logic_compare\" id=\"RwGh;o}ui:a|bU.@kLDv\"><field name=\"OP\">EQ</field><value name=\"A\"><block type=\"variables_get\" id=\"MYUJV@Lrv]^B7%081]h?\"><field name=\"VAR\" id=\"d`=BUYN]|[8b:+QnCvIG\" variabletype=\"\">gateMsg</field></block></value><value name=\"B\"><block type=\"logic_boolean\" id=\"Y@gBFVD/A^{%us[ioT)w\"><field name=\"BOOL\">TRUE</field></block></value></block></value><statement name=\"DO0\"><block type=\"node_object_set\" id=\"kWe7w$xw).]09[lNrXHo\"><value name=\"object_field\"><shadow type=\"node_msg\" id=\"5}39@%9{W{rR9xY(iV0V\"></shadow><block type=\"node_context_memory\" id=\"5M#~GLEo)3t6GhP8:L]_\"></block></value><value name=\"field_name\"><shadow type=\"text\" id=\"WQE.)c`vN#x]E|R(*gk6\"><field name=\"TEXT\">gate</field></shadow></value><value name=\"value_field\"><shadow type=\"logic_null\" id=\",mjKFv0Dg^RS4Cy?XC6i\"></shadow><block type=\"logic_boolean\" id=\"cXS9gB~);.R^}~@mWG9W\"><field name=\"BOOL\">TRUE</field></block></value></block></statement><value name=\"IF1\"><block type=\"logic_compare\" id=\"S.Gf0wI`3NL*U_,_YWa2\"><field name=\"OP\">EQ</field><value name=\"A\"><block type=\"variables_get\" id=\"M4c2sC$`%I?H%7=N+QNZ\"><field name=\"VAR\" id=\"d`=BUYN]|[8b:+QnCvIG\" variabletype=\"\">gateMsg</field></block></value><value name=\"B\"><block type=\"logic_boolean\" id=\"6tYNZlkiu8)ABulMbey(\"><field name=\"BOOL\">FALSE</field></block></value></block></value><statement name=\"DO1\"><block type=\"node_object_set\" id=\"I=b:?92]h3*s8U#7ahR~\"><value name=\"object_field\"><shadow type=\"node_msg\" id=\"5}39@%9{W{rR9xY(iV0V\"></shadow><block type=\"node_context_memory\" id=\".bD:_3An(+heBObs^kvw\"></block></value><value name=\"field_name\"><shadow type=\"text\" id=\"K4lQYjhe#5:|!aF#xk}c\"><field name=\"TEXT\">gate</field></shadow></value><value name=\"value_field\"><shadow type=\"logic_null\" id=\",mjKFv0Dg^RS4Cy?XC6i\"></shadow><block type=\"logic_boolean\" id=\"3UyGiv9b1#wIH_R,MUQ5\"><field name=\"BOOL\">FALSE</field></block></value></block></statement><value name=\"IF2\"><block type=\"logic_compare\" id=\"z]}#wU$HhS~7Tx}(xcb|\"><field name=\"OP\">EQ</field><value name=\"A\"><block type=\"variables_get\" id=\"vLs`dSDZt%X_@O,C}dju\"><field name=\"VAR\" id=\"d`=BUYN]|[8b:+QnCvIG\" variabletype=\"\">gateMsg</field></block></value><value name=\"B\"><block type=\"text\" id=\"6jA}zPbnzdWEW*w3_x1M\"><field name=\"TEXT\">toggle</field></block></value></block></value><statement name=\"DO2\"><block type=\"node_object_set\" id=\"!SAaPGR=NM^zEr`p:E+[\" inline=\"false\"><value name=\"object_field\"><shadow type=\"node_msg\" id=\"5}39@%9{W{rR9xY(iV0V\"></shadow><block type=\"node_context_memory\" id=\"v,Y^I6%=Y/bVfZrs0Kaf\"></block></value><value name=\"field_name\"><shadow type=\"text\" id=\"?YDPewBU84,%ZWrk|pFU\"><field name=\"TEXT\">gate</field></shadow></value><value name=\"value_field\"><shadow type=\"logic_null\" id=\",mjKFv0Dg^RS4Cy?XC6i\"></shadow><block type=\"logic_negate\" id=\"OoJEu+)0QV$T7[=W$Uo9\"><value name=\"BOOL\"><block type=\"node_object_get\" id=\"|M^$@Rs3oJ]}IfZMx!o8\"><value name=\"object\"><shadow type=\"node_msg\" id=\"~Hd~wjelxUU}%v21r0Qu\"></shadow><block type=\"node_context_memory\" id=\")$m/Lsc9`F34E=!qmMCn\"></block></value><value name=\"field_name\"><shadow type=\"text\" id=\"$J0,]XZbR.8Ao#oX640h\"><field name=\"TEXT\">gate</field></shadow></value></block></value></block></value></block></statement><next><block type=\"procedures_callnoreturn\" id=\"u}lB4+Z7LbP(oibz$5?Z\"><mutation name=\"setStatus\"></mutation></block></next></block></next></block></statement><statement name=\"ELSE\"><block type=\"controls_if\" id=\"s{![6_[TJAu8o%zTPo^W\"><mutation else=\"1\"></mutation><value name=\"IF0\"><block type=\"node_object_get\" id=\",cKR;lB+k6hYi9e-TuVj\"><value name=\"object\"><shadow type=\"node_msg\" id=\"X)d#jye;o4PN@I3X_(u?\"></shadow><block type=\"node_context_memory\" id=\"ZDT?I{L3,GFla?p@ch6y\"></block></value><value name=\"field_name\"><shadow type=\"text\" id=\"?aCsZ(tpX3tf~@0~A?f_\"><field name=\"TEXT\">gate</field></shadow></value></block></value><statement name=\"DO0\"><block type=\"node_return_message\" id=\"Dug-9(Hm8D}{Xz*$-F+$\"><field name=\"OUTPUT_NR\">1</field><value name=\"MESSAGE_INPUT\"><shadow type=\"node_msg\" id=\"P{XZf5BwBz7JJN}%WyEF\"></shadow></value></block></statement><statement name=\"ELSE\"><block type=\"procedures_callnoreturn\" id=\"MG,Le/TTrMyCoE2m!b;g\"><mutation name=\"setStatus\"></mutation></block></statement></block></statement></block></next></block><block type=\"procedures_defnoreturn\" id=\"VW#k7XZ{CFx+VtbYvavB\" x=\"-163\" y=\"662\"><field name=\"NAME\">setStatus</field><comment pinned=\"false\" h=\"80\" w=\"160\">Describe this function...</comment><statement name=\"STACK\"><block type=\"controls_if\" id=\"l4`[$C%ap^pmsp$7tLc|\"><mutation else=\"1\"></mutation><value name=\"IF0\"><block type=\"logic_negate\" id=\"!s$ngmsL];IK-mbU[X0_\"><value name=\"BOOL\"><block type=\"node_object_get\" id=\"zkUDZ6P)hpPWDemRd:[m\"><value name=\"object\"><shadow type=\"node_msg\" id=\"X)d#jye;o4PN@I3X_(u?\"></shadow><block type=\"node_context_memory\" id=\"muYmwwU4}b7cV~L^qa`W\"></block></value><value name=\"field_name\"><shadow type=\"text\" id=\"]It@[mD?Gy)qnP]4h5M!\"><field name=\"TEXT\">gate</field></shadow></value></block></value></block></value><statement name=\"DO0\"><block type=\"node_status\" id=\"g|R#AV4vWOU$ok8CV?,q\"><field name=\"COLOUR\">#ff0000</field><field name=\"SHAPE\">DOT</field><value name=\"TEXT_INPUT\"><block type=\"text\" id=\"[@x,-=Y:s/z{^3sFfvF#\"><field name=\"TEXT\">closed</field></block></value></block></statement><statement name=\"ELSE\"><block type=\"node_status\" id=\"repdr,@:$XxBOCYQm+rG\"><field name=\"COLOUR\">#00ff00</field><field name=\"SHAPE\">DOT</field><value name=\"TEXT_INPUT\"><block type=\"text\" id=\"cRrXdQ|PF~MO=u)jo-{l\"><field name=\"TEXT\">open</field></block></value></block></statement></block></statement></block></xml>","outputs":1,"name":"Gate","x":610,"y":120,"wires":[["9c2a9d9c.c3daa"]],"icon":"node-red/alert.png"}]

PS. Here is the generated JS code if you would like to try out the functionality without installing Blocky contrib-node

var gateMsg;

/**
 * Describe this function...
 */
function setStatus() {
  if (!(context.get('gate'))) {
    node.status({fill:"red", shape:"dot", text:'closed'});
  } else {
    node.status({fill:"green", shape:"dot", text:'open'});
  }
}


if ((context.get('gate')) != false) {
  context.set('gate', true);
}
if ((msg['topic']) == 'gate') {
  gateMsg = (msg['payload']);
  if (gateMsg == true) {
    context.set('gate', true);
  } else if (gateMsg == false) {
    context.set('gate', false);
  } else if (gateMsg == 'toggle') {
    context.set('gate', (!(context.get('gate'))));
  }
  setStatus();
} else {
  if (context.get('gate')) {
    return msg;
  } else {
    setStatus();
  }
1 Like

The reason Node-RED doesn't have an explicit Gate node is because the Switch node can be used to do exactly that. The only difference is you don't feed it the control messages to toggle the gate - you pass them to a Change node to change a flow context value which the Switch node uses to decide whether to pass the message on.

@cymplecy my idea of a gate is slightly different: I don't want to discard messages if the gate is closed but I want to queue them ... then an external event opens the gate, which lets one message pass through and the gate is closed again until the next "open" event ...

I've done it using a function node, here's the code, if anyone's interested ...

// simple gate ...
// starts open
// when a (non-gate-control) message arrives:
//  - msg is transferred if gate is open and then gate is closed no matter what
//  - msg is queued if gate is closed
// gate can only be opened by external specific message
// when gate is opened, it lets through ONE msg and then gate is closed again
// gate control is done using msg.gate: {open: true/false, reset: true/false}
// note: msg.gate.reset will clear the msg queue

// default value when reset
var resetGate = {'open': true, 'msgs': []};

var nodeStatus = function() {
    var gate = context.get('gate') || resetGate ;
    node.status({
        "fill": (gate.open ? "green" : "red"), 
        "shape": "dot", 
        "text": (gate.open ?  "Open" : "Msg: " + gate.msgs.length ) 
    });
}

node.on('close', function() {
    // reset msgs ...
    context.set('gate', resetGate ); 
    nodeStatus();
});

var gate = context.get('gate') || resetGate ;

if (msg.gate) {
// checking explicitly true/false for msg.gate.open 
    if (msg.gate.open===true) gate.open = true;
    else if (msg.gate.open===false) gate.open = false;
    if (msg.gate.reset) gate = resetGate;
} else {
    // add msg to queue
    gate.msgs.push(msg);
}

if (gate.open && gate.msgs.length>0) {
    new_msg = gate.msgs.splice(0,1);
    nodeStatus();
    node.send(new_msg);
    gate.open = false;
}

nodeStatus();

context.set("gate",gate);

return null;

You make a fair point :slight_smile:

I'll work on a new name :slight_smile:

My gate is actually a switch but since the word "switch" is already used up - I couldn't call it that :slight_smile:

I just want my gate/switch to stop messages being sent further down the wires while I'm testing things out - I don't use it as part of my flow algorithm

I'll have a look at yours and see if I can do the same processing in Blockly

It was an exercise in whether Blockly could do such a thing easily - which it can :slight_smile:

Simon

2 Likes

@cymplecy Simon, I usually hate making an issue over terminology, but in this case I think that it is clearer and more accurate to refer to @tilleul's function as a queue (first-in-first-out) rather than a gate. It functions very much like node-red-contrib-simple-message-queue and its predecessor flow simple triggered queue. Generally in electronics and computing, what happens to an input to a gate depends only on the current state of the gate and its input(s). Requiring an additional trigger or storing an extended history of its inputs is more typical of a queue.

Here's an example of that with a per topic example in addition:
https://flows.nodered.org/flow/73ff7f7229d8f06c4b1e379d53c3fc65

and

:slight_smile:

I called mine a gate as in a FET control gate lets the current flow or it doesn't :slight_smile:

But a real-world gate would just hold up traffic/people/sheep when it is closed (effectively buffering them on the road/path/field) and letting them through again when its re-opened

Now I'd call that a turnstile that lets people thru one at a time :slight_smile:

Hi, your switch/gate is right thing i neet, but i need to control the Open/Close state by signal (true/falese) going from switch. and sorry but i am not that good in this shit so, please help :smiley:

1 Like

To you mean
image

or

image

probably bouth, but, just few seconds back, by try and erorr i mande to make it work, so thanks. your core concept works for me.

1 Like

Good to get at least one satisfied customer :slight_smile:

@cymplecy I like the word "turnstile" ... it's exactly like that :smiley:

@cymplecy "Turnstile" is a nice image. An important point about queues is that if traffic/people/sheep arrive faster than the triggers to let them through, the road/shop/pasture will fill up. This doesn't have to happen on the average, just for a sufficiently long time. Of course, some in the queue will just give up and go away ("balk"), and node-red-contrib-simple-message-queue uses a "time to live" property to do this. Even so, a queue node or function probably should explicitly limit the length of the queue to prevent running out of memory.

That was well thought out :slight_smile:
I think I'll call mine a drawbridge - when its open - the cars/people/sheep can cross unimpeded but if its closed (i.e raised) then they will all fall into my moat :slight_smile:

OK, just another 0.02. I think the trick Nick described,

is this

[{"id":"472281d0.760a8","type":"inject","z":"d91ddf95.ab3838","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"x":200,"y":80,"wires":[["4516f163.269a9"]]},{"id":"756b45d2.e4c094","type":"inject","z":"d91ddf95.ab3838","name":"enable","topic":"","payload":"true","payloadType":"bool","repeat":"","crontab":"","once":false,"onceDelay":"","x":196,"y":164,"wires":[["5b28fe49.4a16a8"]]},{"id":"4516f163.269a9","type":"switch","z":"d91ddf95.ab3838","name":"gate","property":"gate","propertyType":"flow","rules":[{"t":"true"}],"checkall":"true","repair":false,"outputs":1,"x":393.5,"y":80,"wires":[["1384709e.7241e7"]]},{"id":"72d6bb56.c0f79c","type":"inject","z":"d91ddf95.ab3838","name":"disable","topic":"","payload":"false","payloadType":"bool","repeat":"","crontab":"","once":false,"x":201,"y":211,"wires":[["5b28fe49.4a16a8"]]},{"id":"5b28fe49.4a16a8","type":"change","z":"d91ddf95.ab3838","name":"set gate","rules":[{"t":"move","p":"payload","pt":"msg","to":"gate","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":353.5,"y":162,"wires":[[]]},{"id":"1384709e.7241e7","type":"debug","z":"d91ddf95.ab3838","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":584,"y":80,"wires":[]}]

It used to be in flow library at Simple gate to switch on and off a flow but has been replaced by a flow that uses a function node that stores its state in local context. This avoids having to define and keep track of many flow variables if a number of such gates is used.

I developed a gate node with a few additional features for my own use, but never published it to NPM because there were so many alternatives out there. I can be downloaded from GitHub at https://github.com/drmibell/node-red-contrib-gate if anyone is interested. Comments would be appreciated.

Well looking at the code on Github - it looks a very professional node :slight_smile:

Like the fact that users can set the gate topic and on/off/toggle message names

And the default state is a nice addition

I think what we need is one master gate node that combines does all that together with @tilleuls buffering when closed option, turnstyle mode with rate control and a "time to live" option as well

Since you've nabbed the contrib-gate name - I think its down to you to put it all together :slight_smile:
More than willing to help out with testing and constructive :slight_smile: feedback and suggestions

Simon, thanks for the kind words. I may have some concern on the grounds of "do one thing well." It seems to me that gating and queueing are different enough that perhaps they should be done in separate nodes. I have been thinking about it and looking over existing nodes and have some ideas.

First, I would like to publish node-red-contrib-gate on npm in its present form.

I don't think one can nab a node name until it is published to npm, and perhaps not even then. I'm more than willing to take this on, however, but I have some travel coming up that will slow the process.

I will raise an issue against node-red-contrib-simple-queue to see whether it can be modified to meet @tilleul's needs. At the same time, I'll look into modifying my gate node to (optionally) queue messages that arrive while it is closed. I would impose a limit on the length of the queue but not try to implement the "time to live" feature. If that works, we can decide whether we have one or two nodes, depending on how much extra effort/confusion the queueing feature generates for people who don't need it.

I didn't realise that it wasn't already published :slight_smile:

Very good point and its the NodeRED philosophy :slight_smile:

Maybe your gate could be called simplegate and just stick with doing that the simple pass/block function

But possibly just add a simple buffering option but no fancy turnstile rate limiting.

Simon

I have raised the issue I mentioned.

I think that's the right approach, but keep in mind that once we start queueing messages we buy into most of the complications involved regardless of the mechanism(s) we choose for releasing messages from the queue.

I was thinking that if messages are chosen to be queued rather than dropped, then if the gate is opened - they'd just all be sent thru at once - that's all the functionality I was thinking should be in it