Modbus TCP previews correct output, but receiving end gets garbage

I'm running a Modbus TCP server within node-red. I had a bit of an adventure forwarding the right ports from Docker, and now its signal is reachable from clients (sorry, I know it's master/slave config, but I'm more familiar with server/client analogies).

The server log outputs the following:

{"type":"holding","message":{"topic":"gboiler_running_condition","messageId":"645c21b8b9dfd5476afc4fb9","payload":{"value":6,"register":"holding","address":1,"disableMsgOutput":0},"queueLengthByUnitId":{"unitId":255,"queueLength":2},"queueUnitId":255,"unitId":255,"modbusRequest":{"unitid":255,"fc":3,"address":80,"quantity":1,"emptyMsgOnFail":false,"keepMsgProperties":false,"messageId":"645c21b8b9dfd5476afc4fb9"},"responseBuffer":{"data":[6],"buffer":[0,6]},"_msgid":"c926d42fa9455229","bufferAddress":8,"bufferPayload":6,"wasMultipleWrite":false},"payload":[0,0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,189,0,0,0,0,0,0,0,152,0,0,0,0,0,0,0,50,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"_msgid":"af13a6b155a01bca"}

Server screenshot:

which I assume is valid Modbus signals. I can write arbitrary numbers e.g.1337 and that will be broadcast.

On the receiving end (A), we see something like the following:

JSObject, with 4 numbers spaced 4 "slots" apart (what we're expecting, but arbitrary spacing? In slots 4, 8, 12, 16) and if I intentionally write arbitrary numbers e.g. 1337, we see those in the expected "slot". However, only the first slot contains numbers as broadcast, the others seem scaled or otherwise mangled. I would assume it's just expected numbers for this unfamiliar protocol, were it not for receiving end (b), which receives alternating random characters:

Set 1

Set 2
Brown&carroll client set 2

There's a possibly related thread, but I don't quite understand what I'm looking for or how to fix it. Does the output get displayed as DEC in the log, then get broadcast as HEX or something? Can anyone help me figure this out?

Basically, it's a Modbus TCP slave, and when masters query its holding registers (which the slave log says contain A) we get B. Here's the flows:

Slave (server) flows:

[
    {
        "id": "3decb5f518882686",
        "type": "subflow",
        "name": "live debug",
        "info": "",
        "category": "",
        "in": [
            {
                "x": 240,
                "y": 160,
                "wires": [
                    {
                        "id": "45dbf990.ba2408"
                    }
                ]
            }
        ],
        "out": [
            {
                "x": 560,
                "y": 160,
                "wires": [
                    {
                        "id": "45dbf990.ba2408",
                        "port": 1
                    }
                ]
            }
        ],
        "env": [
            {
                "name": "debug_name",
                "type": "str",
                "value": "Debug",
                "ui": {
                    "label": {
                        "en-US": "Name"
                    },
                    "type": "input",
                    "opts": {
                        "types": [
                            "str"
                        ]
                    }
                }
            },
            {
                "name": "debug_property",
                "type": "str",
                "value": "msg",
                "ui": {
                    "label": {
                        "en-US": "Property Path"
                    },
                    "type": "input",
                    "opts": {
                        "types": [
                            "str"
                        ]
                    }
                }
            },
            {
                "name": "pass_debug_property",
                "type": "bool",
                "value": "false",
                "ui": {
                    "label": {
                        "en-US": "Pass Property Path"
                    },
                    "type": "input",
                    "opts": {
                        "types": [
                            "bool"
                        ]
                    }
                }
            }
        ],
        "meta": {},
        "color": "#DDAA99"
    },
    {
        "id": "45dbf990.ba2408",
        "type": "function",
        "z": "3decb5f518882686",
        "name": "format time nicely",
        "func": "let msg1 = {payload: RED.util.cloneMessage(msg)};\nmsg1.payload.debug_property = msg.debug_property || env.get(\"debug_property\") || \"msg\"\ntry {\n  msg1.payload.debug_property_array = RED.util.normalisePropertyExpression(msg1.payload.debug_property)\n}\ncatch(error){\n  msg.payload = error;\n  msg1.payload.error = error;\n}\nmsg1.payload.debug_name = env.get(\"debug_name\") || msg._msgid || \"name error\"\nmsg1.payload.pass_debug_property = env.get(\"pass_debug_property\") || false\nreturn [msg1,msg];",
        "outputs": 2,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 390,
        "y": 120,
        "wires": [
            [
                "50da04b3.af25fc"
            ],
            []
        ]
    },
    {
        "id": "50da04b3.af25fc",
        "type": "websocket out",
        "z": "3decb5f518882686",
        "name": "",
        "server": "985ecbc7.67a138",
        "client": "",
        "x": 680,
        "y": 120,
        "wires": []
    },
    {
        "id": "1787be40.e87842",
        "type": "http in",
        "z": "3decb5f518882686",
        "name": "",
        "url": "/debug",
        "method": "get",
        "upload": false,
        "swaggerDoc": "",
        "x": 330,
        "y": 40,
        "wires": [
            [
                "1857548e.e7a8ab"
            ]
        ]
    },
    {
        "id": "1857548e.e7a8ab",
        "type": "template",
        "z": "3decb5f518882686",
        "name": "Simple Web Page",
        "field": "payload",
        "fieldType": "msg",
        "format": "html",
        "syntax": "mustache",
        "template": "<!DOCTYPE HTML>\n<html>\n    <head>\n        <style>\n            span.debug_name {\n                color: black;\n            }\n            span.debug_error{\n                color: red;\n            }\n            span.debug_date{\n                color:blue;\n            }\n            pre[id^='json_data_']{\n                font-size:20px;\n                background-color: ghostwhite;\n                border: 1px solid silver;\n                padding: 10px 20px;\n                margin: 20px; \n                white-space: pre-wrap;\n            }\n            .json-key {\n                color: olive;\n            }\n            .json-value {\n                color: navy;\n            }\n            .json-string {\n                color: brown;\n            }\n\n        </style>\n    <title>Debug Messages</title>\n    <script type=\"text/javascript\">\n        var ws;\n        var wsUri = \"ws:\";\n        var loc = window.location;\n        console.log(loc);\n        if (loc.protocol === \"https:\") { wsUri = \"wss:\"; }\n        // This needs to point to the web socket in the Node-RED flow\n        wsUri += \"//\" + loc.host + loc.pathname.replace(\"debug\",\"ws/debug\");\n\n        function wsConnect() {\n            console.log(\"connect\",wsUri);\n            ws = new WebSocket(wsUri);\n            ws.onmessage = function(msg) {\n                segments = document.getElementById('json_data').innerHTML;\n                var paused = document.getElementById('paused').innerText === \"Pause\";\n                var data;\n                var line = \"\";\n                var error = \"\";\n                var date = new Date().toISOString();\n                // parse the incoming message as a JSON object\n                try { \n                    data = JSON.parse(msg.data);\n                } catch (json_error) {\n                    data = {error: [\"json parse error\", json_error]};\n                };   \n                if(!data.error){\n                    var debug_name = data.debug_name ;\n                    var debug_property = data.debug_property_array;\n                    var original_property = data.debug_property;\n                    if(!data.pass_debug_property) delete data.debug_property;\n                    delete data.pass_debug_property;\n                    delete data.debug_property_array;\n                    delete data.debug_name;\n                    debug_property.slice(1).forEach((prop, index) => {\n                        if(data[prop]) {                    \n                            data = data[prop];\n                        }else{\n                           error = \"Property path Error on or after - \" + debug_property[index];\n                        }\n                    });\n\n                    line = `<div id=\"element${date}\">\n                    <hr/>\n                    <span class=\"debug_name\">${debug_name}</span> <span class=\"debug_date\">${date}</span> \n                    <span class=\"debug_orginal\">${original_property}</span> <span class=\"debug_error\">${error}</span> \n                     <button title=\"copy\" alt=\"copy\" onclick=\"copy_text('json_data_${date}')\"> &#10066; </button>\n                    <button title=\"delete\" alt=\"delete\" onclick=\"delete_text('element${date}')\"> &#10060; </button> \n                    <span class=\"json_data\">\n                    <pre id=\"json_data_${date}\"><code id=data>${library.json.prettyPrint(data)}</code></pre></span> \n                    </div>`;\n                }else{\n                    line = `<div id=\"element${date}\">\n                    <hr/>\n                    <span class=\"debug_error\">Error: ${JSON.stringify(data.error)}</span> <span class=\"debug_date\">${date}</span>                  \n                    <button title=\"delete\" alt=\"delete\" onclick=\"delete_text('element${date}')\"> &#10060; </button> \n                    </div>`;\n                }\n            // append line to segment\n            if(paused){\n                document.getElementById('json_data').innerHTML = line + segments;\n            }\n            }\n            ws.onopen = function() {\n                // update the status div with the connection status\n                document.getElementById('status').innerHTML = \"connected\";\n                //ws.send(\"Open for data\");\n                console.log(\"connected\");\n            }\n            ws.onclose = function() {\n                // update the status div with the connection status\n                document.getElementById('status').innerHTML = \"not connected\";\n                // in case of lost connection tries to reconnect every 3 secs\n                setTimeout(wsConnect,3000);\n            }\n        }\n        function copy_text(input) {\n            // Get the text field\n            var copyText = document.getElementById(input).innerText;\n            var dummy = document.createElement(\"textarea\");\n            document.body.appendChild(dummy);\n            dummy.value = copyText;\n            dummy.select();\n            document.execCommand(\"copy\");\n            document.body.removeChild(dummy);\n            alert(\"Copied  Data to Clipboard\\n\\n\" + copyText)\n        }\n        function delete_text(input) {\n            // Get the text field\n            document.getElementById(input).innerHTML = \"\";\n        }\n        if (!library) var library = {};\n            library.json = {\n                replacer: function(match, pIndent, pKey, pVal, pEnd) {\n                    var key = '<span class=json-key>\"';\n                    var val = '<span class=json-value>';\n                    var str = '<span class=json-string>';\n                    var r = pIndent || '';\n                    if (pKey)\n                        r = r + key + pKey.replace(/[\": ]/g, '') + '\"</span>: ';\n                    if (pVal)\n                        r = r + (pVal[0] == '\"' ? str : val) + pVal + '</span>';\n                        return r + (pEnd || '');\n                    },\n                    prettyPrint: function(obj) {\n                        var jsonLine = /^( *)(\"[\\w]+\": )?(\"[^\"]*\"|[\\w.+-]*)?([,[{])?$/mg;\n                        return JSON.stringify(obj, null, 4)\n                            .replace(/&/g, '&amp;').replace(/\\\\\"/g, '&quot;')\n                            .replace(/</g, '&lt;').replace(/>/g, '&gt;')\n                            .replace(jsonLine, library.json.replacer);\n                    }\n                    \n                };\n        function pause() {\n            var element_paused = document.getElementById(\"paused\");\n            paused = (element_paused.innerText === \"Resume\") ? \"Pause\" : \"Resume\";\n            element_paused.innerText = paused;\n        }\n    \n    </script>\n    </head>\n    <body onload=\"wsConnect();\" onunload=\"ws.disconnect();\">\n        <font face=\"Arial\">\n        <h1>Debug Messages <button id=\"paused\" value=\"pause\" onclick=\"pause()\">Pause</button> </h1>\n        <div id=\"json_data\"></div>\n        <hr/>\n        <div id=\"status\">unknown</div>\n        </font>\n    </body>\n</html>\n",
        "x": 550,
        "y": 40,
        "wires": [
            [
                "42a28745.bd5d78"
            ]
        ]
    },
    {
        "id": "42a28745.bd5d78",
        "type": "http response",
        "z": "3decb5f518882686",
        "name": "",
        "x": 730,
        "y": 40,
        "wires": []
    },
    {
        "id": "985ecbc7.67a138",
        "type": "websocket-listener",
        "z": "37f1be47da815ae1",
        "path": "/ws/debug",
        "wholemsg": "false"
    },
    {
        "id": "37f1be47da815ae1",
        "type": "tab",
        "label": "Modbus Server",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "49c18e3ad19acbda",
        "type": "group",
        "z": "37f1be47da815ae1",
        "style": {
            "stroke": "#999999",
            "stroke-opacity": "1",
            "fill": "none",
            "fill-opacity": "1",
            "label": true,
            "label-position": "nw",
            "color": "#a4a4a4"
        },
        "nodes": [
            "c5a0fdf6b8bb9850",
            "ed36434616147015",
            "248c46681d071246",
            "62340306b6eaf6f0",
            "f5457920917eb0f0",
            "8512686ef7c21c3e",
            "4cefa7be49623107",
            "c396898e828dee2a",
            "85ebfdbffa8d984b",
            "8916057c5b4e9270",
            "5d59c82badda5237",
            "d04855631aecc823",
            "3acabffc83517f90",
            "134c7774ebcf3011",
            "4486d6e85a7d3e1b",
            "889f040436167658",
            "99eb4635c1919a85",
            "b3fc5514304b5242",
            "a28bbddf98fa5939",
            "290109b199a881e3",
            "4f9540eaeb13509a",
            "ad73abc4d0bad61d",
            "f4d3a0e92cc61648",
            "509b5b5744b44bea"
        ],
        "x": 14,
        "y": 159,
        "w": 1932,
        "h": 444.5
    },
    {
        "id": "5d59c82badda5237",
        "type": "modbus-server",
        "z": "37f1be47da815ae1",
        "g": "49c18e3ad19acbda",
        "name": "",
        "logEnabled": true,
        "hostname": "0.0.0.0",
        "serverPort": 10502,
        "responseDelay": 100,
        "delayUnit": "ms",
        "coilsBufferSize": "6",
        "holdingBufferSize": "6",
        "inputBufferSize": "6",
        "discreteBufferSize": "6",
        "showErrors": true,
        "x": 1560,
        "y": 540,
        "wires": [
            [
                "d04855631aecc823",
                "3acabffc83517f90"
            ],
            [],
            [],
            [],
            []
        ]
    },
    {
        "id": "d04855631aecc823",
        "type": "debug",
        "z": "37f1be47da815ae1",
        "g": "49c18e3ad19acbda",
        "name": "Server output",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 1820,
        "y": 440,
        "wires": []
    },
    {
        "id": "f5457920917eb0f0",
        "type": "switch",
        "z": "37f1be47da815ae1",
        "g": "49c18e3ad19acbda",
        "name": "",
        "property": "topic",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "gboiler_running_condition",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "gsilo_level_0_1000",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "gmax_stoker_speed",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "gpid_stoker_output",
                "vt": "str"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 4,
        "x": 1030,
        "y": 340,
        "wires": [
            [
                "c396898e828dee2a"
            ],
            [
                "4cefa7be49623107"
            ],
            [
                "8512686ef7c21c3e"
            ],
            [
                "62340306b6eaf6f0"
            ]
        ]
    },
    {
        "id": "b3fc5514304b5242",
        "type": "comment",
        "z": "37f1be47da815ae1",
        "g": "49c18e3ad19acbda",
        "name": "Boiler Heat Meter kW & mWh [req. Serial RS485]",
        "info": "",
        "x": 240,
        "y": 260,
        "wires": []
    },
    {
        "id": "290109b199a881e3",
        "type": "inject",
        "z": "37f1be47da815ae1",
        "g": "49c18e3ad19acbda",
        "name": "Get Silo Level",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "5",
        "crontab": "",
        "once": true,
        "onceDelay": "1",
        "topic": "gsilo_level_0_1000",
        "payload": "",
        "payloadType": "date",
        "x": 180,
        "y": 320,
        "wires": [
            [
                "889f040436167658"
            ]
        ]
    },
    {
        "id": "889f040436167658",
        "type": "link out",
        "z": "37f1be47da815ae1",
        "g": "49c18e3ad19acbda",
        "name": "Sensors out",
        "mode": "link",
        "links": [
            "37db5198429b7396"
        ],
        "x": 445,
        "y": 340,
        "wires": []
    },
    {
        "id": "99eb4635c1919a85",
        "type": "link in",
        "z": "37f1be47da815ae1",
        "g": "49c18e3ad19acbda",
        "name": "Processor in",
        "links": [
            "da25e4e66d04561c"
        ],
        "x": 745,
        "y": 340,
        "wires": [
            [
                "134c7774ebcf3011"
            ]
        ]
    },
    {
        "id": "134c7774ebcf3011",
        "type": "modbus-flex-getter",
        "z": "37f1be47da815ae1",
        "g": "49c18e3ad19acbda",
        "name": "Boiler_getter",
        "showStatusActivities": false,
        "showErrors": false,
        "showWarnings": true,
        "logIOActivities": false,
        "server": "d8b07ddb.c44d4",
        "useIOFile": false,
        "ioFile": "",
        "useIOForPayload": false,
        "emptyMsgOnFail": false,
        "keepMsgProperties": false,
        "delayOnStart": false,
        "startDelayTime": "",
        "x": 870,
        "y": 340,
        "wires": [
            [
                "f5457920917eb0f0"
            ],
            []
        ]
    },
    {
        "id": "4486d6e85a7d3e1b",
        "type": "comment",
        "z": "37f1be47da815ae1",
        "g": "49c18e3ad19acbda",
        "name": "<-- I/O through lvc_labels -->",
        "info": "",
        "x": 600,
        "y": 340,
        "wires": []
    },
    {
        "id": "85ebfdbffa8d984b",
        "type": "debug",
        "z": "37f1be47da815ae1",
        "g": "49c18e3ad19acbda",
        "name": "debug 10",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 1520,
        "y": 280,
        "wires": []
    },
    {
        "id": "a28bbddf98fa5939",
        "type": "inject",
        "z": "37f1be47da815ae1",
        "g": "49c18e3ad19acbda",
        "name": "Shutdown/Fireout",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "5",
        "crontab": "",
        "once": true,
        "onceDelay": "1",
        "topic": "gboiler_running_condition",
        "payload": "",
        "payloadType": "date",
        "x": 190,
        "y": 200,
        "wires": [
            [
                "889f040436167658"
            ]
        ]
    },
    {
        "id": "4f9540eaeb13509a",
        "type": "inject",
        "z": "37f1be47da815ae1",
        "g": "49c18e3ad19acbda",
        "name": "Stoker at maximum speed",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "5",
        "crontab": "",
        "once": true,
        "onceDelay": "1",
        "topic": "gmax_stoker_speed",
        "payload": "",
        "payloadType": "date",
        "x": 200,
        "y": 440,
        "wires": [
            [
                "889f040436167658"
            ]
        ]
    },
    {
        "id": "ad73abc4d0bad61d",
        "type": "inject",
        "z": "37f1be47da815ae1",
        "g": "49c18e3ad19acbda",
        "name": "Averaged stoker speed",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "5",
        "crontab": "",
        "once": true,
        "onceDelay": "1",
        "topic": "gpid_stoker_output",
        "payload": "",
        "payloadType": "date",
        "x": 190,
        "y": 520,
        "wires": [
            [
                "889f040436167658"
            ]
        ]
    },
    {
        "id": "c396898e828dee2a",
        "type": "function",
        "z": "37f1be47da815ae1",
        "g": "49c18e3ad19acbda",
        "name": "Shutdown/Fireout scale",
        "func": "// This node caused \"TypeError: Cannot read properties of undefined (reading 'length')\"\n// Fixed: https://discourse.nodered.org/t/is-not-a-valid-memory-write-message-to-server/77977/7?u=darren\n\nmsg.payload = { \n    'value': Number(msg.payload[0]),\n    'register': 'holding',\n    'address': 1,\n    'disableMsgOutput': 0 };\n   \nreturn msg;\n\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1250,
        "y": 280,
        "wires": [
            [
                "85ebfdbffa8d984b",
                "5d59c82badda5237"
            ]
        ]
    },
    {
        "id": "4cefa7be49623107",
        "type": "function",
        "z": "37f1be47da815ae1",
        "g": "49c18e3ad19acbda",
        "name": "Silo Level scale",
        "func": "msg.payload = {\n    'value': msg.payload,\n    'register': 'holding',\n    'address': 2,\n    'disableMsgOutput': 0\n};\n\n// debug\n//msg.payload = {\n//    'value': 1337, 'register': 'holding',\n//    'address': 2, 'disableMsgOutput': 0\n//};\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1220,
        "y": 320,
        "wires": [
            [
                "8916057c5b4e9270",
                "5d59c82badda5237"
            ]
        ]
    },
    {
        "id": "8512686ef7c21c3e",
        "type": "function",
        "z": "37f1be47da815ae1",
        "g": "49c18e3ad19acbda",
        "name": "Maximum stoker speed scale",
        "func": "msg.payload = {\n    'value': msg.payload,\n    'register': 'holding',\n    'address': 3,\n    'disableMsgOutput': 0\n};\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1260,
        "y": 360,
        "wires": [
            [
                "ed36434616147015",
                "5d59c82badda5237"
            ]
        ]
    },
    {
        "id": "62340306b6eaf6f0",
        "type": "function",
        "z": "37f1be47da815ae1",
        "g": "49c18e3ad19acbda",
        "name": "Averaged stoker speed scale",
        "func": "msg.payload = {\n    'value': msg.payload,\n    'register': 'holding',\n    'address': 4,\n    'disableMsgOutput': 0\n};\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 1260,
        "y": 400,
        "wires": [
            [
                "248c46681d071246",
                "5d59c82badda5237"
            ]
        ]
    },
    {
        "id": "c5a0fdf6b8bb9850",
        "type": "comment",
        "z": "37f1be47da815ae1",
        "g": "49c18e3ad19acbda",
        "name": "[Unavailable] Stoker at minimum speed",
        "info": "",
        "x": 190,
        "y": 380,
        "wires": []
    },
    {
        "id": "8916057c5b4e9270",
        "type": "debug",
        "z": "37f1be47da815ae1",
        "g": "49c18e3ad19acbda",
        "name": "debug 11",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "payload",
        "targetType": "msg",
        "statusVal": "",
        "statusType": "auto",
        "x": 1520,
        "y": 320,
        "wires": []
    },
    {
        "id": "ed36434616147015",
        "type": "debug",
        "z": "37f1be47da815ae1",
        "g": "49c18e3ad19acbda",
        "name": "debug 12",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 1500,
        "y": 360,
        "wires": []
    },
    {
        "id": "248c46681d071246",
        "type": "debug",
        "z": "37f1be47da815ae1",
        "g": "49c18e3ad19acbda",
        "name": "debug 13",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 1500,
        "y": 400,
        "wires": []
    },
    {
        "id": "3acabffc83517f90",
        "type": "subflow:3decb5f518882686",
        "z": "37f1be47da815ae1",
        "g": "49c18e3ad19acbda",
        "name": "",
        "x": 1830,
        "y": 540,
        "wires": [
            []
        ]
    },
    {
        "id": "c1f1279b6bad34ee",
        "type": "comment",
        "z": "37f1be47da815ae1",
        "name": "Brown & Carroll Modbus re-server",
        "info": "",
        "x": 920,
        "y": 100,
        "wires": []
    },
    {
        "id": "f4d3a0e92cc61648",
        "type": "modbus-write",
        "z": "37f1be47da815ae1",
        "g": "49c18e3ad19acbda",
        "name": "",
        "showStatusActivities": false,
        "showErrors": false,
        "showWarnings": true,
        "unitid": "1",
        "dataType": "HoldingRegister",
        "adr": "60",
        "quantity": "1",
        "server": "06fb380adab7a3ac",
        "emptyMsgOnFail": false,
        "keepMsgProperties": false,
        "delayOnStart": false,
        "startDelayTime": "",
        "x": 1160,
        "y": 520,
        "wires": [
            [],
            []
        ]
    },
    {
        "id": "509b5b5744b44bea",
        "type": "inject",
        "z": "37f1be47da815ae1",
        "g": "49c18e3ad19acbda",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "69",
        "payloadType": "num",
        "x": 970,
        "y": 480,
        "wires": [
            [
                "f4d3a0e92cc61648"
            ]
        ]
    },
    {
        "id": "d8b07ddb.c44d4",
        "type": "modbus-client",
        "name": "Boiler_PLC",
        "clienttype": "tcp",
        "bufferCommands": true,
        "stateLogEnabled": false,
        "queueLogEnabled": false,
        "failureLogEnabled": false,
        "tcpHost": "192.168.127.2",
        "tcpPort": "502",
        "tcpType": "DEFAULT",
        "serialPort": "/dev/ttyUSB",
        "serialType": "RTU-BUFFERD",
        "serialBaudrate": "9600",
        "serialDatabits": "8",
        "serialStopbits": "1",
        "serialParity": "none",
        "serialConnectionDelay": "100",
        "serialAsciiResponseStartDelimiter": "",
        "unit_id": "1",
        "commandDelay": "1",
        "clientTimeout": "1000",
        "reconnectOnTimeout": true,
        "reconnectTimeout": "2000",
        "parallelUnitIdsAllowed": true
    },
    {
        "id": "06fb380adab7a3ac",
        "type": "modbus-client",
        "name": "nodered_local_server",
        "clienttype": "tcp",
        "bufferCommands": true,
        "stateLogEnabled": false,
        "queueLogEnabled": false,
        "failureLogEnabled": true,
        "tcpHost": "127.0.0.1",
        "tcpPort": "10502",
        "tcpType": "DEFAULT",
        "serialPort": "/dev/ttyUSB",
        "serialType": "RTU-BUFFERD",
        "serialBaudrate": "9600",
        "serialDatabits": "8",
        "serialStopbits": "1",
        "serialParity": "none",
        "serialConnectionDelay": "100",
        "serialAsciiResponseStartDelimiter": "0x3A",
        "unit_id": "1",
        "commandDelay": "1",
        "clientTimeout": "1000",
        "reconnectOnTimeout": true,
        "reconnectTimeout": "2000",
        "parallelUnitIdsAllowed": true,
        "showWarnings": true,
        "showLogs": true
    }
]

Master (client) flow:

[
    {
        "id": "64c7eb63152d8786",
        "type": "tab",
        "label": "localhost_ModBus_reserver_read",
        "disabled": false,
        "info": "",
        "env": []
    },
    {
        "id": "0b48912e93227892",
        "type": "inject",
        "z": "64c7eb63152d8786",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 300,
        "y": 300,
        "wires": [
            [
                "7f3e59be1ede9951"
            ]
        ]
    },
    {
        "id": "7f3e59be1ede9951",
        "type": "function",
        "z": "64c7eb63152d8786",
        "name": "FC3",
        "func": "msg.payload = { 'fc': 3, 'unitid': 1, 'address': 1 , 'quantity': 20 } //23 is the largest supported\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 450,
        "y": 300,
        "wires": [
            [
                "dd73f6005ec5f9c7"
            ]
        ]
    },
    {
        "id": "dd73f6005ec5f9c7",
        "type": "modbus-flex-getter",
        "z": "64c7eb63152d8786",
        "name": "Modbus Flexible Read",
        "showStatusActivities": false,
        "showErrors": true,
        "showWarnings": true,
        "logIOActivities": false,
        "server": "d8b07ddb.c44d4",
        "useIOFile": false,
        "ioFile": "",
        "useIOForPayload": false,
        "emptyMsgOnFail": false,
        "keepMsgProperties": false,
        "delayOnStart": false,
        "startDelayTime": "",
        "x": 680,
        "y": 280,
        "wires": [
            [
                "b2d46b051fed2505"
            ],
            [
                "c8053c17c0e6e5f3"
            ]
        ]
    },
    {
        "id": "c8053c17c0e6e5f3",
        "type": "debug",
        "z": "64c7eb63152d8786",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 910,
        "y": 260,
        "wires": []
    },
    {
        "id": "9de184514db1a859",
        "type": "comment",
        "z": "64c7eb63152d8786",
        "name": "Notes on why the numbers are funky",
        "info": "https://discourse.nodered.org/t/scaled-value-while-reading-modbus/75871/4",
        "x": 420,
        "y": 460,
        "wires": []
    },
    {
        "id": "2a6cb7b3a3026a0a",
        "type": "modbus-response",
        "z": "64c7eb63152d8786",
        "name": "",
        "registerShowMax": 20,
        "x": 770,
        "y": 420,
        "wires": []
    },
    {
        "id": "4ae24ad69841cc79",
        "type": "modbus-read",
        "z": "64c7eb63152d8786",
        "name": "",
        "topic": "",
        "showStatusActivities": false,
        "logIOActivities": false,
        "showErrors": false,
        "showWarnings": true,
        "unitid": "",
        "dataType": "HoldingRegister",
        "adr": "50",
        "quantity": "20",
        "rate": "5",
        "rateUnit": "s",
        "delayOnStart": false,
        "startDelayTime": "",
        "server": "d8b07ddb.c44d4",
        "useIOFile": false,
        "ioFile": "",
        "useIOForPayload": false,
        "emptyMsgOnFail": false,
        "x": 510,
        "y": 420,
        "wires": [
            [
                "2a6cb7b3a3026a0a"
            ],
            []
        ]
    },
    {
        "id": "b2d46b051fed2505",
        "type": "function",
        "z": "64c7eb63152d8786",
        "name": "function 1",
        "func": "var i = 23;\nmsg.payload[i] = (msg.payload[i] << 16) >> 16\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 880,
        "y": 320,
        "wires": [
            [
                "d52f0a0f6eaa30a4"
            ]
        ]
    },
    {
        "id": "d52f0a0f6eaa30a4",
        "type": "debug",
        "z": "64c7eb63152d8786",
        "name": "debug 4",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 1060,
        "y": 340,
        "wires": []
    },
    {
        "id": "d8b07ddb.c44d4",
        "type": "modbus-client",
        "name": "Boiler_PLC",
        "clienttype": "tcp",
        "bufferCommands": true,
        "stateLogEnabled": false,
        "queueLogEnabled": false,
        "failureLogEnabled": false,
        "tcpHost": "10.91.1.166",
        "tcpPort": "10502",
        "tcpType": "DEFAULT",
        "serialPort": "/dev/ttyUSB",
        "serialType": "RTU-BUFFERD",
        "serialBaudrate": "9600",
        "serialDatabits": "8",
        "serialStopbits": "1",
        "serialParity": "none",
        "serialConnectionDelay": "100",
        "serialAsciiResponseStartDelimiter": "",
        "unit_id": "1",
        "commandDelay": "1",
        "clientTimeout": "1000",
        "reconnectOnTimeout": true,
        "reconnectTimeout": "2000",
        "parallelUnitIdsAllowed": true
    }
]

Update: My friend on the other end is now getting "real numbers", but they still seem to be out of range:

One is interpreting it as signed and the other as unsigned

:sweat_smile: both of those last were the output on my friend's end, he just switched views. My numbers are more along the lines of:

[0,0,0,4,0,0,0,2560,0,0,0,38912,0,0,0,38912,0,0,0,0,null,null,null,0]

i.e.

register my number (broadcast) my number (received) his number (received, unsigned) his number (received, signed)
D401 4 4 63,099 -2437
D402 778 2560 13,791 13,791
D403 2200 38,912 13,101 13,101
D404 2200 38,912 38,678 -26,858

(NB. his numbers are not from the same time as mine, so they are not equivalent, but indicative of range)

Not sure what is the scaling going on. I can force broadcast arbitrary numbers e.g. 1337420 and will receive exactly that number my end, looks like the scaling only happens to (for want of a better term) legit modbus signals.

Is there any visible difference, e.g. is there some sort of bitwise operation that I don't know about that happens with Modbus signals?

I don't understand what you mean by your friends numbers and your numbers. Are you both accessing the same server in the same way?

Same server is being accessed (code above).

I am accessing it via node-red installed on my machine (flow above in "Master"); my friend is using some proprietary software, but we're both getting different numbers to what is broadcast.

Physically the same machine, or the same code on a different machine.

Node-red Modbus Server is running on physical machine A and rebroadcasting signals received from other devices on LAN

My node-red Modbus Client is running on a separate physical machine B, connected via VPN to the same LAN as machine A

My co-worker's Modbus Client is not node-red and I believe running on a PLC (physical machine C); he obtains readings via proprietary software (GXWorks or similar?). It is on the same physical LAN as machine A.

Physical machines B & C are both reading from server on A. I can draw a network diagram if needed.

To investigate why the two are different, examine the raw data returned by the modbus requests. That may give a clue as to why they are both wrong.

Isn't raw data what is returned by the modbus read nodes? e.g. Inject --> modbus flex getter --> log. I was under the impression that was it...

Or is there a particulare software you could recommend to read the raw data?

Yes, so you need to compare that with the data your co-worker gets, as he is getting different data. The question is, is it the interpretation of the raw data or the raw data itself that is different.

Weirdest thing is that my raw data read differs from my raw data broadcast. e.g. debug log says I'm broadcasting ABCD, but I receive AXYZ, unless I explicitly force broadcast xOxx or xxOx etc. , then I receive xOxx etc. I'd be more confident in telling him his software is reading wrong if I was sure mine was reading write haha

Wondering if modbus broadcast is somehow overwriting itself...

EDIT: bad example. Here's a better one:

(Measured just now)

Address Broadcast data Received data Notes
D401 4 4 Seems to receive correctly
D402 778 2560 Later, 799 gives 6912
D403 2200 38912
D404 2200 38912

If I force broadcast an arbitrary number to any of D401, D402, D403, D404, I will receive that number, without any weird scaling factor or whatever is going on. NB readings taken straight from endpoint debug log nodes' output to the log. But any "naturally read" number is scaled weirdly (looks like on the receiving end? or in the aether in between)

Sorry, I have no idea what you mean by that.

Thank you for your patience and help, Colin!

I just posted a better example. I was wondering from a couple other threads I saw, those numbers don't look like some sort of known weird endian or bitwise shenanigans going on to you, do they?

Here's another log, straight from the server:

Address Server output
D401 0x4
D402 0x1e
D403 0x98
D404 0x98

Seems to be in HEX, I wonder if something's getting misinterpreted...

Further weirdness: as a test, I'm writing to register 8 on the server, but reading back, register 8's value on the server comes in at 31 in the array on the client. Does this make sense to anyone? EDIT: Nevermind, that makes sense now. Every 4 slots in the array is used. It fits the pattern.