Newbie: convert modbus float [array] register to float value

Hi,
I have problems to convert float and 32b (signed/unsigned) registers to obtain a result able to be sent to MQTT broker. The modbus device is a semaphore/Servelec Tbox unit. I use node-red modbustcp

Example: I have a float register with a value of 22.362 in it. I want to pass that value to my MQTT broker.
When I debug in node-red, I obtain an array: [16818,58720] but I'm stuck to convert this to a readable precise value of 22.362 (22.36 will be ok)

I have no probs with int16 registers (because they are 16bits obviously but need to find a function to convert negative values to readable ones)
I found here a function but the result seems weird (imprecise).

*Edit: I made my test simple. That's the reason I set the value of 22.362 into register 22362 ! Easier to remember

See this thread (read it all) there are some pointers in there that might help.

Hi Steve and thank you for the link.
It seems that for me the code is not that difficult (...) because values on the array are not hex but plain decimal.
With my first test, I put readFloatBE() and I obtained a usable value.
However, very strange to me, the result varies with every poll (even the data in the register is fixed for my test). Stranger, with 2 polls with the same code, I obtain 2 sets of variable values... The value set in my modbus register is 35.174 and I have the right value (35.17) only once in a while.

*Edit: here is the code I put. Ok, I'm a real newbie and I added what I found around here. I really don't know why those 2 lines are needed, but without: no results.

const buf = Buffer.allocUnsafe(4);
buf.writeUInt16BE(pay[0]); // high byte

*Edit2: it seems, for unknown reasons, that LE displays now "correct" values (aka 0 because wrong type) instead of a replicated one. So Big Endian seems +/- ok, except the variability.

How is it possible ?
Thank you++
Mike

I suspect when you update msg.payload in function 1 you are affecting the object before it's processed in the 2nd function.

It looks looks like the payload is an array/object so I world probably try returning a new object instead of updating msg.payload e.g...

var msg2 = {
  payload : buf.readFloatBE(0)
}
return msg2;

Try to remember the asynchronous nature of node-red and how object references work in JavaScript.

Can't really help more unless you post your flow and sample data.

Assuming this is in a recent version of node-red (@PPz which version is it?) then I don't think that should happen as node-red should automatically clone the message for the second wire out of the node.
@PPz in the functions use node.warn() to show the contents of pay after you have picked it up.

thank you both of you (@Steve-Mcl) for your kind help.
My version is 1.0.3

the code of the function is :

let pay = msg.payload;
const buf = Buffer.allocUnsafe(4); // (4) is ok
buf.writeUInt16BE(pay[0]); // high byte
msg.payload = buf.readFloatBE(0);
// 2 numbers after comma (string)
// msg.payload = buf.readFloatBE(0).toFixed(2);
//
return msg;

the float value into my plc is 22.362
the raw received data is an array [16818,58720]
the result (after function) varies from 22.25 to 22.37

about node.warn(), I'm affraid that I'm a real newbie about node-red

Have you tried searching for it?

Yes, but when I inject
node.warn(pay);
I receive exactly the same array value [16818,58720] as the raw debug message

Update: when I inject node.warn(buf), I obtain a splitted array like this one: [65,178,98,5], or [65,178,125,3], or [65,178,0,0] (variable). Ok, 16818 = (256*65)+178

Update 2: for what I understand here, the 2 first values of the array [65,178] seems consistent and represent probably/certainly the value that doesn't change (22). Why the others are changing ? A real mystery for me.

Update 3: maybe it will help you, but the weird thing about my plc is that the right value is in the array when dealing with 32b registers. If I poll a 32b register with a value of 22358, the node-red poll send me an array with [0,22358]. So it seems that's even not a hex value, just the plain decimal value...

You are allocating a buffer of length 4 bytes. Then you use writeUInt16BE to write to the first two bytes. You have not written anything to the second two bytes so they are left with whatever happened to be lying around in that bit of memory. That is why they contain variable data. You then use readFloat to read all four bytes to give you the (veriable) result.

tagging @Iqapps

Thank you. I need to dig more about this and I will probably come come back later with this. For you, it's probably a piece of cake. Not that obvious for me (must read some doc. I usually learn while have a problem to solve).

