All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.digitalpetri.enip.EtherNetIpClient Maven / Gradle / Ivy
package com.digitalpetri.enip;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import com.digitalpetri.enip.commands.Command;
import com.digitalpetri.enip.commands.CommandCode;
import com.digitalpetri.enip.commands.ListIdentity;
import com.digitalpetri.enip.commands.SendRRData;
import com.digitalpetri.enip.commands.SendUnitData;
import com.digitalpetri.enip.cpf.ConnectedDataItemResponse;
import com.digitalpetri.enip.cpf.CpfPacket;
import com.digitalpetri.enip.cpf.UnconnectedDataItemResponse;
import com.google.common.collect.Maps;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.Timeout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class EtherNetIpClient {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final ExecutorService executor;
private final Map> pendingRequests = Maps.newConcurrentMap();
private final AtomicLong senderContext = new AtomicLong(0L);
private volatile long sessionHandle;
private final ChannelManager channelManager;
private final EtherNetIpClientConfig config;
public EtherNetIpClient(EtherNetIpClientConfig config) {
this.config = config;
executor = config.getExecutor();
channelManager = new ChannelManager(this);
}
public CompletableFuture connect() {
CompletableFuture future = new CompletableFuture<>();
channelManager.getChannel().whenComplete((ch, ex) -> {
if (ch != null) future.complete(EtherNetIpClient.this);
else future.completeExceptionally(ex);
});
return future;
}
public CompletableFuture disconnect() {
channelManager.disconnect();
return CompletableFuture.completedFuture(this);
}
public String getState() {
return channelManager.getState();
}
public CompletableFuture listIdentity() {
return sendCommand(new ListIdentity());
}
public CompletableFuture sendRRData(SendRRData command) {
return sendCommand(command);
}
public CompletableFuture sendUnitData(SendUnitData command) {
CompletableFuture future = new CompletableFuture<>();
channelManager.getChannel().whenComplete((ch, ex) -> {
if (ch != null) {
EnipPacket packet = new EnipPacket(
command.getCommandCode(),
sessionHandle,
EnipStatus.EIP_SUCCESS,
0L,
command);
ch.writeAndFlush(packet).addListener(f -> {
if (f.isSuccess()) future.complete(null);
else future.completeExceptionally(f.cause());
});
} else {
future.completeExceptionally(ex);
}
});
return future;
}
public EtherNetIpClientConfig getConfig() {
return config;
}
public ExecutorService getExecutor() {
return executor;
}
protected CompletableFuture sendCommand(Command command) {
CompletableFuture future = new CompletableFuture<>();
channelManager.getChannel().whenComplete((ch, ex) -> {
if (ch != null) writeCommand(ch, command, future);
else future.completeExceptionally(ex);
});
return future;
}
protected void writeCommand(Channel channel,
Command command,
CompletableFuture future) {
EnipPacket packet = new EnipPacket(
command.getCommandCode(),
sessionHandle,
EnipStatus.EIP_SUCCESS,
senderContext.getAndIncrement(),
command
);
Timeout timeout = config.getWheelTimer().newTimeout(tt -> {
if (tt.isCancelled()) return;
PendingRequest> p = pendingRequests.remove(packet.getSenderContext());
if (p != null) {
String message = String.format("senderContext=%s timed out waiting %sms for response",
packet.getSenderContext(), config.getTimeout().toMillis());
p.promise.completeExceptionally(new Exception(message));
}
}, config.getTimeout().toMillis(), TimeUnit.MILLISECONDS);
pendingRequests.put(packet.getSenderContext(), new PendingRequest<>(future, timeout));
channel.writeAndFlush(packet).addListener(f -> {
if (!f.isSuccess()) {
PendingRequest pending = pendingRequests.remove(packet.getSenderContext());
if (pending != null) {
pending.timeout.cancel();
pending.promise.completeExceptionally(f.cause());
}
}
});
}
private void onChannelRead(EnipPacket packet) {
CommandCode commandCode = packet.getCommandCode();
if (commandCode == CommandCode.SendUnitData) {
onUnitDataReceived((SendUnitData) packet.getCommand());
} else {
if (commandCode == CommandCode.RegisterSession) {
EnipStatus status = packet.getStatus();
if (status == EnipStatus.EIP_SUCCESS) {
sessionHandle = packet.getSessionHandle();
} else {
sessionHandle = 0L;
}
}
PendingRequest> pending = pendingRequests.remove(packet.getSenderContext());
if (pending != null) {
pending.timeout.cancel();
EnipStatus status = packet.getStatus();
if (status == EnipStatus.EIP_SUCCESS) {
pending.promise.complete(packet.getCommand());
} else {
pending.promise.completeExceptionally(new Exception("EtherNet/IP status: " + status));
}
} else {
logger.debug("Received response for unknown context: {}", packet.getSenderContext());
if (packet.getCommand() instanceof SendRRData) {
CpfPacket cpfPacket = ((SendRRData) packet.getCommand()).getPacket();
Arrays.stream(cpfPacket.getItems()).forEach(item -> {
if (item instanceof ConnectedDataItemResponse) {
ReferenceCountUtil.safeRelease(((ConnectedDataItemResponse) item).getData());
} else if (item instanceof UnconnectedDataItemResponse) {
ReferenceCountUtil.safeRelease(((UnconnectedDataItemResponse) item).getData());
}
});
}
}
}
}
private void onChannelInactive(ChannelHandlerContext ctx) {
logger.debug("onChannelInactive() {} <-> {}",
ctx.channel().localAddress(), ctx.channel().remoteAddress());
}
private void onExceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
logger.debug("onExceptionCaught() {} <-> {}",
ctx.channel().localAddress(), ctx.channel().remoteAddress(), cause);
channelManager.disconnect();
}
/**
* Subclasses can override this to handle incoming
* {@link com.digitalpetri.enip.commands.SendUnitData} commands.
*
* @param command the {@link com.digitalpetri.enip.commands.SendUnitData} command received.
*/
protected void onUnitDataReceived(SendUnitData command) {
}
private static final class EtherNetIpClientHandler extends SimpleChannelInboundHandler {
private final ExecutorService executor;
private final EtherNetIpClient client;
private EtherNetIpClientHandler(EtherNetIpClient client) {
this.client = client;
executor = client.getExecutor();
}
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, EnipPacket packet) throws Exception {
executor.execute(() -> client.onChannelRead(packet));
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
client.onChannelInactive(ctx);
super.channelInactive(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
client.onExceptionCaught(ctx, cause);
}
}
public static CompletableFuture bootstrap(EtherNetIpClient client) {
CompletableFuture future = new CompletableFuture<>();
EtherNetIpClientConfig config = client.getConfig();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(config.getEventLoop())
.channel(NioSocketChannel.class)
.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, (int) config.getTimeout().toMillis())
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new EnipCodec());
ch.pipeline().addLast(new EtherNetIpClientHandler(client));
}
});
config.getBootstrapConsumer().accept(bootstrap);
bootstrap.connect(config.getHostname(), config.getPort())
.addListener((ChannelFuture f) -> {
if (f.isSuccess()) {
future.complete(f.channel());
} else {
future.completeExceptionally(f.cause());
}
});
return future;
}
private static final class PendingRequest {
private final CompletableFuture promise = new CompletableFuture<>();
private final Timeout timeout;
@SuppressWarnings("unchecked")
private PendingRequest(CompletableFuture future, Timeout timeout) {
this.timeout = timeout;
promise.whenComplete((r, ex) -> {
if (r != null) {
try {
future.complete((T) r);
} catch (ClassCastException e) {
future.completeExceptionally(e);
}
} else {
future.completeExceptionally(ex);
}
});
}
}
}