Modbus 16bit INT to 32bit signed float for 50+ signals

I see there are plenty of discussions on this topic here in the forums, but since none of these directly answered my question and most of them are already closed, I thought of sharing how I did this case. At least I can look this up myself, next time I bang my head with Modbus...

The setup is like this:

  1. we have a Modbus Slave device that has 50+ tags in it
  2. all tags are to be treated as 32bit FLOAT values, and some of these are signed
  3. we want to process all these tags in one go with minimum amount of fuss and coding, so we get a neat list of floats

The solution
The solution to this problem is inspired by the Youtube video by @achmadinlabs2720 (Modbus RTU TCP/IP on Node-Red (16bit, 32bit and floating data) - YouTube) - Kudos to Achmad!

  1. First step is to read in the data from Modbus. In my case I always use this node and since I have more than 50 32bit tags, it means reading in 100+ registers. In my case the Modbus Slave device is unit ID = 1 and has a zero indexing type of regs, so we start from 0. FC3 is the Modbus function "Read holding registers".
msg.payload = {
    'fc': 3, 
    'unitid': 1,
    'address': 0,
    'quantity': 120
}; 
return msg;
  1. We feed the above into the mighty Modbus-Flex-Getter node (where we have separately configured the Modbus server)

  2. We grab the data from the Modbus-node, which in our case is 120 16bit integers

  3. Now the first challenge is how to get these 120 register values to 32bit and doing this in a fairly simple way and doing ALL tags in one function node. We do it like this, by shifting the first register of each pair by 16bits with the << operatore.

// put all data in our raw array
let raw = msg.payload;
// convert each pair to 32bit
// you may need to swap the order of the values, ie (raw[1] << 16) + (raw[0])
// instead of (raw[0] << 16) + (raw[1]), if the word order is swapped
let tag1 = (raw[0] << 16) + (raw[1]);
let tag2 = (raw[2] << 16) + (raw[3]);
let tag3 = (raw[4] << 16) + (raw[5]);
let tag4 = (raw[6] << 16) + (raw[7]);
let tag5 = (raw[8] << 16) + (raw[9]);
// you can add here as many register pairs as you like, in our case we have 56
let tag55 = (raw[108] << 16) + (raw[109]);
let tag56 = (raw[110] << 16) + (raw[111]);

// and lastly spit these out as a new array
// note the bracket [ ie we don't use a curly bracket { here, since we need an array
msg.payload = [   
    tag1,
    tag2,
    tag3,
    tag4,
    tag5,
    tag55,
    tag56
]
return msg;

The output will look like this

As you see, we now have a new array of numbers.

  1. Then the next challenge is how to convert this to Float values. And here we use the float node. The beauty with this node is that it ALSO accepts an array of values, ie we can take all our 56 tags and convert them in one go to floats.

  1. And all is well, until we notice that one or two of the tags is signed... Ie the original decimal value in the Modbus Slave is negative, like -0.1 or something like that. I am sure there are various ways of doing this, but for our purpose it was easiest to do this in our function node where we do the 16->32 conversion. So we add in this for the signed values.
let tag35 = (raw[68] << 16) + (raw[69]);
let tag36 = (raw[70] << 16) + (raw[71]);
// tag37 is signed 32bit, use >>> 0 operator to convert to signed
let tag37 = ((raw[72] << 16) + (raw[73])) >>> 0;
let tag38 = (raw[74] << 16) + (raw[75]);
let tag39 = (raw[76] << 16) + (raw[77]);

This will do the conversion nicely and with minimal fuss and we can feed it to the foFloat node, which then gives us the correct value like -0.1.

  1. And lastly we create a new numbered tag list.

  2. Oh and yes, we do use Excel CONCAT to create easily these longish lists of tag definitions

The complete flow is below.

