西门子PLC支持以太网通信,常用的通信协议是S7协议,S7协议本质上也属于TCP/IP的一种,TCP/IP通信一般是基于Socket实现的,考虑到Socket通信是I/O阻塞模式,在连接大批量PLC节点是效率不佳的,Netty是基于NIO基础上的封装,是一个高性能NIO框架,项目中立库需要连接上百个点位的PLC,由此引入Netty实现。
支持的西门子PLC版本:S200、S300 、S400 、S200Smart 、S1200 、S1500
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.10.Final</version>
</dependency>
Netty连接PLC核心类SiemensNet
public class SiemensNet {
private static final Logger log = LoggerFactory.getLogger(SiemensNet.class);
private String plcIP = "127.0.0.1";
private Bootstrap bootstrap = new Bootstrap();
private EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
private SiemensHandler siemensHandler = new SiemensHandler();
private String errorMsg = "msg";
public SiemensNet(SiemensPLCS type) {
this.siemensHandler.setHead(type);
this.bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);
((((this.bootstrap.group(this.eventLoopGroup))
.channel(NioSocketChannel.class))
.option(ChannelOption.TCP_NODELAY, true))
.option(ChannelOption.SO_KEEPALIVE, true))
.handler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline().addLast("SiemensDecoder", new SiemensDecoder());
channel.pipeline().addLast("SiemensEncoder", new SiemensEncoder());
channel.pipeline().addLast("SiemensHandler", SiemensNet.this.siemensHandler);
}
});
}
public boolean isConnectd() {
boolean isOk = false;
if (this.siemensHandler != null) {
isOk = this.siemensHandler.isConnectd();
}
return isOk;
}
public void connect() {
if (!StringUtil.isNullOrEmpty(this.plcIP)) {
this.connect(this.plcIP);
}
}
public void connect(String host) {
if (!this.isConnectd()) {
this.plcIP = host;
try {
ChannelFuture channelFuture = this.bootstrap.connect(host, 102);
channelFuture.awaitUninterruptibly();
if (!channelFuture.isSuccess()) {
this.showErrorMsg("连接PLC [" + host + "]超时!");
return;
}
try {
Thread.sleep(1500L);
} catch (InterruptedException var4) {
this.showErrorMsg("connect:" + var4.getMessage());
}
} catch (Exception var5) {
var5.printStackTrace();
}
}
}
public void close() {
if (this.eventLoopGroup != null) {
this.eventLoopGroup.shutdownGracefully();
}
log.info("关闭连接,释放对象!");
}
public ResultData read(String address) {
return this.readBytes(address, 0);
}
public ResultData readBytes(String address, int count) {
ResultData rd = new ResultData();
rd.setConnecd(false);
rd.setSuccess(false);
rd.setMessage("当前与PLC通信断开!");
if (this.isConnectd()) {
SiemensMessage msg = Utility.getAddress(address);
if (msg == null) {
rd.setMessage("请输入正确的PLC地址!");
return rd;
}
try {
msg.setType(CommandType.Read);
if (count > 0) {
if (msg.getDataType() != DataType.Byte) {
rd.setSuccess(false);
rd.setMessage("对不起,请输入Byte类型地址!");
return rd;
}
msg.setDataType(DataType.Bytes);
msg.setDataLength(count);
} else {
msg.setDataLength(msg.getDataType().getValue());
}
SiemensMessage result = this.send(msg);
if (result.isSuccess()) {
Object value = this.getValue(result.getDataValue(), result.getDataType(), result.getDataLength());
rd.setValue(value);
}
rd.setSuccess(result.isSuccess());
if (this.isConnectd()) {
rd.setMessage(result.getMessage());
}
rd.setConnecd(this.isConnectd());
} catch (Exception var7) {
rd.setMessage(var7.getMessage());
}
}
return rd;
}
private Object getValue(ByteBuf in, DataType type, int dataLength) {
Object value = null;
switch(type) {
case Bit:
value = in.readByte() == 1;
break;
case Byte:
value = in.readByte();
break;
case Word:
value = in.readShort();
break;
case Dint:
value = in.readInt();
break;
case Long:
value = in.readLong();
break;
case Bytes:
byte[] buffer = new byte[dataLength];
in.readBytes(buffer);
value = buffer;
}
ReferenceCountUtil.release(in);
return value;
}
public ResultData write(String address, boolean value) {
return this.write(address, (byte)(value ? 1 : 0));
}
public ResultData write(String address, byte value) {
ByteBuf buf = Unpooled.buffer(1);
buf.writeByte(value);
return this.write(address, buf);
}
public ResultData write(String address, short value) {
ByteBuf buf = Unpooled.buffer(2);
buf.writeShort(value);
return this.write(address, buf);
}
public ResultData write(String address, int value) {
ByteBuf buf = Unpooled.buffer(4);
buf.writeInt(value);
return this.write(address, buf);
}
public ResultData write(String address, long value) {
ByteBuf buf = Unpooled.buffer(4);
buf.writeLong(value);
return this.write(address, buf);
}
public ResultData write(String address, byte[] value) {
ByteBuf buf = Unpooled.wrappedBuffer(value);
return this.write(address, buf);
}
private ResultData write(String address, ByteBuf value) {
ResultData rd = new ResultData();
rd.setConnecd(false);
rd.setSuccess(false);
rd.setMessage("当前与PLC通信断开!");
if (this.isConnectd()) {
rd.setConnecd(true);
SiemensMessage msg = Utility.getAddress(address);
if (msg == null) {
rd.setMessage("当前PLC地址不正确!");
return rd;
}
msg.setType(CommandType.Write);
msg.setDataValue(value);
msg.setDataLength(value.writerIndex());
try {
SiemensMessage result = this.send(msg);
if (this.isConnectd()) {
rd.setMessage(result.getMessage());
}
rd.setSuccess(result.isSuccess());
rd.setValue(result.getDataValue());
rd.setConnecd(this.isConnectd());
} catch (Exception var6) {
rd.setMessage(var6.getMessage());
}
}
return rd;
}
private synchronized SiemensMessage send(SiemensMessage message) throws InterruptedException, ExecutionException {
ChannelPromise promise = this.siemensHandler.sendMessage(message);
promise.await(5L, TimeUnit.SECONDS);
return this.siemensHandler.getResponse();
}
private void showErrorMsg(String msg) {
if (!this.errorMsg.equals(msg)) {
this.errorMsg = msg;
log.error(this.errorMsg);
}
}
}
自定义处理器 SiemensHandler,解码器SiemensDecoder,加码器SiemensEncoder
不便对外提供,可自己实现,这部分可以参考开源框架HslCommunication
PLC连接类SiemensNet 调用流程(伪代码)
public SiemensNet siemensNet;//引入连接类
public void connectPLC() {
this.siemensNet = new SiemensNet("PLC版本枚举类");//连接类初始化
}
this.siemensNet.connect("127.0.0.1");//传入IP,端口默认是102,可不传
this.siemensNet.read("DB8.DBB1234");//传入PLC点位,读取值
this.siemensNet.write("DB8.DBB1234", 1);//传入PLC点位,写值
Java控制西门子PLC设备有以下方式:
- HslCommunication,基于S7协议,是个人开源,商业授权的框架,优势是封装了各种类型PLC设备的控制,功能最强大,C#版本更新最快,对C#项目非常友好,Java版本有点跟不上,个人也觉得有点臃肿;
- OPC UA,基于OPC协议,优势的传输效率高,是工控的标准协议,需要KEPServerEX软件,这个好像是商业的,缺点是KEPServerEX需作为OPC服务器,要配置每个PLC点位,相当于中间层,有点麻烦,目前我还在观望;
- S7connector,是GitHub上开源的框架,调用方式和Hsl类似,我暂没尝试
- Socket或Netty,完全自定义实现,缺点是S7协议是加密的,传输需解码加码,是我目前立库项目在用的调用方式,
-_-个人对这块非常感兴趣,有同方向的老铁们欢迎留言区沟通-_-