Global variables, hardware buttons and my pea-brain

The four inject nodes are simulating your button inputs. Two for each button, one for up and one for down.

As an alternative option ..
What firmware you are running on ESP-s?
Many widely known firmwares support "long press" option alongside regular press for buttons. This way complexity can be avoided as there is 2 events available for one button.

I use my own firmware -ESP-GO - I have long-press but in this case the end product is a heating controller for our holiday rental - long press is WAY too complicated for end-users - 2 buttons at the same time is bad enough.

I have tried your function and realise I had not understood the requirement. If you press and release A then it sends "d". If you press and release B then it sends "u". Unless when you press one of them them the other is already pressed, in which case you wait till both are released and then send "a".
I need to think again about how I would do it.

You might like to try this. I think it is a bit more easy to understand what is going on.

[{"id":"a0cdb98f.65691","type":"join","z":"514a90a5.c7bae8","name":"","mode":"custom","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":true,"timeout":"","count":"1","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":333.5,"y":525,"wires":[["a1d27d3d.ff0ef8"]]},{"id":"a1d27d3d.ff0ef8","type":"function","z":"514a90a5.c7bae8","name":"u/d/a","func":"var aTopic = \"stat2/fromesp/trigger4\"\nvar bTopic = \"stat2/fromesp/trigger5\"\nvar waitingToSendAuto = context.get(\"waiting\") // defaults to effectively false\nif (waitingToSendAuto) {\n    // send it when both released\n    if (msg.payload[aTopic] === \"1\" && msg.payload[bTopic] === \"1\") {\n        msg.payload = \"a\"\n        waitingToSendAuto = false\n    } else {\n        msg = null\n    }\n} else {\n    // not waiting to send auto\n    if (msg.topic == aTopic && msg.payload[aTopic] === \"1\") {\n        // A has been released\n        msg.payload = \"d\"\n    } else if (msg.topic == bTopic && msg.payload[bTopic] === \"1\") {\n        // B has been released\n        msg.payload = \"u\"\n    } else if (msg.payload[aTopic] === \"0\" && msg.payload[bTopic] === \"0\") {\n        // both pressed, must wait for both to be released\n        waitingToSendAuto = true\n        msg = null\n    } else {\n        msg = null\n    }\n}\ncontext.set(\"waiting\", waitingToSendAuto)\nreturn msg;","outputs":1,"noerr":0,"x":473.5,"y":525,"wires":[["b5e7fa54.4425a"]]}]

Hi Colin

Asit happens - communicating with you guys led to me coming up with a solution as higher up... but the interaction was priceless - thanks for putting time into this - I'll check out your new solution later.