Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -214,4 +214,9 @@ private PlcResponseCode decodeResponseCode(short status) {
return PlcResponseCode.NOT_FOUND;
}

@Override
public void channelInactive(ConversationContext<CIPEncapsulationPacket> context) {
tm.shutdown();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -1985,4 +1985,9 @@ protected byte[] getNullByteTerminatedArray(String value) {
return nullTerminatedBytes;
}

@Override
public void channelInactive(ConversationContext<AmsTCPPacket> context) {
tm.shutdown();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -288,4 +288,9 @@ private String toString(BacNetIpTag tag) {
return tag.getDeviceIdentifier() + "/" + tag.getObjectType() + "/" + tag.getObjectInstance();
}

@Override
public void channelInactive(ConversationContext<BVLC> context) {
// Nothing to do here ...
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,9 @@ public CompletableFuture<PlcReadResponse> read(PlcReadRequest readRequest) {
protected void decode(ConversationContext<CBusCommand> context, CBusCommand msg) throws Exception {
}

@Override
public void channelInactive(ConversationContext<CBusCommand> context) {
tm.shutdown();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,9 @@ public void close(ConversationContext<C> context) {
delegate.close(new ConversationContextWrapper<>(context, wireType, adapter, frameHandler, context.getAuthentication()));
}

@Override
public void channelInactive(ConversationContext<C> context) {
delegate.close(new ConversationContextWrapper<>(context, wireType, adapter, frameHandler, context.getAuthentication()));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -233,4 +233,10 @@ public PlcConsumerRegistration register(Consumer<PlcSubscriptionEvent> consumer,
public void unregister(PlcConsumerRegistration registration) {
consumers.remove(registration);
}

@Override
public void channelInactive(ConversationContext<GenericFrame> context) {
tm.shutdown();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -453,4 +453,9 @@ public void onDisconnect(ConversationContext<CANOpenFrame> context) {
}
}

@Override
public void channelInactive(ConversationContext<CANOpenFrame> context) {
tm.shutdown();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -1355,4 +1355,9 @@ private PlcResponseCode decodeResponseCode(int status) {
}
}

@Override
public void channelInactive(ConversationContext<EipPacket> context) {
tm.shutdown();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -320,4 +320,9 @@ protected BitSet getDigitalValues(int byteBlock, List<Byte> data) {
return BitSet.valueOf(bitSetData);
}

@Override
public void channelInactive(ConversationContext<FirmataMessage> context) {
connected.set(false);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -235,4 +235,9 @@ protected void publishEvent(LocalDateTime timeStamp, Iec608705104Tag tag, PlcVal
}
}

@Override
public void channelInactive(ConversationContext<APDU> context) {
tm.shutdown();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -640,4 +640,9 @@ protected static String toString(KnxGroupAddress groupAddress) {
throw new PlcRuntimeException("Unsupported Group Address Type " + groupAddress.getClass().getName());
}

@Override
public void channelInactive(ConversationContext<KnxNetIpMessage> context) {
tm.shutdown();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import org.apache.plc4x.java.spi.messages.utils.PlcTagItem;
import org.apache.plc4x.java.spi.optimizer.SingleTagOptimizer;
import org.apache.plc4x.java.spi.values.PlcBOOL;
import org.apache.plc4x.java.spi.values.PlcList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -208,11 +209,25 @@ protected PlcReadResponse processReadResponses(PlcReadRequest readRequest, Map<P

// Calculate the byte that contains the response for this Coil
byte[] responseData = response.getResponseData();
int bitPosition = modbusTag.getAddress() - response.startingAddress;
int bytePosition = bitPosition / 8;
int bitPositionInByte = bitPosition % 8;
boolean isBitSet = (responseData[bytePosition] & (1 << bitPositionInByte)) != 0;
values.put(tagName, new DefaultPlcResponseItem<>(PlcResponseCode.OK, new PlcBOOL(isBitSet)));

// Check if we're dealing with an array of Coils or Discrete Inputs
if (modbusTag.getNumberOfElements() > 1) {
PlcList bitValues = new PlcList();
for (int i = 0; i < modbusTag.getNumberOfElements(); i++) {
int bitPosition = modbusTag.getAddress() - response.startingAddress + i;
int bytePosition = bitPosition / 8;
int bitPositionInByte = bitPosition % 8;
boolean isBitSet = (responseData[bytePosition] & (1 << bitPositionInByte)) != 0;
bitValues.add(new PlcBOOL(isBitSet));
}
values.put(tagName, new DefaultPlcResponseItem<>(PlcResponseCode.OK, bitValues));
} else {
int bitPosition = modbusTag.getAddress() - response.startingAddress;
int bytePosition = bitPosition / 8;
int bitPositionInByte = bitPosition % 8;
boolean isBitSet = (responseData[bytePosition] & (1 << bitPositionInByte)) != 0;
values.put(tagName, new DefaultPlcResponseItem<>(PlcResponseCode.OK, new PlcBOOL(isBitSet)));
}
break;
}
}
Expand Down Expand Up @@ -436,6 +451,32 @@ private ReadBuffer getReadBuffer(byte[] data, ModbusByteOrder byteOrder) {
byte[] reordered = ModbusProtocolLogic.byteSwap(data);
return new ReadBufferByteBased(reordered, ByteOrder.LITTLE_ENDIAN);
}
case BIG_ENDIAN_WORD_SWAP: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sort of not entirely happy with the names. I asked chatgpt for help 😉 here's the response:

You're dealing with eight different byte/word permutations for Modbus data interpretation, which is common when mapping PLC registers to types like int32, float, or double. The current enum names are a bit ambiguous and repetitive.

🧠 Goal

Come up with more descriptive, unambiguous names that clearly convey:

Byte order within a word (e.g., big vs. little endian)

Word order (e.g., whether 16-bit words are swapped)

Whether both are swapped (combined swap)

🔢 First, mapping by example

Let's take the base array:
[1, 2, 3, 4, 5, 6, 7, 8]
Split into 16-bit words (2 bytes each):
[1,2], [3,4], [5,6], [7,8]

Then apply the transformations per enum:

EnumResultByte OrderWord OrderByte Swap?Word Swap?BIG_ENDIAN[1,2,3,4,5,6,7,8]BigNormal❌❌LITTLE_ENDIAN[8,7,6,5,4,3,2,1]LittleReversed✅✅BIG_ENDIAN_BYTE_SWAP[2,1,4,3,6,5,8,7]LittleNormal✅❌LITTLE_ENDIAN_BYTE_SWAP[7,8,5,6,3,4,1,2]BigReversed❌✅BIG_ENDIAN_WORD_SWAP[3,4,1,2,7,8,5,6]BigSwapped❌✅LITTLE_ENDIAN_WORD_SWAP[6,5,8,7,2,1,4,3]LittleSwapped✅✅BIG_ENDIAN_WORD_SWAP_BYTE_SWAP[4,3,2,1,8,7,6,5]ReversedSwapped✅✅LITTLE_ENDIAN_WORD_SWAP_BYTE_SWAP[5,6,7,8,1,2,3,4]NormalSwapped❌✅

✅ Proposed Enum Names

Old NameNew ProposalReasoningBIG_ENDIANBYTE_ORDER_BIG__WORD_ORDER_NORMALExplicitLITTLE_ENDIANBYTE_ORDER_LITTLE__WORD_ORDER_REVERSEDReflects full reversalBIG_ENDIAN_BYTE_SWAPBYTE_ORDER_LITTLE__WORD_ORDER_NORMALOnly byte swap = little endianLITTLE_ENDIAN_BYTE_SWAPBYTE_ORDER_BIG__WORD_ORDER_REVERSEDOnly word swap = word reversalBIG_ENDIAN_WORD_SWAPBYTE_ORDER_BIG__WORD_ORDER_SWAPPEDWords out of orderLITTLE_ENDIAN_WORD_SWAPBYTE_ORDER_LITTLE__WORD_ORDER_SWAPPEDByte+Word swapBIG_ENDIAN_WORD_SWAP_BYTE_SWAPBYTE_ORDER_REVERSED__WORD_ORDER_SWAPPEDBoth swapped, full reverseLITTLE_ENDIAN_WORD_SWAP_BYTE_SWAPBYTE_ORDER_NORMAL__WORD_ORDER_SWAPPEDJust word swap

💡 Optional Shorter Variant

If you want brevity but still clarity:

NORMAL, // [1,2,3,4] REVERSED, // [4,3,2,1] BYTE_SWAP, // [2,1,4,3] BYTE_SWAP_REVERSED, // [7,8,5,6,3,4,1,2] WORD_SWAP, // [3,4,1,2] BYTE_WORD_SWAP, // [6,5,8,7,2,1,4,3] FULL_REVERSED, // [4,3,2,1,8,7,6,5] WORD_SWAP_CONTIGUOUS // [5,6,7,8,1,2,3,4]

Would you prefer long descriptive or shorter but still clear names? I can adjust accordingly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Argh.. It fu**ed up the table 🙁

// [3, 4, 1, 2]
// [3, 4, 1, 2, 7, 8, 5, 6]
byte[] reordered = ModbusProtocolLogic.wordSwap(data);
return new ReadBufferByteBased(reordered, ByteOrder.BIG_ENDIAN);
}
case LITTLE_ENDIAN_WORD_SWAP: {
// [2, 1, 4, 3]
// [6, 5, 8, 7, 2, 1, 4, 3]
byte[] reordered = ModbusProtocolLogic.wordSwap(data);
return new ReadBufferByteBased(reordered, ByteOrder.LITTLE_ENDIAN);
}
case BIG_ENDIAN_WORD_SWAP_BYTE_SWAP: {
// [4, 3, 2, 1]
// [4, 3, 2, 1, 8, 7, 6, 5]
byte[] reordered = ModbusProtocolLogic.byteSwap(data);
reordered = ModbusProtocolLogic.wordSwap(reordered);
return new ReadBufferByteBased(reordered, ByteOrder.BIG_ENDIAN);
}
case LITTLE_ENDIAN_WORD_SWAP_BYTE_SWAP: {
// [1, 2, 3, 4]
// [5, 6, 7, 8, 1, 2, 3, 4]
byte[] reordered = ModbusProtocolLogic.byteSwap(data);
reordered = ModbusProtocolLogic.wordSwap(reordered);
return new ReadBufferByteBased(reordered, ByteOrder.LITTLE_ENDIAN);
}
default:
// 16909060
// [1, 2, 3, 4]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ public void close(ConversationContext<T> context) {
// Nothing to do here ...
}

@Override
public void channelInactive(ConversationContext<T> context) {
// Nothing to do here ...
}

@Override
protected void decode(ConversationContext<T> context, T msg) throws Exception {
super.decode(context, msg);
Expand Down Expand Up @@ -418,4 +423,18 @@ public static byte[] byteSwap(byte[] in) {
return out;
}

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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ public void close(ConversationContext<ModbusTcpADU> context) {
tm.shutdown();
}

@Override
public void channelInactive(ConversationContext<ModbusTcpADU> context) {
tm.shutdown();
}

@Override
public CompletableFuture<PlcPingResponse> ping(PlcPingRequest pingRequest) {
CompletableFuture<PlcPingResponse> future = new CompletableFuture<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,17 @@ public enum ModbusByteOrder {
BIG_ENDIAN_BYTE_SWAP,
// [3, 4, 1, 2]
// [7, 8, 5, 6, 3, 4, 1, 2]
LITTLE_ENDIAN_BYTE_SWAP
LITTLE_ENDIAN_BYTE_SWAP,
// [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,
}
Original file line number Diff line number Diff line change
Expand Up @@ -900,6 +900,11 @@ private GuidValue toGuidValue(String identifier) {
return new GuidValue(0L, 0, 0, data4, data5);
}

@Override
public void channelInactive(ConversationContext<OpcuaAPU> context) {
tm.shutdown();
}

private static <T> void bridge(RequestTransaction transaction, CompletableFuture<T> future, T response, Throwable error) {
if (error != null) {
future.completeExceptionally(error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,8 @@ public PlcConsumerRegistration register(Consumer<PlcSubscriptionEvent> consumer,
public void unregister(PlcConsumerRegistration plcConsumerRegistration) {
}

@Override
public void channelInactive(ConversationContext<OpenProtocolMessage> context) {
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -200,4 +200,9 @@ protected void decode(ConversationContext<Plc4xMessage> context, Plc4xMessage ms
super.decode(context, msg);
}

@Override
public void channelInactive(ConversationContext<Plc4xMessage> context) {
tm.shutdown();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -905,4 +905,9 @@ protected int getDataTypeLengthInBytes(PlcValueType dataType) {
throw new PlcRuntimeException("Length undefined");
}

@Override
public void channelInactive(ConversationContext<Ethernet_Frame> context) {
context.getChannel().close();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -271,4 +271,9 @@ public CompletableFuture<PlcSubscriptionResponse> subscribe(PlcSubscriptionReque
protected void decode(ConversationContext<Ethernet_Frame> context, Ethernet_Frame msg) throws Exception {
super.decode(context, msg);
}

@Override
public void channelInactive(ConversationContext<Ethernet_Frame> context) {
// TODO:- Do something here
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2497,4 +2497,9 @@ public int getCurLength() {

}

@Override
public void channelInactive(ConversationContext<TPKTPacket> context) {
tm.shutdown();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ public void close(ConversationContext<TPKTPacket> context) {
tm.shutdown();
}

@Override
public void channelInactive(ConversationContext<TPKTPacket> context) {
tm.shutdown();
}

@Override
public void onConnect(ConversationContext<TPKTPacket> context) {
// Only the TCP transport supports login.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,19 @@ public ExpectRequestContext<T> expectRequest(Class<T> clazz, Duration timeout) {

@Override
public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
logger.trace("close.. context: {}", ctx.name());
super.close(ctx, promise);
timeoutManager.stop();
}

@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
logger.trace("channelInactive.. context: {}", ctx.name());
super.channelInactive(ctx);
this.protocolBase.channelInactive(new DefaultConversationContext<>(this::registerHandler, ctx, authentication, passive));
timeoutManager.stop();
}

@Override
protected void encode(ChannelHandlerContext channelHandlerContext, Object msg, List<Object> list) throws Exception {
logger.debug("Forwarding request to plc {}", msg);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,6 @@ public CompletableFuture<PlcBrowseResponse> browseWithInterceptor(PlcBrowseReque

public abstract void close(ConversationContext<T> context);

public abstract void channelInactive(ConversationContext<T> context);

}
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,14 @@ class DefaultNettyPlcConnectionTest {
final GateKeeper connect = new GateKeeper("connect");
final GateKeeper disconnect = new GateKeeper("disconnect");
final GateKeeper close = new GateKeeper("close");
final GateKeeper channelInactive = new GateKeeper("channelInactive");

@Test
void checkInitializationSequence() throws Exception {
ChannelFactory channelFactory = new TestChannelFactory();

ProtocolStackConfigurer<Message> stackConfigurer = (configuration, pipeline, authentication, passive, listeners) -> {
TestProtocolBase base = new TestProtocolBase(discovery, connect, disconnect, close);
TestProtocolBase base = new TestProtocolBase(discovery, connect, disconnect, close, channelInactive);
Plc4xNettyWrapper<Message> context = new Plc4xNettyWrapper<>(new NettyHashTimerTimeoutManager(), pipeline, passive, base, authentication, Message.class);
pipeline.addLast(context);
return base;
Expand Down Expand Up @@ -153,12 +154,14 @@ static class TestProtocolBase extends Plc4xProtocolBase<Message> {
private final GateKeeper connect;
private final GateKeeper close;
private final GateKeeper disconnect;
private final GateKeeper channelInactive;

public TestProtocolBase(GateKeeper discover, GateKeeper connect, GateKeeper disconnect, GateKeeper close) {
public TestProtocolBase(GateKeeper discover, GateKeeper connect, GateKeeper disconnect, GateKeeper close, GateKeeper channelInactive) {
this.discover = discover;
this.connect = connect;
this.close = close;
this.disconnect = disconnect;
this.channelInactive = channelInactive;
}

@Override
Expand Down Expand Up @@ -200,6 +203,14 @@ public void close(ConversationContext<Message> context) {
close.reportExit();
}

@Override
public void channelInactive(ConversationContext<Message> context) {
logger.trace("On ChannelInactive");
channelInactive.permitEntry();
awaitIn(channelInactive);
channelInactive.reportExit();
}

private void awaitIn(GateKeeper signal) {
try {
if (!signal.awaitIn()) {
Expand Down
Loading