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.github.xingshuangs.iot.protocol.s7.service.PLCNetwork Maven / Gradle / Ivy
package com.github.xingshuangs.iot.protocol.s7.service;
import com.github.xingshuangs.iot.exceptions.S7CommException;
import com.github.xingshuangs.iot.net.client.TcpClientBasic;
import com.github.xingshuangs.iot.protocol.common.buff.ByteReadBuff;
import com.github.xingshuangs.iot.protocol.s7.algorithm.S7ComGroup;
import com.github.xingshuangs.iot.protocol.s7.algorithm.S7ComItem;
import com.github.xingshuangs.iot.protocol.s7.algorithm.S7SequentialGroupAlg;
import com.github.xingshuangs.iot.protocol.s7.constant.ErrorCode;
import com.github.xingshuangs.iot.protocol.s7.enums.*;
import com.github.xingshuangs.iot.protocol.s7.model.*;
import lombok.extern.slf4j.Slf4j;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
* plc的网络通信
* 最大读取字节数组大小是240-18=222,480-18=462,960-18=942
* 根据测试S1200[CPU 1214C],单次读多字节
* 发送:最大字节读取长度是 216 = 240 - 24, 24(请求报文的PDU)=10(header)+14(parameter)
* 接收:最大字节读取长度是 222 = 240 - 18, 18(响应报文的PDU)=12(header)+2(parameter)+4(dataItem)
* 根据测试S1200[CPU 1214C],单次写多字节
* 发送:最大字节写入长度是 212 = 240 - 28, 28(请求报文的PDU)=10(header)+14(parameter)+4(dataItem)
* 接收:最大字节写入长度是 225 = 240 - 15, 15(响应报文的PDU)=12(header)+2(parameter)+1(dataItem)
*
* @author xingshuang
*/
@SuppressWarnings("DuplicatedCode")
@Slf4j
public class PLCNetwork extends TcpClientBasic {
/**
* 锁
*/
private final Object objLock = new Object();
/**
* PLC的类型
*/
protected EPlcType plcType = EPlcType.S1200;
/**
* PLC机架号
*/
protected int rack = 0;
/**
* PLC槽号,S7-300 = 2
*/
protected int slot = 1;
/**
* 最大的PDU长度,不同PLC对应不同值,有240,480,960,目前默认240
*/
protected int pduLength;
/**
* 是否持久化,默认是持久化,对应长连接,true:长连接,false:短连接
*/
private boolean persistence = true;
/**
* 通信回调
*/
private Consumer comCallback;
public void setComCallback(Consumer comCallback) {
this.comCallback = comCallback;
}
public boolean isPersistence() {
return persistence;
}
public void setPersistence(boolean persistence) {
this.persistence = persistence;
}
public PLCNetwork() {
super();
}
public PLCNetwork(String host, int port) {
super(host, port);
}
//region socket连接后握手操作
/**
* 连接成功之后要做的动作
*/
@Override
protected void doAfterConnected() {
this.connectionRequest();
// 存在设置的PDULength != 实际PLC的PDULength,因此以PLC的为准
this.pduLength = this.connectDtData();
log.debug("PLC[{}]握手成功,机架号[{}],槽号[{}],PDU长度[{}]", this.plcType, this.rack, this.slot, this.pduLength);
}
/**
* 连接请求
* 1500 1200 300 400 200 200Smart
* 0x0102 0x0102 0x0102 0x0102 0x4d57 0x1000
* 0x0100 0x0100 0x0102 0x0103 0x4d57 0x0300
*/
private void connectionRequest() {
// 对应0xC1
int local = 0x0102;
// 对应0xC2 | 经测试:S1200支持0x0100、0x0200、0x0300,S200Smart支持0x0200、0x0300
int remote = 0x0100;
switch (this.plcType) {
case S200:
// S7net中写的是0x1000,0x1001
local = 0x4D57;
remote = 0x4D57;
break;
case S200_SMART:
local = 0x0100;
remote = 0x0300;
break;
case S300:
case S400:
case S1200:
case S1500:
remote += 0x20 * this.rack + this.slot;
break;
case SINUMERIK_828D:
local = 0x0400;
remote = 0x0D04;
remote += 0x20 * this.rack + this.slot;
break;
}
S7Data req = S7Data.createConnectRequest(local, remote);
S7Data ack = this.readFromServer(req);
if (ack.getCotp().getPduType() != EPduType.CONNECT_CONFIRM) {
throw new S7CommException("连接请求被拒绝");
}
}
/**
* 连接setup
*
* @return pduLength pdu长度
*/
private int connectDtData() {
S7Data req = S7Data.createConnectDtData(this.pduLength);
S7Data ack = this.readFromServer(req);
if (ack.getCotp().getPduType() != EPduType.DT_DATA) {
throw new S7CommException("连接Setup响应错误");
}
if (ack.getHeader() == null || ack.getHeader().byteArrayLength() != AckHeader.BYTE_LENGTH) {
throw new S7CommException("连接Setup响应错误,缺失响应头header或响应头长度不够[12]");
}
int length = ((SetupComParameter) ack.getParameter()).getPduLength();
if (length <= 0) {
throw new S7CommException("PDU的最大长度小于0");
}
return length;
}
//endregion
//region 底层数据通信部分
/**
* 从服务器读取数据
*
* @param req S7协议数据
* @return S7协议数据
*/
private S7Data readFromServer(S7Data req) {
byte[] sendData = req.toByteArray();
if (this.comCallback != null) {
this.comCallback.accept(sendData);
}
// 将报文中的TPKT和COTP减掉,剩下PDU的内容,7=4(tpkt)+3(cotp)
if (this.pduLength > 0 && sendData.length - 7 > this.pduLength) {
throw new S7CommException(String.format("发送请求的字节数过长[%d],已经大于最大的PDU长度[%d]", sendData.length, this.pduLength));
}
TPKT tpkt;
int len;
byte[] remain;
synchronized (this.objLock) {
this.write(sendData);
byte[] data = new byte[TPKT.BYTE_LENGTH];
len = this.read(data);
if (len < TPKT.BYTE_LENGTH) {
throw new S7CommException(" TPKT 无效,长度不一致");
}
tpkt = TPKT.fromBytes(data);
remain = new byte[tpkt.getLength() - TPKT.BYTE_LENGTH];
len = this.read(remain);
}
if (len < remain.length) {
throw new S7CommException(" TPKT后面的数据长度,长度不一致");
}
S7Data ack = S7Data.fromBytes(tpkt, remain);
if (this.comCallback != null) {
this.comCallback.accept(ack.toByteArray());
}
this.checkPostedCom(req, ack);
return ack;
}
private byte[] readFromServer(byte[] sendData) {
if (this.comCallback != null) {
this.comCallback.accept(sendData);
}
// 将报文中的TPKT和COTP减掉,剩下PDU的内容,7=4(tpkt)+3(cotp)
if (this.pduLength > 0 && sendData.length - 7 > this.pduLength) {
throw new S7CommException(String.format("发送请求的字节数过长[%d],已经大于最大的PDU长度[%d]", sendData.length, this.pduLength));
}
TPKT tpkt;
int len;
byte[] total;
synchronized (this.objLock) {
this.write(sendData);
byte[] data = new byte[TPKT.BYTE_LENGTH];
len = this.read(data);
if (len < TPKT.BYTE_LENGTH) {
throw new S7CommException(" TPKT 无效,长度不一致");
}
tpkt = TPKT.fromBytes(data);
total = new byte[tpkt.getLength()];
System.arraycopy(data, 0, total, 0, data.length);
len = this.read(total, TPKT.BYTE_LENGTH, tpkt.getLength() - TPKT.BYTE_LENGTH);
}
if (len < total.length - TPKT.BYTE_LENGTH) {
throw new S7CommException(" TPKT后面的数据长度,长度不一致");
}
if (this.comCallback != null) {
this.comCallback.accept(total);
}
return total;
}
/**
* 包含持久化的从服务器读取数据,外部继承使用该方法进行交互,内部不使用
*
* @param req 请求数据
* @return 响应数据
*/
public S7Data readFromServerByPersistence(S7Data req) {
try {
return this.readFromServer(req);
} finally {
if (!this.persistence) {
this.close();
}
}
}
/**
* 包含持久化的从服务器读取数据,外部继承使用该方法进行交互,内部不使用
*
* @param req 请求数据
* @return 响应数据
*/
public byte[] readFromServerByPersistence(byte[] req) {
try {
return this.readFromServer(req);
} finally {
if (!this.persistence) {
this.close();
}
}
}
/**
* 后置通信处理,对请求和响应数据进行一次校验
*
* @param req 请求数据
* @param ack 响应属于
*/
private void checkPostedCom(S7Data req, S7Data ack) {
if (ack.getHeader() == null) {
return;
}
// 响应头正确
AckHeader ackHeader = (AckHeader) ack.getHeader();
if (ackHeader.getErrorClass() != EErrorClass.NO_ERROR) {
throw new S7CommException(String.format("响应异常,错误类型:%s,错误原因:%s",
ackHeader.getErrorClass().getDescription(), ErrorCode.MAP.get(ackHeader.getErrorCode())));
}
// 发送和接收的PDU编号一致
if (ackHeader.getPduReference() != req.getHeader().getPduReference()) {
throw new S7CommException("pdu应用编号不一致,数据有误");
}
if (ack.getDatum() == null) {
return;
}
// 请求的数据个数一致
List returnItems = ack.getDatum().getReturnItems();
ReadWriteParameter parameter = (ReadWriteParameter) req.getParameter();
if (returnItems.size() != parameter.getItemCount()) {
throw new S7CommException("返回的数据个数和请求的数据个数不一致");
}
// 返回结果校验
returnItems.forEach(x -> {
if (x.getReturnCode() != EReturnCode.SUCCESS) {
throw new S7CommException(String.format("返回结果异常,原因:%s", x.getReturnCode().getDescription()));
}
});
}
//endregion
//region S7数据读写部分
/**
* 读取S7协议数据
*
* @param requestItems 请求项列表
* @return 数据项列表
*/
public List readS7Data(List requestItems) {
if (requestItems == null || requestItems.isEmpty()) {
throw new S7CommException("请求项缺失,无法获取数据");
}
// 根据原始请求列表提取每个请求数据大小
List rawNumbers = requestItems.stream().map(RequestItem::getCount).collect(Collectors.toList());
// 根据原始请求列表构建最终结果列表
List resultList = requestItems.stream().map(x -> DataItem.createReq(new byte[x.getCount()],
x.getVariableType() == EParamVariableType.BIT ? EDataVariableType.BIT : EDataVariableType.BYTE_WORD_DWORD))
.collect(Collectors.toList());
// 根据顺序分组算法得出分组结果,
// 发送: 12=10(header)+2(parameter前),12(parameter后)
// 接收: 14=12(header)+2(parameter),5(DataItem),dataItem可能4或5,统一采用5
List s7ComGroups = S7SequentialGroupAlg.readRecombination(rawNumbers, this.pduLength - 14, 5, 12);
try {
s7ComGroups.forEach(x -> {
// 根据分组构建对应的请求列表
List comItemList = x.getItems();
List newRequestItems = comItemList.stream().map(i -> {
RequestItem item = requestItems.get(i.getIndex()).copy();
item.setCount(i.getRipeSize());
item.setByteAddress(item.getByteAddress() + i.getSplitOffset());
return item;
}).collect(Collectors.toList());
// S7数据请求
S7Data req = S7Data.createReadRequest(newRequestItems);
S7Data ack = this.readFromServer(req);
List dataItems = ack.getDatum().getReturnItems().stream().map(DataItem.class::cast).collect(Collectors.toList());
// 将获取的数据重装实际结果列表中
for (int i = 0; i < comItemList.size(); i++) {
S7ComItem comItem = comItemList.get(i);
byte[] src = dataItems.get(i).getData();
byte[] des = resultList.get(comItem.getIndex()).getData();
System.arraycopy(src, 0, des, comItem.getSplitOffset(), src.length);
}
});
return resultList;
} finally {
if (!this.persistence) {
this.close();
}
}
}
/**
* 读取S7协议数据
*
* @param requestItem 请求项
* @return 数据项
*/
public DataItem readS7Data(RequestItem requestItem) {
return this.readS7Data(Collections.singletonList(requestItem)).get(0);
}
/**
* 写S7协议数据
*
* @param requestItem 请求项
* @param dataItem 数据项
*/
public void writeS7Data(RequestItem requestItem, DataItem dataItem) {
this.writeS7Data(Collections.singletonList(requestItem), Collections.singletonList(dataItem));
}
/**
* 写S7协议
*
* @param requestItems 请求项列表
* @param dataItems 数据项列表
*/
public void writeS7Data(List requestItems, List dataItems) {
if (requestItems.size() != dataItems.size()) {
throw new S7CommException("写操作过程中,requestItems和dataItems数据个数不一致");
}
// 根据原始请求列表提取每个请求数据大小
List rawNumbers = requestItems.stream().map(RequestItem::getCount).collect(Collectors.toList());
// 根据顺序分组算法得出分组结果
// 发送:12=10(header)+2(parameter前),17=12(parameter后)+5(dataItem),dataItem可能4或5,统一采用5
// 接收:14=12(header)+2(parameter),1(DataItem)
List s7ComGroups = S7SequentialGroupAlg.writeRecombination(rawNumbers, this.pduLength - 12, 17);
try {
s7ComGroups.forEach(x -> {
// 根据分组构建对应的请求列表
List comItemList = x.getItems();
List newRequestItems = comItemList.stream().map(i -> {
RequestItem item = requestItems.get(i.getIndex()).copy();
item.setCount(i.getRipeSize());
item.setByteAddress(item.getByteAddress() + i.getSplitOffset());
return item;
}).collect(Collectors.toList());
// 根据分组构建对应的数据列表
List newDataItems = comItemList.stream().map(i -> {
DataItem item = dataItems.get(i.getIndex()).copy();
item.setCount(i.getRipeSize());
item.setData(ByteReadBuff.newInstance(item.getData()).getBytes(i.getSplitOffset(), i.getRipeSize()));
return item;
}).collect(Collectors.toList());
// S7数据请求
S7Data req = S7Data.createWriteRequest(newRequestItems, newDataItems);
this.readFromServer(req);
});
} finally {
if (!this.persistence) {
this.close();
}
}
}
//endregion
//region 读取NCK数据
/**
* 读取S7协议NCK数据
*
* @param requestItem 请求项
* @return 数据项
*/
public DataItem readS7NckData(RequestNckItem requestItem) {
return this.readS7NckData(Collections.singletonList(requestItem)).get(0);
}
/**
* 读取S7协议NCK数据
*
* @param requestItems 请求项列表
* @return 数据项
*/
public List readS7NckData(List requestItems) {
S7Data s7Data = NckRequestBuilder.creatNckRequest(requestItems);
S7Data ack = this.readFromServerByPersistence(s7Data);
return ack.getDatum().getReturnItems().stream().map(DataItem.class::cast).collect(Collectors.toList());
}
//endregion
}