Modbus client not translating tag into correct floating-point format

I have a modus server spun up using pymodbus, it pulls data from a CSV and updates the holding registers once every second (all values are 32-bit float). I’ve written a basic client for this server, and verified that values pulled from the csv, pushed to the server and then pulled “on the other side” by the client match what was originally in the CSV.

However, when I connect to this server using the Modus TCP driver in Ignition Edge, it can’t seem to decode the value even when I explicitly put it in as a float - it seems to be instead providing the numerical value of the first 16-bit word, or some weird intermediate values.

This is what my tag (right now I’m just trying to get one) looks like in designer:

This is what the overall designer window is showing me:

And this is what I see on the gateway side:

1 Like

You’re likely off by one register or need to enable the “Swap Words” setting, or both.

1 Like

It’s unlikely to be an off-by-one error, I checked and compared the modbus requests received when using my hand-made client and when using Ignition, they are exactly the same.

As far as the “Swap Words” setting, could you tell me where that is? I don’t see that in either the gateway or the Designer.

It’s in the Gateway under the advanced settings for the Modbus device you created.

1 Like

Is it “Reverse Word Order”? Because I just toggled that and it doesn’t seem to have had any effect.

Yeah, that’s it.

If you can get a Wireshark capture or turn the “ReadHoldingRegistersRequest” logger to TRACE level you’ll be able to see exactly what value your server is returning in response to exactly what request.

https://docs.inductiveautomation.com/display/DOC81/Diagnostics+-+Logs#DiagnosticsLogs-ChangingLoggingLevels

You’ve specified HRF1 as your address, so you should expect to see a read holding registers request for 2 registers at protocol address 0. (Ignition is 1-based by default, like the Modbus spec describes, however at the protocol level Modbus is 0-based…).

You should see 4 bytes for those 2 registers in return. The default expectation before you toggled that setting was that they will combine form a 4-byte float in big-endian byte order. Reverse Word Order just does exactly what it sounds like, swaps the registers before looking at the bytes. [HR1][HR2] → [HR2][HR1].

1 Like

Right, I suspected as much. On the modbus server that I spun up, here’s what I see in the logs:

2022-03-10 16:52:01,699 MainThread      DEBUG    asynchronous   :64       Data Received: 0x2 0x47 0x0 0x0 0x0 0x6 0x0 0x3 0x0 0x0 0x0 0x2
2022-03-10 16:52:01,699 MainThread      DEBUG    socket_framer  :147      Processing: 0x2 0x47 0x0 0x0 0x0 0x6 0x0 0x3 0x0 0x0 0x0 0x2
2022-03-10 16:52:01,699 MainThread      DEBUG    factory        :137      Factory Request[ReadHoldingRegistersRequest: 3]
2022-03-10 16:52:01,699 MainThread      DEBUG    context        :63       validate: fc-[3] address-1: count-2
2022-03-10 16:52:01,699 MainThread      DEBUG    context        :77       getValues fc-[3] address-1: count-2
2022-03-10 16:52:01,700 MainThread      DEBUG    asynchronous   :102      send: b'024700000007000304733ebbd5'

It’s not from the IA internals, but hopefully should be understandable - the modbus request we see on the first line looks exactly like I’d expect, starting at 0x0 0x0 and ending at 0x0 0x2. The logs from my handmade client show the exact same request, minus the synchronization bytes at the beginning.

I think I figured out how to turn on the Ignition logger for ReadHoldingRegistersRequest (or at least filter out for specifically that logger) but it doesn’t seem to be showing every request explicitly, just the ones that failed. Even with me toggling the logging level to TRACE.

You need to use the gear icon in the upper right to find the logger and set its level, not the filter you get presented with on the main log view.

What value are you expecting? Even from your logs I can see the server is returning 733ebbd5:

>>> import struct
>>> struct.unpack('!f', '733ebbd5'.decode('hex'))[0]
1.5111482075746394e+31

Gotcha. Here’s a screenshot of the logs:

Something in the range of 0-1 - not entirely sure where the issue is happening, because it looks to be receiving the exact same request but working fine in my handmade client… But I’m explicitly doing the unpacking/decoding on that one.

I’ll have to do some deeper investigation, give me a little bit…

Okay so the value is changing each response, but it still looks like your server isn’t returning anything sensible.

Ignition uses unit/slave id 0 by default - is your client also using 0 or using 1 or something else?

It might be using unit 1, actually… pymodbus isn’t that clear on the defaults. I just tried to check, and I actually got the correct expected responses using both unit_id=0 and unit_id=1 (this was in my handmade client). That’s a little unexpected…

If the requests and responses are the same then I can only imagine your client interprets the bytes in a different order.

Given 733ebbd5 the only order that yields a “normal” looking value in [0,1] is BADC, where Ignition expects ABCD (big endian) or CDAB (word swapped).

I’ll follow up on this later - you’ve given me some good information and I have a hunch you may be right about the ordering issue. Currently investigating.

Tip: Modbus defines the order on the wire for 16-bit quantities to be big-endian. Send some 16-bit integers and see how they come out.

Finishing this issue for the sake of posterity (hopefully somebody will find this useful in the future): Apparently pymodbus by default saves word order as Big Endian and byte order as Little Endian, exactly the opposite of what works for Ignition either default or word-swapped. Unsure if there’s a way to account for this in Ignition, but hopefully this knowledge is useful for someone else in the future.

Nope. pymodbus is just wrong. The modbus spec leaves a great deal unstated for non-16-bit quantities, but those are always big-endian in the payload.

It looks like you can force PyModbus to use different packing, somehow: