So far, from the documentation, 2 words of 16 bits are used for a total of 32-bits are used to hold single-precision floating point numbers.
That is as expected.
BUT that does not mean whoever programmed the PLC used 32 bit data. They might have packed the float into an INT (i.e. it is possible and common for a PLC programmer to store 0.123
as 123
in a WD)
Assuming for a second you actually did a cross-reference on the PLC for this data & assuming you found they were being written to PLC memory via 32 bit functions (e.g. FMOV (Floating Point Move), FMUL (Floating Point Multiply):, DMOV (Double Word Move) etc) then you need to adjust your ModBus reads as you are only reading / providing 1 WD of data in your example...
Modbus values are 16 bit. [41496] is 1 WD (2 bytes) - i.e. not enough data to build a Float value.
Your ModBus requests, for float values SHOULD be set to a length of 2.
Remember, 2 UINT16s (aka 2 WORD, aka 4 bytes, aka 32 bits)
In short, your problems begin at the fact you are attempting to compute a float value with only half of the data.
Currently I'm trying to follow the following guides to read 32bit values:
We could do some multiplication PLC-side, but would prefer to avoid reprogramming it.
EDIT: I'm now using the following flow:
[
{
"id": "5e0f1cacf0774214",
"type": "inject",
"z": "85dc5731.881ef8",
"name": "gfuel_flow_rate",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "10",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "gfuel_flow_rate",
"payload": "",
"payloadType": "date",
"x": 360,
"y": 620,
"wires": [
[
"fb2ca1bab7230577",
"1682d736eb4b5782",
"894ca54d5d0e3e64"
]
]
},
{
"id": "1682d736eb4b5782",
"type": "debug",
"z": "85dc5731.881ef8",
"name": "debug 8",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 560,
"y": 620,
"wires": []
},
{
"id": "894ca54d5d0e3e64",
"type": "function",
"z": "85dc5731.881ef8",
"name": "Input 32bit float",
"func": "//src: https://www.youtube.com/watch?v=YAvSJrp4lq0\nvar fc = 3;\nvar sa = 106;\nvar addresses = 2;\nvar slave_ip = msg.payload.slave_ip;\n//msg.slave_ip = \"192.168.1.31\";\nmsg.payload = { value: msg.payload, 'fc': fc, 'unitid': 1, 'address': sa, 'quantity': addresses };\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 680,
"y": 560,
"wires": [
[
"fd8499e0ba5275c3"
]
]
},
{
"id": "fd8499e0ba5275c3",
"type": "modbus-flex-getter",
"z": "85dc5731.881ef8",
"name": "",
"showStatusActivities": false,
"showErrors": false,
"showWarnings": true,
"logIOActivities": false,
"server": "d8b07ddb.c44d4",
"useIOFile": false,
"ioFile": "",
"useIOForPayload": false,
"emptyMsgOnFail": false,
"keepMsgProperties": false,
"delayOnStart": false,
"startDelayTime": "",
"x": 850,
"y": 660,
"wires": [
[],
[
"3df29f7242fe316c"
]
]
},
{
"id": "3df29f7242fe316c",
"type": "function",
"z": "85dc5731.881ef8",
"name": "Get 32bit float",
"func": "// src: https://stevesnoderedguide.com/node-red-modbus\nconst buf = Buffer.from(msg.payload.buffer);\nconst value = buf.readFloatLE();\nmsg.value = value;\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1020,
"y": 580,
"wires": [
[
"a7cca763971d2b3d"
]
]
},
{
"id": "a7cca763971d2b3d",
"type": "debug",
"z": "85dc5731.881ef8",
"name": "debug 12",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 1200,
"y": 580,
"wires": []
},
{
"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
}
]
to get data that looks like this:
{"data":[54006,15745],"buffer":[210,246,61,129]}
Hold on. There is no need for any PLC re-programming
Just answer the question: Does the CURRENT PLC Ladder use 16 bit or 32 bit functions.
If 16 bit, OK, no problem.
If the PLC uses 32 bit values, you need to read a quantity of 2
from ModBus for each 32 bit value - otherwise, we are missing IMPORTANT parts of the data and we CANNOT compute the floating point value without all of the data!
As I have stated several times: ModBus reads in UINT16. But that does NOT mean you have to re-program the PLC. Just read MORE data from ModBus to get 2 UINT16s for every FLOAT. Then use buffer parser to convert the 2 x UINT16 back into a FLOAT or UINT32 or whatever it needs to be!
What part of this is not clear Darren? Please, tell me so I can improve my communication skills.
Lets try to be ultra clear and precise...
If your PLC has a FLOAT value and it is stored in 2 WORDS (i.e. 32 bit) in PLC memory, then you MUST read both WORDS (quantity 2) from ModBus to get all of the necessary components of data so that you can re-create the Float.
What functions are used in the PLC to generate the numbers you are trying to read? Are the double WORD instructions like FMOV, FDIV, DMOV etc. OR. single WORD instructions e.g. DIV, MUL, MOV?
In short...
Where you said
I expect to see 4 bytes for every FLOAT
Expected value | Modbus |
---|---|
0.165 | {data:[41496, ???],buffer:[162,24, ???, ???]} |
0.229 | {data:[32108, ???],buffer:[125,108, ???, ???]} |
this assumes your PLC is using 2 WORD/32 bit values - but you still have NOT confirmed this
ok, I see you have now edited your post and are now getting 2 WORDS / 32 bit data
Good.
So what should that equal? Remember, when I asked for data I also asked for what it should be.
Almost there
Here's new data (I'll have to post more next week, as the system is shutting down for the weekend):
Expected | Modbus |
---|---|
0.024 | {"data":[33154,15553],"buffer":[129,130,60,193]} |
I've been distracted with other work; finally had a chance to get some more data!
Expected | Modbus |
---|---|
0.150 | {"data":[19850,15897],"buffer":[77,138,62,25]} |
0.228 | {"data":[7247,15977],"buffer":[28,79,62,105]} |
0.183 | {"data":[53360,15931],"buffer":[208,112,62,59]} |
0.137 | {"data":[2037,15884],"buffer":[7,245,62,12]} |
0.193 | {"data":[42589,15941],"buffer":[166,93,62,69]} |
0.143 | {"data":[63073,15889],"buffer":[246,97,62,17]} |
0.149 | {"data":[455,15897],"buffer":[1,199,62,25]} |
0.229 | {"data":[32108,15978],"buffer":[125,108,62,106]} |
@Steve-Mcl Do those numbers look ok? I'm experimenting once again with variations in the buffer-parser, hopefully 32bit will produce some realistic numbers.
It has been too long for me to parse this all back into my head.
To give me a leg up, please update that table with register numbers and modbus unit numbers
e.g.
expected | node-red data | register | unit id |
---|---|---|---|
0.150 | {"data":[19850,15897],"buffer":[77,138,62,25]} | 1 & 2 | 0 |
0.228 | {"data":[7247,15977],"buffer":[28,79,62,105]} | 3 & 4 | 0 |
0.183 | {data:[53360,15931],buffer:[208,112,62,59]} | 5 & 6 | etc |
0.137 | {data:[2037,15884],buffer:[7,245,62,12]} | etc | etc |
0.193 | {data:[42589,15941],buffer:[166,93,62,69]} | etc | etc |
0.143 | {data:[63073,15889],buffer:[246,97,62,17]} | etc | etc |
0.149 | {data:[455,15897],buffer:[1,199,62,25]} | etc | etc |
0.229 | {data:[32108,15978],buffer:[125,108,62,106]} | etc | etc |
WIP:
Expected | Modbus | Register | Unit ID |
---|---|---|---|
0.024 | {"data":[33154,15553],"buffer":[129,130,60,193]} | 106 | 1 |
0.150 | {"data":[19850,15897],"buffer":[77,138,62,25]} | 106 | 1 |
0.228 | {"data":[7247,15977],"buffer":[28,79,62,105]} | 106 | 1 |
0.183 | {"data":[53360,15931],"buffer":[208,112,62,59]} | 106 | 1 |
0.137 | {"data":[2037,15884],"buffer":[7,245,62,12]} | 106 | 1 |
0.193 | {"data":[42589,15941],"buffer":[166,93,62,69]} | 106 | 1 |
0.143 | {"data":[63073,15889],"buffer":[246,97,62,17]} | 106 | 1 |
0.149 | {"data":[455,15897],"buffer":[1,199,62,25]} | 106 | 1 |
0.229 | {"data":[32108,15978],"buffer":[125,108,62,106]} | 106 | 1 |
Unless I've become completely confused, it looks like it's the same register and unit ID for them all:
Code:
Input 32bit float
//src: https://www.youtube.com/watch?v=YAvSJrp4lq0
var fc = 3;
var sa = 106;
var addresses = 2;
var slave_ip = msg.payload.slave_ip;
//msg.slave_ip = "192.168.1.31";
msg.payload = { value: msg.payload, 'fc': fc, 'unitid': 1, 'address': sa, 'quantity': addresses };
return msg;
Modbus Flex Getter
standard, connection to PLC
Get 32bit float
// src: https://stevesnoderedguide.com/node-red-modbus
const buf = Buffer.from(msg.payload.buffer);
const value = buf.readFloatLE();
msg.value = value;
return msg;
Thank you for your help, Steve!!!
I cannot make head nor tail of your data. There is absolutely no pattern whatsoever. Its like the first sample value is an LE (little endian) number, the 2nd is a BE (big endian) number and the third makes no sense whatsoever.
Looking at your flow screenshot - I suspect there is some shenanigans going on BEFORE your debug node - when I asked for the output of the Modbus node mean DIRECTLY (untouched, unspoiled)
Either that or Register 106
is not lined up to what you think it is in the PLC
Can you grab the data once more (directly from Modbus as shown above) and list a few samples with "expect value" & "Node-RED debug value" please?
Question: Since this is Mitsubishi, cant you use the MC-Protocol nodes instead? Much easier and you dont have the whole address remapping from Modbus address space to Mitsubishi native address space to deal with!
Direct readings (Register D106, Unit ID: 1):
Expected | Direct Modbus |
---|---|
0.166 | {"data":[29258,15914],"buffer":[114,74,62,42]} |
0.165 | {"data":[41496,15912],"buffer":[162,24,62,40]} |
0.182 | {"data":[32012,15930],"buffer":[125,12,62,58]} |
0.229 | {"data":[65061,15912],"buffer":[254,37,62,40]} |
0.141 | {"data":[24066,15888],"buffer":[94,2,62,16]} |
0.153 | {"data":[41652,15900],"buffer":[162,180,62,28]} |
0.230 | {"data":[12173,15979],"buffer":[47,141,62,107]} |
Again, those number vary so wildly, there is something wrong.
Perhaps REG 106 is NOT pointing to the address in the PLC you think it is.
Perhaps, you have an "off by one" error (e.g. you should be reading REG 105)
- Show me the actual D values in the PLC memory view.
- Show me the mapping setup for Modbus (i.e. how did you come to Modbus REG 106? What do you think it points to in the PLC?)
- Answer my QUESTION from previous post
- The strange thing is, we always get the same garbage per sensible number; I have observed them to match up (any errors would be down to human reaction times copying the log). I cross-referenced the values, and direct from the Modbus Flex Getter or after "Get 32bit float" values are the same, i.e. both previous tables have equally valid data.
We get the PLC memory address D106 from XML exported from GXWorks3:
<variable name="gfuel_flow_rate" address="D106">
<type>
<REAL />
</type>
<addData>
<data name="http://www.mitsubishielectric.com/xml/variableLineNumber" handleUnknown="implementation">
<variableLineNumber number="3" />
</data>
</addData>
</variable>
- Regarding MC-Protocol, we typically prefer Modbus for code portability. We export an XML from the GXWorks3 program of all the device labels, which we then push through a Modbus getters to use to filter the connected devices into individual dials and grab the data. It works for 99% of the other sensors, even this particular type of rotation sensor with exactly the same program elsewhere. We also sometimes use cheaper non-Mitsubishi PLCs or need to communicate with 3rd party equipment, for which we're obliged to use Modbus. I will give the MC-Protocol nodes a go, hopefully won't have to reprogram the PLC too much for that.
Solution
PS. I got it working - after comparing with another working legacy program we had, turns out I was trying to read a data array when all I needed was the second number from data
, e.g. for {"data":[53360,15931],"buffer":[208,112,62,59]}
, all I needed was 15931
and put it through buffer parser
to read msg.payload.buffer
, byte swap 16, float LE, and I get sensible numbers that match up with GXWorks3
Flow:
[
{
"id": "5e0f1cacf0774214",
"type": "inject",
"z": "85dc5731.881ef8",
"name": "gfuel_flow_rate",
"props": [
{
"p": "payload"
},
{
"p": "topic",
"vt": "str"
}
],
"repeat": "10",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "gfuel_flow_rate",
"payload": "",
"payloadType": "date",
"x": 360,
"y": 620,
"wires": [
[
"fb2ca1bab7230577",
"1682d736eb4b5782",
"894ca54d5d0e3e64"
]
]
},
{
"id": "1682d736eb4b5782",
"type": "debug",
"z": "85dc5731.881ef8",
"name": "debug 8",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 560,
"y": 620,
"wires": []
},
{
"id": "894ca54d5d0e3e64",
"type": "function",
"z": "85dc5731.881ef8",
"name": "Input 32bit float",
"func": "//src: https://www.youtube.com/watch?v=YAvSJrp4lq0\nvar fc = 3;\nvar sa = 106;\nvar addresses = 2;\nvar slave_ip = msg.payload.slave_ip;\n//msg.slave_ip = \"192.168.1.31\";\nmsg.payload = { value: msg.payload, 'fc': fc, 'unitid': 1, 'address': sa, 'quantity': addresses };\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 680,
"y": 560,
"wires": [
[
"fd8499e0ba5275c3"
]
]
},
{
"id": "fd8499e0ba5275c3",
"type": "modbus-flex-getter",
"z": "85dc5731.881ef8",
"name": "",
"showStatusActivities": false,
"showErrors": false,
"showWarnings": true,
"logIOActivities": false,
"server": "d8b07ddb.c44d4",
"useIOFile": false,
"ioFile": "",
"useIOForPayload": false,
"emptyMsgOnFail": false,
"keepMsgProperties": false,
"delayOnStart": false,
"startDelayTime": "",
"x": 850,
"y": 660,
"wires": [
[],
[
"3df29f7242fe316c",
"298d7c567c6bad3f"
]
]
},
{
"id": "298d7c567c6bad3f",
"type": "debug",
"z": "85dc5731.881ef8",
"name": "debug 13",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 1080,
"y": 660,
"wires": []
},
{
"id": "3df29f7242fe316c",
"type": "function",
"z": "85dc5731.881ef8",
"name": "Get 32bit float",
"func": "// src: https://stevesnoderedguide.com/node-red-modbus\nconst buf = Buffer.from(msg.payload.buffer);\nconst value = buf.readFloatLE();\nmsg.value = value;\nmsg.value = msg.value[1];\n\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1020,
"y": 580,
"wires": [
[
"a7cca763971d2b3d",
"b251a56754b03ef8"
]
]
},
{
"id": "b251a56754b03ef8",
"type": "buffer-parser",
"z": "85dc5731.881ef8",
"name": "",
"data": "payload.buffer",
"dataType": "msg",
"specification": "spec",
"specificationType": "ui",
"items": [
{
"type": "floatle",
"name": "item1",
"offset": 0,
"length": 1,
"offsetbit": 0,
"scale": "1",
"mask": ""
}
],
"swap1": "swap16",
"swap2": "",
"swap3": "",
"swap1Type": "swap",
"swap2Type": "swap",
"swap3Type": "swap",
"msgProperty": "payload",
"msgPropertyType": "str",
"resultType": "keyvalue",
"resultTypeType": "return",
"multipleResult": false,
"fanOutMultipleResult": false,
"setTopic": true,
"outputs": 1,
"x": 1190,
"y": 420,
"wires": [
[
"992482cf6db63c1f",
"4845ee5fdce680bb"
]
]
},
{
"id": "992482cf6db63c1f",
"type": "function",
"z": "85dc5731.881ef8",
"name": "Strip Item",
"func": "var num = msg.payload[\"item1\"];\nmsg.payload = Math.round((num + Number.EPSILON) * 1000) / 1000\nreturn msg;",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 1460,
"y": 400,
"wires": [
[
"194804c392c89172",
"236ea1364a916407"
]
]
},
{
"id": "236ea1364a916407",
"type": "delay",
"z": "85dc5731.881ef8",
"name": "",
"pauseType": "timed",
"timeout": "5",
"timeoutUnits": "seconds",
"rate": "1",
"nbRateUnits": "5",
"rateUnits": "minute",
"randomFirst": "1",
"randomLast": "5",
"randomUnits": "seconds",
"drop": true,
"allowrate": false,
"outputs": 1,
"x": 1760,
"y": 440,
"wires": [
[
"a5e739635d2d4d37"
]
]
},
{
"id": "a5e739635d2d4d37",
"type": "ui_chart",
"z": "85dc5731.881ef8",
"name": "",
"group": "e331a34b.02e348",
"order": 2,
"width": 6,
"height": 4,
"label": "Fuel Flow Rate Trend",
"chartType": "line",
"legend": "false",
"xformat": "auto",
"interpolate": "linear",
"nodata": "",
"dot": false,
"ymin": "0",
"ymax": "2",
"removeOlder": 1,
"removeOlderPoints": "",
"removeOlderUnit": "604800",
"cutout": 0,
"useOneColor": false,
"useUTC": false,
"colors": [
"#1f77b4",
"#aec7e8",
"#ff7f0e",
"#2ca02c",
"#98df8a",
"#d62728",
"#ff9896",
"#9467bd",
"#c5b0d5"
],
"outputs": 1,
"useDifferentColor": false,
"className": "",
"x": 2011.375,
"y": 443.4285583496094,
"wires": [
[]
]
},
{
"id": "194804c392c89172",
"type": "ui_gauge",
"z": "85dc5731.881ef8",
"name": "",
"group": "e331a34b.02e348",
"order": 1,
"width": 0,
"height": 0,
"gtype": "gage",
"title": "Fuel Flow Rate - Stoker Sensor",
"label": "m3/hr",
"format": "{{value}}",
"min": 0,
"max": "2",
"colors": [
"#00b500",
"#e6e600",
"#ca3838"
],
"seg1": "",
"seg2": "",
"diff": false,
"className": "",
"x": 1690,
"y": 500,
"wires": []
},
{
"id": "4845ee5fdce680bb",
"type": "debug",
"z": "85dc5731.881ef8",
"name": "buffer parser output",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "payload",
"targetType": "msg",
"statusVal": "",
"statusType": "auto",
"x": 1310,
"y": 460,
"wires": []
},
{
"id": "a7cca763971d2b3d",
"type": "debug",
"z": "85dc5731.881ef8",
"name": "debug 12",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "false",
"statusVal": "",
"statusType": "auto",
"x": 1200,
"y": 580,
"wires": []
},
{
"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": "e331a34b.02e348",
"type": "ui_group",
"name": "Silo Status",
"tab": "64088923.99b638",
"order": 6,
"disp": true,
"width": "6",
"collapse": false,
"className": ""
},
{
"id": "64088923.99b638",
"type": "ui_tab",
"name": "Boiler Status",
"icon": "fa-bar-chart",
"order": 1,
"disabled": false,
"hidden": false
}
]
Thank you for your help, Steve!
This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.