Skip to content

feat: MQTT Request Node - official v5 request-response support#5860

Open
Steve-Mcl wants to merge 12 commits into
mainfrom
feat-mqtt-request
Open

feat: MQTT Request Node - official v5 request-response support#5860
Steve-Mcl wants to merge 12 commits into
mainfrom
feat-mqtt-request

Conversation

@Steve-Mcl

@Steve-Mcl Steve-Mcl commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

Proposed changes

As discussed here: https://discourse.nodered.org/t/we-have-http-request-tcp-request-serial-request-any-objections-to-an-mqtt-request/101413

(UPDATE 2026-07-12) rebased this atop of the 5865-fix-tests-for-win branch (PR #5866) to pick up test fixes for windows

Overview

Adds a new core mqtt request node that implements the MQTT v5
request/response pattern (Response Topic + Correlation Data), plus the test
infrastructure to exercise the MQTT nodes without a live broker.

Major changes

This work was done in a specific order to avoid any breakage of existing MQTT nodes or flows:

In order of implementation:

  1. Reworked the MQTT unit tests to run against a mock broker. Added an
    in-process fake broker so the suite no longer requires a real broker, and
    made the tests run by default. They still run against a real broker when
    NR_MQTT_TESTS=1. Verified the full suite passes in both modes (not 100%
    true, 1 test was not working against real or fake - that was fixed in the process).

  2. Added the mqtt request node (editor UI, runtime, and built-in help).

  3. Confirmed the existing MQTT tests continue to pass against both the mock
    and a real broker (the change touches shared code such as
    subscriptionHandler).

  4. Added a test suite for the mqtt request node covering config defaults,
    the v5 guard, request/response round-trips, correlation matching, timeouts,
    in-flight collisions, QoS, and the buffer cap.

    • (UPDATE 2026-07-12) extended with an end-to-end slow responder test (times out with delivered:true and verifies the late reply is ignored - runs against both the mock and a real broker) and a mock-only half-open test (times out with delivered:false and verifies the stuck outgoing packet is purged)
  5. (UPDATE 2026-07-12) Timeout behaviour is now end-to-end The timeout is measured from when the message is received and covers the whole exchange (subscribe, publish and waiting for the response), so the node always produces exactly one outcome - a response or an error - within the timeout, even if the broker connection is half-open. If no matching response arrives in time (or the request cannot be sent), the node reports an error. The original input message is passed to any catch node, with a msg.mqtt object describing the outcome:

    • msg.mqtt.timedOut (boolean) - always true for a timeout
    • msg.mqtt.qos (number) - the QoS the request was published at
    • msg.mqtt.delivered (boolean) - true if publishing was confirmed before the timeout. For QoS 1/2 this means
      the broker acknowledged receipt; for QoS 0 it only means the packet was written to the connection.

    Use msg.mqtt.delivered to decide whether a retry is safe: when false, the request was not confirmed sent (and,
    for QoS 1/2, is purged from the client's outgoing store so it will not be re-sent on reconnect).

Demo flows & Screenshots

I will add these in a follow up comment

Additional detail

The node

  • MQTT v5 only. If the selected broker is not v5 the node does not register
    with it, shows a "requires MQTT v5" status, and rejects input with an error.
  • Response topic — static or dynamic. A static response topic is subscribed
    once at start-up and kept for the node's lifetime. A dynamic (msg.) response
    topic is subscribed per-request and always torn down after the matching
    response (or timeout).
  • Correlation data. Defaults to an auto-generated UUID (guaranteeing
    uniqueness); can instead be taken from a msg property.
    There was much discussion on the forum about hiding this altogether however
    there are valid use cases for a user-specified correlation value (e.g. a
    PLC with minimal string support, where the correlation is a simple integer representing
    a sequence number, where correlation is a composite of multiple values, or where the
    correlation data is exxpected to be a specific format).
    The node supports both, and the default is a safe choice for most users.
  • QoS defaults to 1 rather than 0 — a dropped response would only ever
    surface as a timeout. QoS 2 is intentionally not the default as it isn't
    universally supported (e.g. AWS IoT).
  • Message Expiry Interval is tied to the timeout: the outgoing request is
    sent with a v5 messageExpiryInterval equal to the wait timeout, so the
    broker discards an undelivered request once we've stopped waiting for it.
  • Subscribe-before-publish. The response subscription is confirmed
    (SUBACK) before the request is published; a refused subscription fails the
    request instead of publishing into the void.

Message property pass-through

  • The incoming message is passed through to the output. All non-MQTT
    properties are preserved — including req/res (http-in), _linkSource
    (link-call), _msgid, and any user properties — so the node composes inline
    in existing flows.
  • The MQTT message properties (payload, topic, qos, retain,
    responseTopic, correlationData, contentType, messageExpiryInterval,
    payloadFormatIndicator, userProperties) are replaced with the response's,
    or cleared if the response doesn't include them (so a request-side value can't
    linger and masquerade as the response's).
    The response payload is always set in msg.payload.
  • The incoming msg is cloned (RED.util.cloneMessage) up front, before any
    publish-time mutation, and that clone is the base for the output — isolating
    the in-flight message from by-reference changes elsewhere. cloneMessage
    keeps req/res by reference, so http-response style flows still work.

requestTopic

  • msg.topic on the output is the topic the response arrived on (the usual
    MQTT convention). To avoid losing the original request topic, it's preserved
    as msg.requestTopic.

Error handling

  • On timeout, a refused subscription, or a publish failure, the node reports an
    error rather than emitting on its output. The clean, passed-through input
    message (non-MQTT properties intact) is routed to any Catch node so the
    failure can be handled.

Collisions and back-pressure

  • In-flight requests are tracked on the shared broker connection, keyed by
    response topic + correlation data. Two requests that would put the same pair
    on the same client are indistinguishable on the wire, so the second is
    rejected rather than allowed to cross responses — this also covers multiple
    request nodes sharing one broker.
  • The number of concurrent in-flight requests a node buffers is bounded by the
    nodeMessageBufferMaxLength runtime setting.

Test infrastructure

  • mqtt.connect is exported as a non-configurable getter and can't be stubbed
    with sinon, so the mock is installed by swapping the mqtt module's cached
    exports for a shim whose connect we control; the node module is required
    after the shim is in place.
  • The request-node tests drive request/response through an ordinary node-based
    responder (mqtt in → change → mqtt out) rather than test-internal hooks, so
    the same tests pass against both the fake and a real broker.
  • Two tests that require driving the transport directly (a decoy mismatched
    correlation, and a forced SUBACK refusal) run only against the fake broker.
    What that essentially means is that in day-to-day development, all tests are
    ran (on the fake broker) but real tests against an actual MQTT broker (which
    to be fair, were rarely run anyway) are still possible except for 2 tests
    which would require a lot more plumbing for little gain.

Checklist

  • I have read the contribution guidelines
  • For non-bugfix PRs, I have discussed this change on the forum/slack team.
  • I have run npm run test to verify the unit tests pass
  • I have added suitable unit tests to cover the new/changed functionality

@Steve-Mcl

Copy link
Copy Markdown
Contributor Author

Demo Flow

[{"id":"8656004d13c70984","type":"tab","label":"MQTT Request","disabled":false,"info":"","env":[]},{"id":"a2cdf2f97358e7bd","type":"group","z":"8656004d13c70984","name":"Demonstrating MQTT Request - fully static","style":{"label":true,"stroke":"#92d04f"},"nodes":["ccd5460a6b619a71","486d024692f44322","b63d59667dfe0897","c86332a93b903785","f0fec2a5ba045cbc"],"x":54,"y":99,"w":712,"h":142},{"id":"31073873cda8bb2b","type":"group","z":"8656004d13c70984","name":"A Flow to that listens to a topic and reponds to the v5 repsonseTopic property \\n (e.g. another Node-RED or a 3rd party/external device)","style":{"label":true,"fill":"#001f60","fill-opacity":"0.64"},"nodes":["f6464087e144e1d1","01e20085de86bbb2","f5d16eb2d3cba37b","e08d55d86e6111d2","a219bfa4882b8aa1"],"x":54,"y":1151,"w":792,"h":170},{"id":"832757b35bc9b0a6","type":"group","z":"8656004d13c70984","name":"Demonstrating MQTT Request - dynamic topic","style":{"label":true,"stroke":"#92d04f"},"nodes":["4e1d9040379deb66","510327632d7db7c3","3dd42be8e5fb09ab","7962d70bc064b4ad","95da070ff6095c78"],"x":54,"y":259,"w":712,"h":142},{"id":"50f40042b1f708c5","type":"group","z":"8656004d13c70984","name":"Demonstrating MQTT Request - dynamic topic & response topic","style":{"label":true,"stroke":"#92d04f"},"nodes":["40b29ba1a1be9702","33422568cd89fd9b","99fdf51bf382a6b0","65fab33788597f2e","8548694b10b72c1c","b1a4520d9043f26e"],"x":54,"y":419,"w":712,"h":142},{"id":"791fcd8fef2c2b7c","type":"group","z":"8656004d13c70984","name":"Demonstrating MQTT Request - fully dynamic","style":{"label":true,"stroke":"#92d04f"},"nodes":["3a0912461b5c3a4f","7340f5300a3ba543","d63f4edb9ceb6a68","5b438aa9e2b43275","9499b88af24a7553","e84118fedd652b2e"],"x":54,"y":579,"w":712,"h":142},{"id":"b04584c910ce884a","type":"group","z":"8656004d13c70984","name":"Demonstrating timeout handling","style":{"label":true,"stroke":"#ff0000"},"nodes":["886bcfba9733419e","42fee9dd285fefd8","f58c400ad0d92918","716418eb49a22c77","2b5c61c2b90e25ea"],"x":834,"y":99,"w":752,"h":142},{"id":"5ebd38a1f3c5394a","type":"group","z":"8656004d13c70984","name":"Demonstrating MQTT Request -  using composite ENV VARs","style":{"label":true,"stroke":"#92d04f"},"nodes":["3817009a616c5e35","a5bc90edc1d20f72","abb7fef7eef5bd4f","51bb82a3042929fe","1e48cbe905a3cbd9"],"env":[{"name":"MQTT_REQ_MAX_TIME","value":"5","type":"num"},{"name":"SYS_NAME","value":"ff-test","type":"str"}],"x":54,"y":739,"w":712,"h":142},{"id":"d9f704f0612a4943","type":"group","z":"8656004d13c70984","name":"Demonstrating deliberate collision by sending successive messages with user correlation","style":{"label":true,"stroke":"#ff0000"},"nodes":["6123ebb0f5b8c11e","7a7525102c752da3","66b197b0d7130744","6f7e48e21a1e0b75","0a98a9f0de58dcc7","9b5684a220059202","5a3c4ef7955f913c","7ceb55cbd9808d8c","8b2b2277156b30de"],"x":834,"y":739,"w":752,"h":182},{"id":"52bd56340b996fbc","type":"group","z":"8656004d13c70984","name":"Demonstrating bad topic handling","style":{"label":true,"stroke":"#ff0000"},"nodes":["0b3a056f6d592730","c6f1c72583b416c7","91c987ae024aaab6","ac4f3c8bd908f25b","1c27aee822f60bc4"],"x":834,"y":259,"w":752,"h":142},{"id":"892c41296d0f081c","type":"group","z":"8656004d13c70984","name":"Demonstrating bad response topic handling","style":{"label":true,"stroke":"#ff0000"},"nodes":["6ff70dae64b1bdf8","12648ffd43b01163","1b09528d4f649d11","416083fbc1cb299b","6e4e75d39c3d33ef"],"x":834,"y":419,"w":752,"h":142},{"id":"5b794ab1431bac9e","type":"group","z":"8656004d13c70984","name":"Demonstrating MQTT Request - invalid timeout number","style":{"label":true,"stroke":"#ff0000"},"nodes":["6268491bd5f3a55c","63dc85c73cc08ddf","9119b16abf49c125","3c96740bd97a54f0","61d12885548e558a"],"x":834,"y":579,"w":752,"h":142},{"id":"224a1a43efa85ba6","type":"group","z":"8656004d13c70984","name":"Demonstrating MQTT Request exceeding nodeMessageBufferMaxLength (set in settings.js to 5)","style":{"label":true,"stroke":"#ff0000"},"nodes":["d2b9147b750922f0","c3c990cafd7ce09a","369778fac5ac674f","52b02dc6af73b78d","64e63a5b6a2a5051","a560e56ea71365fd"],"x":834,"y":947,"w":752,"h":154},{"id":"6fd81b79ee3659c3","type":"group","z":"8656004d13c70984","name":"Demonstrating MQTT Request - maintain input props & play nice with other flows like http endpoints/link-call","style":{"label":true,"stroke":"#92d04f"},"nodes":["2c20ef66950b1cf6","b62c54290661307c","ef146c0da0fb64b4","a29893eedfe9adf5","ceccc3ab5a6ab57e","007fa3b853715f71","8c82aceedef4dfe3","6f8a431d32b3126d"],"x":54,"y":899,"w":712,"h":142},{"id":"ccd5460a6b619a71","type":"mqtt request","z":"8656004d13c70984","g":"a2cdf2f97358e7bd","name":"","topic":"ff-test/1/request","topicType":"str","qos":"1","datatype":"auto-detect","responseTopic":"ff-test/response","responseTopicType":"str","correlationData":"correlationData","correlationDataType":"auto","timeout":5,"timeoutType":"num","broker":"502920ed14864bb3","x":420,"y":140,"wires":[["b63d59667dfe0897"]]},{"id":"f6464087e144e1d1","type":"mqtt in","z":"8656004d13c70984","g":"31073873cda8bb2b","name":"","topic":"ff-test/+/request","qos":"1","datatype":"auto-detect","broker":"502920ed14864bb3","nl":false,"rap":true,"rh":0,"inputs":0,"x":160,"y":1220,"wires":[["f5d16eb2d3cba37b","a219bfa4882b8aa1"]]},{"id":"01e20085de86bbb2","type":"debug","z":"8656004d13c70984","g":"31073873cda8bb2b","name":"remote responded","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":690,"y":1280,"wires":[]},{"id":"486d024692f44322","type":"inject","z":"8656004d13c70984","g":"a2cdf2f97358e7bd","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"DEMO-1","payloadType":"str","x":160,"y":140,"wires":[["ccd5460a6b619a71"]]},{"id":"b63d59667dfe0897","type":"debug","z":"8656004d13c70984","g":"a2cdf2f97358e7bd","name":"DEMO 1","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"payload","statusType":"auto","x":660,"y":140,"wires":[]},{"id":"f5d16eb2d3cba37b","type":"change","z":"8656004d13c70984","g":"31073873cda8bb2b","name":"Update payload \\n Move responseTopic to topic","rules":[{"t":"move","p":"responseTopic","pt":"msg","to":"topic","tot":"msg"},{"t":"set","p":"payload","pt":"msg","to":"\"[\" & $moment().format('HH:mm:ss.SSS') & \"] \" & payload & \" (Response Payload)\" ","tot":"jsonata"},{"t":"set","p":"userProperties","pt":"msg","to":"{\"res\":\"I was added by the responder\"}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":420,"y":1220,"wires":[["01e20085de86bbb2","e08d55d86e6111d2"]]},{"id":"e08d55d86e6111d2","type":"mqtt out","z":"8656004d13c70984","g":"31073873cda8bb2b","name":"central broker.","topic":"","qos":"2","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"502920ed14864bb3","x":740,"y":1220,"wires":[]},{"id":"c86332a93b903785","type":"catch","z":"8656004d13c70984","g":"a2cdf2f97358e7bd","name":"","scope":"group","uncaught":false,"x":410,"y":200,"wires":[["f0fec2a5ba045cbc"]]},{"id":"f0fec2a5ba045cbc","type":"debug","z":"8656004d13c70984","g":"a2cdf2f97358e7bd","name":"Error","active":true,"tosidebar":true,"console":true,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":660,"y":200,"wires":[]},{"id":"a219bfa4882b8aa1","type":"debug","z":"8656004d13c70984","g":"31073873cda8bb2b","name":"remote received","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":180,"y":1280,"wires":[]},{"id":"4e1d9040379deb66","type":"mqtt request","z":"8656004d13c70984","g":"832757b35bc9b0a6","name":"","topic":"topic","topicType":"msg","qos":"","datatype":"auto-detect","responseTopic":"ff-test/response","responseTopicType":"str","correlationData":"correlationData","correlationDataType":"auto","timeout":5,"timeoutType":"num","broker":"502920ed14864bb3","x":450,"y":300,"wires":[["3dd42be8e5fb09ab"]]},{"id":"510327632d7db7c3","type":"inject","z":"8656004d13c70984","g":"832757b35bc9b0a6","name":"ff-test/1/request : DEMO-2","props":[{"p":"topic","vt":"str"},{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"ff-test/1/request","payload":"DEMO-2","payloadType":"str","x":210,"y":300,"wires":[["4e1d9040379deb66"]]},{"id":"3dd42be8e5fb09ab","type":"debug","z":"8656004d13c70984","g":"832757b35bc9b0a6","name":"DEMO 2","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"payload","statusType":"auto","x":660,"y":300,"wires":[]},{"id":"7962d70bc064b4ad","type":"catch","z":"8656004d13c70984","g":"832757b35bc9b0a6","name":"","scope":"group","uncaught":false,"x":410,"y":360,"wires":[["95da070ff6095c78"]]},{"id":"95da070ff6095c78","type":"debug","z":"8656004d13c70984","g":"832757b35bc9b0a6","name":"Error","active":true,"tosidebar":true,"console":true,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":660,"y":360,"wires":[]},{"id":"40b29ba1a1be9702","type":"mqtt request","z":"8656004d13c70984","g":"50f40042b1f708c5","name":"","topic":"topic","topicType":"msg","qos":"","datatype":"auto-detect","responseTopic":"responseTopic","responseTopicType":"msg","correlationData":"correlationData","correlationDataType":"auto","timeout":5,"timeoutType":"num","broker":"502920ed14864bb3","x":490,"y":460,"wires":[["99fdf51bf382a6b0"]]},{"id":"33422568cd89fd9b","type":"inject","z":"8656004d13c70984","g":"50f40042b1f708c5","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"DEMO-3","payloadType":"str","x":160,"y":460,"wires":[["b1a4520d9043f26e"]]},{"id":"99fdf51bf382a6b0","type":"debug","z":"8656004d13c70984","g":"50f40042b1f708c5","name":"DEMO 3","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"payload","statusType":"auto","x":660,"y":460,"wires":[]},{"id":"65fab33788597f2e","type":"catch","z":"8656004d13c70984","g":"50f40042b1f708c5","name":"","scope":"group","uncaught":false,"x":410,"y":520,"wires":[["8548694b10b72c1c"]]},{"id":"8548694b10b72c1c","type":"debug","z":"8656004d13c70984","g":"50f40042b1f708c5","name":"Error","active":true,"tosidebar":true,"console":true,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":660,"y":520,"wires":[]},{"id":"b1a4520d9043f26e","type":"function","z":"8656004d13c70984","g":"50f40042b1f708c5","name":"set topics","func":"\nlet index = (context.get('index') ?? 0) + 1\ncontext.set('index', index)\n\nmsg.topic = `ff-test/${index}/request`\nmsg.responseTopic = `ff-test/${index}/response`\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":320,"y":460,"wires":[["40b29ba1a1be9702"]]},{"id":"3a0912461b5c3a4f","type":"mqtt request","z":"8656004d13c70984","g":"791fcd8fef2c2b7c","name":"","topic":"topic","topicType":"msg","qos":"","datatype":"auto-detect","responseTopic":"responseTopic","responseTopicType":"msg","correlationData":"correlationData","correlationDataType":"auto","timeout":"messageExpiryInterval","timeoutType":"msg","broker":"502920ed14864bb3","x":490,"y":620,"wires":[["d63f4edb9ceb6a68"]]},{"id":"7340f5300a3ba543","type":"inject","z":"8656004d13c70984","g":"791fcd8fef2c2b7c","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"DEMO-4","payloadType":"str","x":160,"y":620,"wires":[["e84118fedd652b2e"]]},{"id":"d63f4edb9ceb6a68","type":"debug","z":"8656004d13c70984","g":"791fcd8fef2c2b7c","name":"DEMO 4","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"payload","statusType":"auto","x":660,"y":620,"wires":[]},{"id":"5b438aa9e2b43275","type":"catch","z":"8656004d13c70984","g":"791fcd8fef2c2b7c","name":"","scope":"group","uncaught":false,"x":410,"y":680,"wires":[["9499b88af24a7553"]]},{"id":"9499b88af24a7553","type":"debug","z":"8656004d13c70984","g":"791fcd8fef2c2b7c","name":"Error","active":true,"tosidebar":true,"console":true,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":660,"y":680,"wires":[]},{"id":"e84118fedd652b2e","type":"function","z":"8656004d13c70984","g":"791fcd8fef2c2b7c","name":"set props","func":"\nfunction randomInteger(min, max) {\n  return Math.floor(Math.random() * (max - min) ) + min;\n}\nlet index = (context.get('index') ?? 0) + 1\ncontext.set('index', index)\n\nmsg.topic = `ff-test/${index}/request`\nmsg.responseTopic = `ff-test/${index}/response`\nmsg.messageExpiryInterval = randomInteger(1,5)\n\n\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":320,"y":620,"wires":[["3a0912461b5c3a4f"]]},{"id":"886bcfba9733419e","type":"mqtt request","z":"8656004d13c70984","g":"b04584c910ce884a","name":"","topic":"ff-test/1/request-not-subbed","topicType":"str","qos":"","datatype":"auto-detect","responseTopic":"ff-test/response","responseTopicType":"str","correlationData":"correlationData","correlationDataType":"auto","timeout":"1","timeoutType":"num","broker":"502920ed14864bb3","x":1260,"y":140,"wires":[["f58c400ad0d92918"]]},{"id":"42fee9dd285fefd8","type":"inject","z":"8656004d13c70984","g":"b04584c910ce884a","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"DEMO-6","payloadType":"str","x":940,"y":140,"wires":[["886bcfba9733419e"]]},{"id":"f58c400ad0d92918","type":"debug","z":"8656004d13c70984","g":"b04584c910ce884a","name":"DEMO 6","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1480,"y":140,"wires":[]},{"id":"716418eb49a22c77","type":"catch","z":"8656004d13c70984","g":"b04584c910ce884a","name":"","scope":"group","uncaught":false,"x":1210,"y":200,"wires":[["2b5c61c2b90e25ea"]]},{"id":"1e48cbe905a3cbd9","type":"mqtt request","z":"8656004d13c70984","g":"5ebd38a1f3c5394a","name":"${SYS_NAME}/1/request","topic":"${SYS_NAME}/1/request","topicType":"env","qos":"","datatype":"auto-detect","responseTopic":"${SYS_NAME}/1/response","responseTopicType":"env","correlationData":"correlationData","correlationDataType":"auto","timeout":"MQTT_REQ_MAX_TIME","timeoutType":"env","broker":"502920ed14864bb3","x":430,"y":780,"wires":[["a5bc90edc1d20f72"]]},{"id":"3817009a616c5e35","type":"inject","z":"8656004d13c70984","g":"5ebd38a1f3c5394a","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"DEMO-5","payloadType":"str","x":160,"y":780,"wires":[["1e48cbe905a3cbd9"]]},{"id":"a5bc90edc1d20f72","type":"debug","z":"8656004d13c70984","g":"5ebd38a1f3c5394a","name":"DEMO 5","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"payload","statusType":"auto","x":660,"y":780,"wires":[]},{"id":"abb7fef7eef5bd4f","type":"catch","z":"8656004d13c70984","g":"5ebd38a1f3c5394a","name":"","scope":"group","uncaught":false,"x":410,"y":840,"wires":[["51bb82a3042929fe"]]},{"id":"51bb82a3042929fe","type":"debug","z":"8656004d13c70984","g":"5ebd38a1f3c5394a","name":"Error","active":true,"tosidebar":true,"console":true,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":660,"y":840,"wires":[]},{"id":"6123ebb0f5b8c11e","type":"mqtt request","z":"8656004d13c70984","g":"d9f704f0612a4943","name":"","topic":"ff-test/1/request","topicType":"str","qos":"","datatype":"auto-detect","responseTopic":"ff-test/response","responseTopicType":"str","correlationData":"correlationData","correlationDataType":"msg","timeout":"2","timeoutType":"num","broker":"502920ed14864bb3","x":1260,"y":780,"wires":[["66b197b0d7130744"]]},{"id":"7a7525102c752da3","type":"inject","z":"8656004d13c70984","g":"d9f704f0612a4943","name":"correlationData=abc \\n payload=DEMO-10","props":[{"p":"payload"},{"p":"correlationData","v":"abc","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"DEMO-10","payloadType":"str","x":970,"y":800,"wires":[["7ceb55cbd9808d8c","8b2b2277156b30de"]]},{"id":"66b197b0d7130744","type":"debug","z":"8656004d13c70984","g":"d9f704f0612a4943","name":"DEMO 10a","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"payload","statusType":"msg","x":1470,"y":780,"wires":[]},{"id":"6f7e48e21a1e0b75","type":"catch","z":"8656004d13c70984","g":"d9f704f0612a4943","name":"","scope":"group","uncaught":false,"x":930,"y":880,"wires":[["0a98a9f0de58dcc7"]]},{"id":"0b3a056f6d592730","type":"mqtt request","z":"8656004d13c70984","g":"52bd56340b996fbc","name":"","topic":"topic","topicType":"msg","qos":"","datatype":"auto-detect","responseTopic":"ff-test/response","responseTopicType":"str","correlationData":"","correlationDataType":"auto","timeout":"2","timeoutType":"num","broker":"502920ed14864bb3","x":1210,"y":300,"wires":[["91c987ae024aaab6"]]},{"id":"c6f1c72583b416c7","type":"inject","z":"8656004d13c70984","g":"52bd56340b996fbc","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"#/bad#/topic","payload":"DEMO-7","payloadType":"str","x":980,"y":300,"wires":[["0b3a056f6d592730"]]},{"id":"91c987ae024aaab6","type":"debug","z":"8656004d13c70984","g":"52bd56340b996fbc","name":"DEMO 7","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1480,"y":300,"wires":[]},{"id":"ac4f3c8bd908f25b","type":"catch","z":"8656004d13c70984","g":"52bd56340b996fbc","name":"","scope":"group","uncaught":false,"x":1210,"y":360,"wires":[["1c27aee822f60bc4"]]},{"id":"6ff70dae64b1bdf8","type":"mqtt request","z":"8656004d13c70984","g":"892c41296d0f081c","name":"","topic":"ff-test/1/request","topicType":"str","qos":"","datatype":"auto-detect","responseTopic":"responseTopic","responseTopicType":"msg","correlationData":"correlationData","correlationDataType":"msg","timeout":"2","timeoutType":"num","broker":"502920ed14864bb3","x":1220,"y":460,"wires":[["1b09528d4f649d11"]]},{"id":"12648ffd43b01163","type":"inject","z":"8656004d13c70984","g":"892c41296d0f081c","name":"res topic = this/#is/#bad \\n payload=DEMO-8","props":[{"p":"responseTopic","v":"this/#is/#bad","vt":"str"},{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"DEMO-8","payloadType":"str","x":980,"y":480,"wires":[["6ff70dae64b1bdf8"]]},{"id":"1b09528d4f649d11","type":"debug","z":"8656004d13c70984","g":"892c41296d0f081c","name":"DEMO 8","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1480,"y":460,"wires":[]},{"id":"416083fbc1cb299b","type":"catch","z":"8656004d13c70984","g":"892c41296d0f081c","name":"","scope":"group","uncaught":false,"x":1210,"y":520,"wires":[["6e4e75d39c3d33ef"]]},{"id":"6268491bd5f3a55c","type":"mqtt request","z":"8656004d13c70984","g":"5b794ab1431bac9e","name":"","topic":"ff-test/99/request","topicType":"str","qos":"","datatype":"auto-detect","responseTopic":"ff-test/response","responseTopicType":"str","correlationData":"","correlationDataType":"auto","timeout":"messageExpiryInterval","timeoutType":"msg","broker":"502920ed14864bb3","x":1230,"y":620,"wires":[["9119b16abf49c125"]]},{"id":"63dc85c73cc08ddf","type":"inject","z":"8656004d13c70984","g":"5b794ab1431bac9e","name":"messageExpiryInterval=BAD \\n payload=DEMO-9","props":[{"p":"messageExpiryInterval","v":"BAD","vt":"str"},{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"DEMO-9","payloadType":"str","x":1000,"y":640,"wires":[["6268491bd5f3a55c"]]},{"id":"9119b16abf49c125","type":"debug","z":"8656004d13c70984","g":"5b794ab1431bac9e","name":"DEMO 9","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1480,"y":620,"wires":[]},{"id":"3c96740bd97a54f0","type":"catch","z":"8656004d13c70984","g":"5b794ab1431bac9e","name":"","scope":"group","uncaught":false,"x":1210,"y":680,"wires":[["61d12885548e558a"]]},{"id":"61d12885548e558a","type":"debug","z":"8656004d13c70984","g":"5b794ab1431bac9e","name":"Error","active":true,"tosidebar":true,"console":true,"tostatus":true,"complete":"true","targetType":"full","statusVal":"$substring(error.message, 7, 80)","statusType":"jsonata","x":1480,"y":680,"wires":[]},{"id":"6e4e75d39c3d33ef","type":"debug","z":"8656004d13c70984","g":"892c41296d0f081c","name":"Error","active":true,"tosidebar":true,"console":true,"tostatus":true,"complete":"true","targetType":"full","statusVal":"$substring(error.message, 7, 80)","statusType":"jsonata","x":1480,"y":520,"wires":[]},{"id":"1c27aee822f60bc4","type":"debug","z":"8656004d13c70984","g":"52bd56340b996fbc","name":"Error","active":true,"tosidebar":true,"console":true,"tostatus":true,"complete":"true","targetType":"full","statusVal":"$substring(error.message, 7, 80)","statusType":"jsonata","x":1480,"y":360,"wires":[]},{"id":"0a98a9f0de58dcc7","type":"debug","z":"8656004d13c70984","g":"d9f704f0612a4943","name":"Error","active":true,"tosidebar":true,"console":true,"tostatus":true,"complete":"true","targetType":"full","statusVal":"$substring(error.message, 7, 80)","statusType":"jsonata","x":1480,"y":880,"wires":[]},{"id":"2b5c61c2b90e25ea","type":"debug","z":"8656004d13c70984","g":"b04584c910ce884a","name":"Error","active":true,"tosidebar":true,"console":true,"tostatus":true,"complete":"true","targetType":"full","statusVal":"$substring(error.message, 7, 80)","statusType":"jsonata","x":1480,"y":200,"wires":[]},{"id":"9b5684a220059202","type":"mqtt request","z":"8656004d13c70984","g":"d9f704f0612a4943","name":"","topic":"ff-test/2/request","topicType":"str","qos":"","datatype":"auto-detect","responseTopic":"ff-test/response","responseTopicType":"str","correlationData":"correlationData","correlationDataType":"msg","timeout":"2","timeoutType":"num","broker":"502920ed14864bb3","x":1260,"y":840,"wires":[["5a3c4ef7955f913c"]]},{"id":"5a3c4ef7955f913c","type":"debug","z":"8656004d13c70984","g":"d9f704f0612a4943","name":"DEMO 10b","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"payload","statusType":"msg","x":1470,"y":840,"wires":[]},{"id":"7ceb55cbd9808d8c","type":"change","z":"8656004d13c70984","g":"d9f704f0612a4943","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload & \"-MSG-1\"","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":1125,"y":780,"wires":[["6123ebb0f5b8c11e"]],"l":false},{"id":"8b2b2277156b30de","type":"change","z":"8656004d13c70984","g":"d9f704f0612a4943","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload & \"-MSG-2\"","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1125,"y":840,"wires":[["9b5684a220059202"]],"l":false},{"id":"12ac4ddf702a1abe","type":"comment","z":"8656004d13c70984","name":"Working Demos of MQTT Request \\n These demos show how the MQTT Request can be operated via fixed entry or dynamically","info":"","x":390,"y":60,"wires":[]},{"id":"aa5187336d87dbcd","type":"comment","z":"8656004d13c70984","name":"Error handling demos of MQTT Request \\n These demos show how the MQTT Request handles bad input for topic, responseTopic, expiry etc","info":"","x":1190,"y":60,"wires":[]},{"id":"d2b9147b750922f0","type":"catch","z":"8656004d13c70984","g":"224a1a43efa85ba6","name":"","scope":"group","uncaught":false,"x":930,"y":1060,"wires":[["c3c990cafd7ce09a"]]},{"id":"c3c990cafd7ce09a","type":"debug","z":"8656004d13c70984","g":"224a1a43efa85ba6","name":"Error","active":true,"tosidebar":true,"console":true,"tostatus":true,"complete":"true","targetType":"full","statusVal":"$substring(error.message, 7, 80)","statusType":"jsonata","x":1200,"y":1060,"wires":[]},{"id":"369778fac5ac674f","type":"inject","z":"8656004d13c70984","g":"224a1a43efa85ba6","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"DEMO-11","payloadType":"str","x":940,"y":1000,"wires":[["52b02dc6af73b78d"]]},{"id":"52b02dc6af73b78d","type":"function","z":"8656004d13c70984","g":"224a1a43efa85ba6","name":"send 6 requests \\n in quick succession","func":"const first = 1\nconst last = 6\n\nfor (let index = first; index <= last; index++) {\n  const m = RED.util.cloneMessage(msg)\n  m.payload = `MSG-${index}`\n  m.topic = `ff-test/${index}/request`\n  m.responseTopic = `ff-test/response`\n  m.messageExpiryInterval = 5\n  node.send(m);\n}\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1130,"y":1000,"wires":[["64e63a5b6a2a5051"]]},{"id":"64e63a5b6a2a5051","type":"mqtt request","z":"8656004d13c70984","g":"224a1a43efa85ba6","name":"","topic":"topic","topicType":"msg","qos":"","datatype":"auto-detect","responseTopic":"responseTopic","responseTopicType":"msg","correlationData":"correlationData","correlationDataType":"auto","timeout":"messageExpiryInterval","timeoutType":"msg","broker":"502920ed14864bb3","x":1310,"y":1000,"wires":[["a560e56ea71365fd"]]},{"id":"a560e56ea71365fd","type":"debug","z":"8656004d13c70984","g":"224a1a43efa85ba6","name":"DEMO 11","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"payload","statusType":"auto","x":1480,"y":1000,"wires":[]},{"id":"2c20ef66950b1cf6","type":"inject","z":"8656004d13c70984","g":"6fd81b79ee3659c3","name":"","props":[{"p":"payload"},{"p":"extra","v":"i am an extra prop from the request","vt":"str"},{"p":"userProperties","v":"{\"req\":\"I will be overwritten\"}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"DEMO-12","payloadType":"str","x":160,"y":940,"wires":[["6f8a431d32b3126d"]]},{"id":"b62c54290661307c","type":"debug","z":"8656004d13c70984","g":"6fd81b79ee3659c3","name":"DEMO 12","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"payload","statusType":"auto","x":660,"y":940,"wires":[]},{"id":"ef146c0da0fb64b4","type":"catch","z":"8656004d13c70984","g":"6fd81b79ee3659c3","name":"","scope":"group","uncaught":false,"x":470,"y":1000,"wires":[["a29893eedfe9adf5"]]},{"id":"a29893eedfe9adf5","type":"debug","z":"8656004d13c70984","g":"6fd81b79ee3659c3","name":"Error","active":true,"tosidebar":true,"console":true,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":660,"y":1000,"wires":[]},{"id":"ceccc3ab5a6ab57e","type":"link in","z":"8656004d13c70984","g":"6fd81b79ee3659c3","name":"link in 5","links":[],"x":95,"y":1000,"wires":[["8c82aceedef4dfe3"]]},{"id":"007fa3b853715f71","type":"link out","z":"8656004d13c70984","g":"6fd81b79ee3659c3","name":"link out 8","mode":"return","links":[],"x":355,"y":1000,"wires":[]},{"id":"8c82aceedef4dfe3","type":"mqtt request","z":"8656004d13c70984","g":"6fd81b79ee3659c3","name":"","topic":"ff-test/1/request","topicType":"str","qos":"1","datatype":"auto-detect","responseTopic":"ff-test/response","responseTopicType":"str","correlationData":"correlationData","correlationDataType":"auto","timeout":5,"timeoutType":"num","broker":"502920ed14864bb3","x":220,"y":1000,"wires":[["007fa3b853715f71"]]},{"id":"6f8a431d32b3126d","type":"link call","z":"8656004d13c70984","g":"6fd81b79ee3659c3","name":"","links":["ceccc3ab5a6ab57e"],"linkType":"static","timeout":"30","x":360,"y":940,"wires":[["b62c54290661307c"]]},{"id":"c77b262d767c9072","type":"complete","z":"8656004d13c70984","name":"","scope":["64e63a5b6a2a5051","9b5684a220059202","6123ebb0f5b8c11e","6268491bd5f3a55c","6ff70dae64b1bdf8","0b3a056f6d592730","886bcfba9733419e","ccd5460a6b619a71","4e1d9040379deb66","40b29ba1a1be9702","3a0912461b5c3a4f","1e48cbe905a3cbd9","8c82aceedef4dfe3"],"uncaught":false,"x":1010,"y":1220,"wires":[["4b3c20a40a4a412f"]]},{"id":"4b3c20a40a4a412f","type":"debug","z":"8656004d13c70984","name":"Complete","active":true,"tosidebar":true,"console":true,"tostatus":true,"complete":"true","targetType":"full","statusVal":"requestTopic or topic","statusType":"jsonata","x":1230,"y":1220,"wires":[]},{"id":"5c4fac7adb9e4ff9","type":"comment","z":"8656004d13c70984","name":"Complete should only occur on successful response","info":"","x":1130,"y":1180,"wires":[]},{"id":"502920ed14864bb3","type":"mqtt-broker","name":"","broker":"broker.emqx.io","port":1883,"clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"5","keepalive":60,"cleansession":true,"autoUnsubscribe":true,"birthTopic":"","birthQos":"0","birthRetain":"false","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closeRetain":"false","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willRetain":"false","willPayload":"","willMsg":{},"userProps":"","sessionExpiry":""}]

Screenshots

MKZylcMmFt image

@Steve-Mcl Steve-Mcl force-pushed the feat-mqtt-request branch from 6bcacb4 to b786725 Compare July 12, 2026 12:03
@Steve-Mcl Steve-Mcl changed the base branch from dev to 5865-fix-tests-for-win July 12, 2026 12:05
@Steve-Mcl Steve-Mcl requested a review from knolleary July 12, 2026 12:09
Base automatically changed from 5865-fix-tests-for-win to main July 13, 2026 09:02
@colinl

colinl commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

I have done some fairly extensive testing, in particular the operation over network failure events.
Tested dynamic request/response topics tested, including multiple transactions with differing topics underway at the same time.
Tested correlation data supplied via message.
Tested V5 user properties are propagated and returned correctly.
Checked that with QoS 1/2 that messages sent immediately after network disconnection, but before the connection is flagged as not connected, do not get sent when the network recovers.
All functioning as expected as far as I can see.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants