plc4x icon indicating copy to clipboard operation
plc4x copied to clipboard

[Feature Request]: Modbus: Support for Word Swap

Open IsmoLeszczynski opened this issue 7 months ago • 3 comments

What would you like to happen?

The 0.13.0 version of the Modbus driver introduced the endianness and byte swapping features, but there is still one feature that should be added - word swapping. Word swapping swaps sequential word pairs (or registers), which is relevant with 64-bit values. For example, with 4 registers totalling 8 bytes:

Big Endian : [1, 2] [3, 4] [5, 6] [7, 8]

Big Endian with Word Swap: [3, 4] [1, 2] [7, 8] [5, 6]

Big Endian with Word Swap and Byte Swap: [4, 3] [2, 1] [8, 7] [6, 5]

I'm not sure if the LittleEndian was thought to be for this use originally, but as it reverses the entire sequence, it is conceptually different from Modbus word swapping for large data types.

I have created an implementation by extending the ModbusByteOrder with additional members:

  // [3, 4, 1, 2]
  // [3, 4, 1, 2, 7, 8, 5, 6]
  BIG_ENDIAN_WORD_SWAP,
  // [2, 1, 4, 3]
  // [6, 5, 8, 7, 2, 1, 4, 3]
  LITTLE_ENDIAN_WORD_SWAP,
  // [4, 3, 2, 1]
  // [4, 3, 2, 1, 8, 7, 6, 5]
  BIG_ENDIAN_WORD_SWAP_BYTE_SWAP,
  // [1, 2, 3, 4]
  // [5, 6, 7, 8, 1, 2, 3, 4]
  LITTLE_ENDIAN_WORD_SWAP_BYTE_SWAP

And in addition, to complement byteSwap(), I've added wordSwap():

  public static byte[] wordSwap(byte[] in) {
    if (in.length % 2 != 0) {
      throw new PlcRuntimeException("Input byte array length must be a multiple of 2 for word swapping.");
    }
    byte[] out = new byte[in.length];
    for (int i = 0; i < in.length; i += 4) {
      out[i] = in[i + 2];
      out[i + 1] = in[i + 3];
      out[i + 2] = in[i];
      out[i + 3] = in[i + 1];
    }
    return out;
  }

So, depending on the ModbusByteOrder, a combination of these methods are applied. My implementation currently is built on top of the previous PR #2055, I could either expand that PR to be more generic for Modbus improvements, or if it can be handled first, I can make a separate PR with the implementation.

Programming Languages

  • [x] plc4j
  • [ ] plc4go
  • [ ] plc4c
  • [ ] plc4net

Protocols

  • [ ] AB-Ethernet
  • [ ] ADS /AMS
  • [ ] BACnet/IP
  • [ ] CANopen
  • [ ] DeltaV
  • [ ] DF1
  • [ ] EtherNet/IP
  • [ ] Firmata
  • [ ] KNXnet/IP
  • [x] Modbus
  • [ ] OPC-UA
  • [ ] S7

IsmoLeszczynski avatar Apr 24 '25 11:04 IsmoLeszczynski

Have you had a look at this?

https://github.com/apache/plc4x/blob/a7ab2cf08c8b06b44c0d1b7ff8b71813d6a4966b/plc4j/drivers/modbus/src/main/java/org/apache/plc4x/java/modbus/base/optimizer/ModbusOptimizer.java#L419

Ok ... reading up on your case ... I guess you did ... sorry.

chrisdutz avatar Apr 24 '25 11:04 chrisdutz

I guess a summary of all encodings would make sense ... I personally have lost track of all of these crazy Modbus encodings.

chrisdutz avatar Apr 24 '25 12:04 chrisdutz

Yeah, Modbus is a beast of it's own with all these different ways to use the same data from the registers. Our previous implementation was technically always Big Endian with options for byte and word swapping, hadn't run in to the Little Endian cases before. With these word swap additions, as far as I can tell, we can now handle any "well known" ordering of the bytes with PLC4X.

IsmoLeszczynski avatar Apr 24 '25 13:04 IsmoLeszczynski

Time flies, apparently even summer holidays are too busy - I finally created #2192 to handle this and another issue.

IsmoLeszczynski avatar Jul 23 '25 10:07 IsmoLeszczynski

Didn't we already have 4 options for modbus? What's the difference?

chrisdutz avatar Jul 23 '25 10:07 chrisdutz

We had the order for the whole byte sequence (endianness), and we had the order of bytes within a word (byte swap). Word swap is for word pairs, where the two words are switched - and this is done in word pairs for as many pairs are there are in a sequence. The key is that the swapping is for sequential word pairs, not the complete sequence.

IsmoLeszczynski avatar Jul 23 '25 10:07 IsmoLeszczynski