Open fabio622 opened 1 year ago
ModbusEndianness
denotes the endianness of the server you connect to so that the library is able to convert the data to the endianness of your machine.
It looks like your server sends big endian data over the wire and thus you should use the BigEndian mode to get little endian data on your little endian machine.
Maybe it should have been called ServerEndianness :-)
The problem is that I try to read the same tags from the same machine via Kepware and via PLC Siemens using LittleEndian. So, I'm sure that Server send LittleEndian data.
There is not much going on in this library regarding endiannes: First, find out if there is an endianness mismatch: https://github.com/Apollo3zehn/FluentModbus/blob/40b6a56c98ad78e595964f585b82b060a44f4522/src/FluentModbus/Client/ModbusTcpClient.cs#L159-L160
if true, swap bytes: https://github.com/Apollo3zehn/FluentModbus/blob/40b6a56c98ad78e595964f585b82b060a44f4522/src/FluentModbus/Client/ModbusClient.cs#L127-L128
I guess your machine is little endian, so if you then specify ModbusEndianness.LittleEndian
, the raw data are being left untouched.
The source of confusion must lie elsewhere ... Maybe because our terms are not precise enough. The Modbus protocol spec defines that the data should be send as BigEndian over the wire. It does not specify how the data are being stored on the server side.
Some servers are using little-endian for the wire format - against the specification - and that is why FluentModbus supports both ways.
Maybe Kepware and Siemens PLC always convert the big-endian wire data transparently to little-endian on your machine. And when you specify the endianness this does not refer to the wire format but how the data is actually stored in the server. I.e. if you specify big-endian, your data get converted twice, which is equal to no conversion - while FluentModbus would convert it once, since it actually talks about the wire format (so ModbusEndianness should not be called ServerEndianness as stated above but WireEndiannes
).
I have no other explanation right now :-/
The current design is somewhat confusing to use.
When I use it, I tend to read the byte
directly, and then convert it to Span<short>
for processing, which can avoid many such problems, as follows:
Span<byte> datasets = client.ReadHoldingRegisters(_unitIdentifier, _address, countVariable);
Span<short> _dataset = MemoryMarshal.Cast<byte, short>(datasets).ToArray();
float bigEndian = _dataset.GetBigEndian<float>(offset);
float littleEndian = _dataset.GetLittleEndian<float>(offset);
float midLittleEndian = _dataset.GetMidLittleEndian<float>(offset);
Thank you @Apollo3zehn for clarification!
@fabio622 you are welcome!
@Mjollnirs thank your for sharing your workflow. I know the current API might be a bit confusing. It has been designed with maximum performance in mind. My use case is Modbus TCP with hundreds of float32 variables @25 Hz. For that the generic ReadXXX
I will leave this issue open as a reminder to think about how the API could be changed to make its usage clearer.
i have a problem too with Endianness. my computer ist LittleEndian and i call Connect(IPEndPoint remoteEndpoint), also with default little endian too I must swape value with IPAddress.NetworkToHostOrder(value) before get bytes and sending to the modbus server
@Apollo3zehn It looks like the low-level API methods do not check SwapBytes. Is this intentional? The modbus server I am using sends registers in BE. Since I don't have access to ModbusUtils.SwitchEndianness(), I need to swap the bytes on my own. Is there any reason for not applying the endianness inside the method?
so how to solve this problem? the data that read from the server in littleendianness mode is wrong
Thank you @Apollo3zehn for clarification!
how to solve this problem?
@clee781 If you try to read data of type short
or ushort
from the server, then you could use the generic method ReadHoldingRegisters<ushort>
instead of the low level one.
The endianness cannot be applied in the low level methods because the data may be e.g. of type long
but without that knowledge it is impossible to say how to swap bytes (it all depends on the size of the actual data type).
The main confusion comes from the fact that Modbus defines 16-bit registers but actual data may be larger (e.g. 32 bit) and Modbus servers behave inconsistently in that case. Some do swap bytes at the 16-bit boundaries and some swap them at the 32 bit boundaries (or whatever the data type size is).
In case you know that your server swaps data always at the 16-bit boundary, you could do the following to read e.g. long data:
var registerData = client.ReadHoldingRegisters<ushort>(...);
var longData = MemoryMarshal.Cast<ushort, long>(registerData);
This way ModbusUtils.SwitchEndianness
will be applied to the data if necessary by swapping pairs of bytes. The data will then be casted to the actual data type (long
in this example).
If both of you, @clee781 and @Chuo1st, could answer the following, I may give you better advise
Thanks!
@clee781 If you try to read data of type
short
orushort
from the server, then you could use the generic methodReadHoldingRegisters<ushort>
instead of the low level one.The endianness cannot be applied in the low level methods because the data may be e.g. of type
long
but without that knowledge it is impossible to say how to swap bytes (it all depends on the size of the actual data type).The main confusion comes from the fact that Modbus defines 16-bit registers but actual data may be larger (e.g. 32 bit) and Modbus servers behave inconsistently in that case. Some do swap bytes at the 16-bit boundaries and some swap them at the 32 bit boundaries (or whatever the data type size is).
In case you know that your server swaps data always at the 16-bit boundary, you could do the following to read e.g. long data:
var registerData = client.ReadHoldingRegisters<ushort>(...); var longData = MemoryMarshal.Cast<ushort, long>(registerData);
This way
ModbusUtils.SwitchEndianness
will be applied to the data if necessary by swapping pairs of bytes. The data will then be casted to the actual data type (long
in this example).If both of you, @clee781 and @Chuo1st, could answer the following, I may give you better advise
- how many data do you want to read from the server (a single value or multiple values?)
- data type of the data on the server (byte, short, int, long, ...)
- endianness of the server and how the server behaves (are bytes swapped at register boundaries (16-bit) or at data type size boundaries)?
Thanks!
谢谢(thanks), it works correctly after adding the snippets!
@clee781 If you try to read data of type
short
orushort
from the server, then you could use the generic methodReadHoldingRegisters<ushort>
instead of the low level one.The endianness cannot be applied in the low level methods because the data may be e.g. of type
long
but without that knowledge it is impossible to say how to swap bytes (it all depends on the size of the actual data type).The main confusion comes from the fact that Modbus defines 16-bit registers but actual data may be larger (e.g. 32 bit) and Modbus servers behave inconsistently in that case. Some do swap bytes at the 16-bit boundaries and some swap them at the 32 bit boundaries (or whatever the data type size is).
In case you know that your server swaps data always at the 16-bit boundary, you could do the following to read e.g. long data:
var registerData = client.ReadHoldingRegisters<ushort>(...); var longData = MemoryMarshal.Cast<ushort, long>(registerData);
This way
ModbusUtils.SwitchEndianness
will be applied to the data if necessary by swapping pairs of bytes. The data will then be casted to the actual data type (long
in this example).If both of you, @clee781 and @Chuo1st, could answer the following, I may give you better advise
- how many data do you want to read from the server (a single value or multiple values?)
- data type of the data on the server (byte, short, int, long, ...)
- endianness of the server and how the server behaves (are bytes swapped at register boundaries (16-bit) or at data type size boundaries)?
Thanks!
Thank you for the suggestion. I'm using the read ushort workaround now.
I understand that Modbus servers can be inconsistent about endianness of the 16-bit registers in a 32-bit word, but the spec is very clear about using big-endian for the register itself regardless of what the register order of the word is. Can't we safely swap the endianness at the 16-bit boundary? As it is now, I don't believe the low level methods are applicable in any scenario since the register endianness will always need to be swapped regardless of the word order .Virtually all Modbus slaves will send 16-bit registers in big-endian format. I think the default should be to assume this. Is there a device I don't know about that doesn't send the registers as big-endian over the wire?
For my current application, I am reading in a couple dozen values of different datatypes. I prefer to do this in one transaction because it's faster.
I try to connect with LittleEndian mode but reads result is in BigEndian mode. If i try to connect with BigEndian mode the reads result is in LittleEndian mode.