[{"id":"75de6ce4c6486604","type":"function","z":"1f78ec24476b22f4","name":"Read 1-120","func":"msg.payload = {\n    'fc': 3, \n    'unitid': 1,\n    'address': 0,\n    'quantity': 120\n}; \nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":290,"y":80,"wires":[["11d71e27b87d8efb"]]},{"id":"11d71e27b87d8efb","type":"modbus-flex-getter","z":"1f78ec24476b22f4","name":"Modbus device","showStatusActivities":true,"showErrors":true,"showWarnings":true,"logIOActivities":false,"server":"31437533b8c066eb","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":false,"keepMsgProperties":false,"delayOnStart":false,"startDelayTime":"","x":460,"y":80,"wires":[["ad01bbd36f355197","8805930fae41330f"],[]]},{"id":"ad01bbd36f355197","type":"function","z":"1f78ec24476b22f4","name":"Scale and tag","func":"let raw = msg.payload;\nlet tag1 = (raw[0] << 16) + (raw[1]);\nlet tag2 = (raw[2] << 16) + (raw[3]);\nlet tag3 = (raw[4] << 16) + (raw[5]);\nlet tag4 = (raw[6] << 16) + (raw[7]);\nlet tag5 = (raw[8] << 16) + (raw[9]);\nlet tag6 = (raw[10] << 16) + (raw[11]);\nlet tag7 = (raw[12] << 16) + (raw[13]);\nlet tag8 = (raw[14] << 16) + (raw[15]);\nlet tag9 = (raw[16] << 16) + (raw[17]);\nlet tag10 = (raw[18] << 16) + (raw[19]);\nlet tag11 = (raw[20] << 16) + (raw[21]);\nlet tag12 = (raw[22] << 16) + (raw[23]);\nlet tag13 = (raw[24] << 16) + (raw[25]);\nlet tag14 = (raw[26] << 16) + (raw[27]);\nlet tag15 = (raw[28] << 16) + (raw[29]);\nlet tag16 = (raw[30] << 16) + (raw[31]);\nlet tag17 = (raw[32] << 16) + (raw[33]);\nlet tag18 = (raw[34] << 16) + (raw[35]);\nlet tag19 = (raw[36] << 16) + (raw[37]);\nlet tag20 = (raw[38] << 16) + (raw[39]);\nlet tag21 = (raw[40] << 16) + (raw[41]);\nlet tag22 = (raw[42] << 16) + (raw[43]);\nlet tag23 = (raw[44] << 16) + (raw[45]);\nlet tag24 = (raw[46] << 16) + (raw[47]);\nlet tag25 = (raw[48] << 16) + (raw[49]);\nlet tag26 = (raw[50] << 16) + (raw[51]);\nlet tag27 = (raw[52] << 16) + (raw[53]);\nlet tag28 = (raw[54] << 16) + (raw[55]);\nlet tag29 = (raw[56] << 16) + (raw[57]);\nlet tag30 = (raw[58] << 16) + (raw[59]);\nlet tag31 = (raw[60] << 16) + (raw[61]);\nlet tag32 = (raw[62] << 16) + (raw[63]);\nlet tag33 = (raw[64] << 16) + (raw[65]);\nlet tag34 = (raw[66] << 16) + (raw[67]);\nlet tag35 = (raw[68] << 16) + (raw[69]);\nlet tag36 = (raw[70] << 16) + (raw[71]);\n// tag37 rpmPs is signed 32bit, use >>> 0 operator to convert\nlet tag37 = ((raw[72] << 16) + (raw[73])) >>> 0;\nlet tag38 = (raw[74] << 16) + (raw[75]);\nlet tag39 = (raw[76] << 16) + (raw[77]);\nlet tag40 = (raw[78] << 16) + (raw[79]);\nlet tag41 = (raw[80] << 16) + (raw[81]);\nlet tag42 = (raw[82] << 16) + (raw[83]);\nlet tag43 = (raw[84] << 16) + (raw[85]);\n// tag44 rpmSb is signed 32bit, use >>> 0 operator to convert\nlet tag44 = ((raw[86] << 16) + (raw[87])) >>> 0;\nlet tag45 = (raw[88] << 16) + (raw[89]);\nlet tag46 = (raw[90] << 16) + (raw[91]);\nlet tag47 = (raw[92] << 16) + (raw[93]);\nlet tag48 = (raw[94] << 16) + (raw[95]);\nlet tag49 = (raw[96] << 16) + (raw[97]);\nlet tag50 = (raw[98] << 16) + (raw[99]);\nlet tag51 = (raw[100] << 16) + (raw[101]);\nlet tag52 = (raw[102] << 16) + (raw[103]);\nlet tag53 = (raw[104] << 16) + (raw[105]);\nlet tag54 = (raw[106] << 16) + (raw[107]);\nlet tag55 = (raw[108] << 16) + (raw[109]);\nlet tag56 = (raw[110] << 16) + (raw[111]);\nmsg.payload = [\n    tag1,\n    tag2,\n    tag3,\n    tag4,\n    tag5,\n    tag6,\n    tag7,\n    tag8,\n    tag9,\n    tag10,\n    tag11,\n    tag12,\n    tag13,\n    tag14,\n    tag15,\n    tag16,\n    tag17,\n    tag18,\n    tag19,\n    tag20,\n    tag21,\n    tag22,\n    tag23,\n    tag24,\n    tag25,\n    tag26,\n    tag27,\n    tag28,\n    tag29,\n    tag30,\n    tag31,\n    tag32,\n    tag33,\n    tag34,\n    tag35,\n    tag36,\n    tag37,\n    tag38,\n    tag39,\n    tag40,\n    tag41,\n    tag42,\n    tag43,\n    tag44,\n    tag45,\n    tag46,\n    tag47,\n    tag48,\n    tag49,\n    tag50,\n    tag51,\n    tag52,\n    tag53,\n    tag54,\n    tag55,\n    tag56\n]\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":640,"y":80,"wires":[["e7306e59870db61d","73f92ed0032e7c70"]]},{"id":"0b8bd62e15fce13b","type":"debug","z":"1f78ec24476b22f4","name":"debug 4","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":920,"y":40,"wires":[]},{"id":"e7306e59870db61d","type":"toFloat","z":"1f78ec24476b22f4","name":"","toFixed":"2","x":790,"y":80,"wires":[["0b8bd62e15fce13b","66b7426cb45e6bf9"]]},{"id":"73f92ed0032e7c70","type":"debug","z":"1f78ec24476b22f4","name":"debug 7","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":780,"y":40,"wires":[]},{"id":"66b7426cb45e6bf9","type":"function","z":"1f78ec24476b22f4","name":"to taglist","func":"let data = msg.payload;\nlet tag1 = data[0];\nmsg.payload = {\n    \"1\": data[0],\n    \"3\": data[1],\n    \"5\": data[2],\n    \"7\": data[3],\n    \"9\": data[4],\n    \"11\": data[5],\n    \"13\": data[6],\n    \"15\": data[7],\n    \"17\": data[8],\n    \"19\": data[9],\n    \"21\": data[10],\n    \"23\": data[11],\n    \"25\": data[12],\n    \"27\": data[13],\n    \"29\": data[14],\n    \"31\": data[15],\n    \"33\": data[16],\n    \"35\": data[17],\n    \"37\": data[18],\n    \"39\": data[19],\n    \"41\": data[20],\n    \"43\": data[21],\n    \"45\": data[22],\n    \"47\": data[23],\n    \"49\": data[24],\n    \"51\": data[25],\n    \"53\": data[26],\n    \"55\": data[27],\n    \"57\": data[28],\n    \"59\": data[29],\n    \"61\": data[30],\n    \"63\": data[31],\n    \"65\": data[32],\n    \"67\": data[33],\n    \"69\": data[34],\n    \"71\": data[35],\n    \"73\": data[36],\n    \"75\": data[37],\n    \"77\": data[38],\n    \"79\": data[39],\n    \"81\": data[40],\n    \"83\": data[41],\n    \"85\": data[42],\n    \"87\": data[43],\n    \"89\": data[44],\n    \"91\": data[45],\n    \"93\": data[46],\n    \"95\": data[47],\n    \"97\": data[48],\n    \"99\": data[49],\n    \"101\": data[50],\n    \"103\": data[51],\n    \"105\": data[52],\n    \"107\": data[53],\n    \"109\": data[54],\n    \"111\": data[55]\n}\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":920,"y":80,"wires":[["74d72a657927c89c"]]},{"id":"74d72a657927c89c","type":"debug","z":"1f78ec24476b22f4","name":"debug 8","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1080,"y":40,"wires":[]},{"id":"4246cc8376664773","type":"comment","z":"1f78ec24476b22f4","name":"Modbus data","info":"","x":130,"y":40,"wires":[]},{"id":"c4b16c03af453a22","type":"inject","z":"1f78ec24476b22f4","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"str","x":130,"y":80,"wires":[["75de6ce4c6486604"]]},{"id":"8805930fae41330f","type":"debug","z":"1f78ec24476b22f4","name":"debug 9","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":620,"y":40,"wires":[]}]

What was your question?

If you had asked I would have pointed you to the buffer-parser node & several of the worked examples on the forum.

For example, here is a NO-CODE solution of what you have above


(obviously I dont have your data so it is just made up)

1 Like

Excellent @Steve-Mcl! I hadn't tested that buffer parser. Let me give it a go and see how it works in this context, where some of the tags are signed and some unsigned.