For the moment, I simplified my problem with my PLC (that I know far better): I created a bunch of 16b variables and made a small pgm (into my plc) to convert some floats registers into int16 (float*100) and I transform them into floats (int16/100) to obtain a nice result with node-red and passing the values to mqtt and hubitat [shame on me).
Because my main problem was to harvest modbus data and send them to Hubitat. Learn Hubitat, MQTT apps, mosquitto and Node-red at the same time is a little bit too much for me (I'm an old guy).

Thank you again for your answers.
Mike

2 Likes

Here is little more information about how modbus handle 32 bit....

1 Like

tagging @Colin

thanks for the doc and the help.
I discovered more and more and simplified a lot.
Build on my plc a set of int16 registers.
at first, I defined different polls, but the target plc was not happy of that (poll errors)
Like in modbus devices, I set now 1 poll with successive addresses :slight_smile:
Far better !
But now, with that method, I loose my different topic names for my MQTT broker (before: 1 topic per poll, now: 1 topic only because 1 poll)

Is it a way to define an array of topics when polling once and/or functions to build a specific topic for each extracted value ?

Before:

After:

In the Change nodes where you extract the individual parts add another row that Sets msg.topic To the/topic/for/this/value

1 Like

Mucho Perfecto ! And much faster too...

2 Likes


I believe this is along the same lines as this topic, but I'm a total newbie and need more help then just a picture of some screens.
A little setting of the stage for this:
I have some Modbus over Ethernet devices, right now I'm just polling one. I thought at first everything was great until I looked at my data logs this morning and saw some abnormal readings. So long story, I just get to the point, to make this east to understand and view I broke this back to the very basic.

I have a few values I can set manually in my device using the software that comes with it, I'm also polling that value in node-red. I used two just to show the what happens when any value get over 65.53.
So it did take me a while to notice but each register is 2 points, the first point only shows a 0 or 1, the second point shows the value of point I'm polling. Notice on the bottom poll the actual reading on my device is 65.50 and the polled values are (0, 65000) "yes I could use a range on it to be the normal 65.50 but I wanted to show the raw data for this".
The top one you'll notice looks completely different. The value in my device is at 66.0 and the polled value is now (1, 464).
The actual tipping point is 65.53,
65.54 comes back as 4,
65.55 as 14
65.56 as 24
65.57 as 34
65.67 as 134
Its almost like it starts counting over above 65.53.
It doesn't matter what point I poll on the controller nor does it matter what the scale is if it is 65.54 or greater it comes back as (1, xxx) type value.

Is there a way I can put this value through a function or something to balance this out?

Thank You

1 Like

65.53 triggered a bell to me.
does 65535 rings a bell to you ?
one less than 2 to the 16th power
there is a 16b integer somewhere in your value.

256 (2 to the 8th power) for 8 bits value. those numbers are important.

The harvested array changes because your data reached a limit.
below 65500 (I think it could-must be 65535), the low byte is 0 and the high is the value. If it increases, your low byte changes to 1 and you add the difference
in your case (1x65535) + 464 = = 65999 => 65.99 = 66.0 (precision)

Note: it could be different, depending if your register is signed or unsigned.

a signed value has a range of 32767 because the value can be also negative while an unsigned value has a range of a full 16bit, aka 65535 ! In your case, your modbus register is an unsigned 16b

of course, your modbus device could also have 32bit registers, you can count.
16b registers are common in modbus devices, some devices have only 16b registers. But you can harvest them an modify/split them the way you want.
I have one plc and before sending data to mqtt / node-red, I change them all to 16b to simplify the poll.

coils (relays), digital 0 or 1 => 16b register (0 or 1)
32b registers ? convert them to 16b registers and pay attention if their value is over 65535.
I'm lazy, so if I know that the 32b register I poll will never be over 65535 (by example a temperature on a float register), I divide them by 100, tranform them to 16b and multiply then by 100 on the other side to obtain 2 numbers after the comma.

Float1 = 45.35 ===> tempo = 45.35x100 = 4535 => transform to 16b => 4535 ===> node-red/etc ==> 4535 / 100 = 45.35 on the other side. Far easier than send floats and convert them with functions [low byte, high byte], etc !

1 Like

PPz,
thanks for the fast response.
It sounds like you got this figured out which is great, now we just have to move your knowledge over to me.
I know I'm asking you to take time out of your day, but is it possible you could either explain your calculations or better yet do it in node-red and show me how to do it.
FYI the docs on my hardware show I have a few different Bits depending on what I'm polling.
could you explain what I would need to do for each of these, I guess I'm confused on the signed/Unsigned. I mean I sort of understand the bit thing but not why its not compatible when I poll it through node-red.
32 bit unsigned Integers
32 bit signed Integer
Sometimes those have LOW_HI at the end, not sure what the LOW_HI means as there is no further description in the docs.

Thanks for helping.

PPz,

I GOT IT!! well sort of, I know how to do this on paper now I just need to figure out how to do this in node-red.

so I have to poll the two registers (0,65000)
If the first number is "X" and the second set of number is "Y" then:
X*Y+Y is what I'm looking for. then I could multiply time 100 to make it 65.00.

I just don't know how to do that in node-red just yet.