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.
/*
* MIT License
*
* Copyright (c) 2021-2099 Oscura (xingshuang)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.xingshuangs.iot.protocol.s7.serializer;
import com.github.xingshuangs.iot.exceptions.S7CommException;
import com.github.xingshuangs.iot.common.buff.ByteReadBuff;
import com.github.xingshuangs.iot.common.buff.ByteWriteBuff;
import com.github.xingshuangs.iot.common.enums.EDataType;
import com.github.xingshuangs.iot.common.serializer.IPLCSerializable;
import com.github.xingshuangs.iot.protocol.s7.enums.EPlcType;
import com.github.xingshuangs.iot.protocol.s7.model.DataItem;
import com.github.xingshuangs.iot.protocol.s7.model.RequestItem;
import com.github.xingshuangs.iot.protocol.s7.service.S7PLC;
import com.github.xingshuangs.iot.protocol.s7.utils.AddressUtil;
import java.lang.reflect.Field;
import java.nio.charset.Charset;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* S7序列化工具
*
* @author xingshuang
*/
public class S7Serializer implements IPLCSerializable {
private final S7PLC s7PLC;
public S7Serializer(S7PLC s7PLC) {
this.s7PLC = s7PLC;
}
/**
* 静态方法实例对象
*
* @param s7PLC plc对象
* @return 对象实例
*/
public static S7Serializer newInstance(S7PLC s7PLC) {
return new S7Serializer(s7PLC);
}
/**
* 将类根据S7Variable注解转换为解析数据
*
* @param targetClass 目标类型
* @return 解析数据列表
*/
private List parseBean(final Class> targetClass) {
List s7ParseDataList = new ArrayList<>();
for (final Field field : targetClass.getDeclaredFields()) {
final S7Variable s7Variable = field.getAnnotation(S7Variable.class);
if (s7Variable == null) {
continue;
}
S7Parameter parameter = new S7Parameter(s7Variable.address(), s7Variable.type(), s7Variable.count());
S7ParseData s7ParseData = this.createS7ParseData(parameter, field);
s7ParseDataList.add(s7ParseData);
}
return s7ParseDataList;
}
/**
* 解析参数
*
* @param parameters 参数列表
* @return 解析列表
*/
private List parseBean(final List parameters) {
try {
List s7ParseDataList = new ArrayList<>();
for (final S7Parameter p : parameters) {
if (p == null) {
// parameters列表中存在null
throw new S7CommException("null exists in the parameters list");
}
S7ParseData s7ParseData = this.createS7ParseData(p, p.getClass().getDeclaredField("value"));
s7ParseDataList.add(s7ParseData);
}
return s7ParseDataList;
} catch (NoSuchFieldException e) {
throw new S7CommException(e);
}
}
/**
* 校验S7Variable的数据是否满足规则要求
*
* @param parameter parameter
*/
private void checkS7Variable(final S7Parameter parameter) {
if (parameter.getAddress().isEmpty()) {
// S7参数中[address]不能为空
throw new S7CommException("[address] in the S7 parameter cannot be empty");
}
if (parameter.getCount() < 0) {
// S7参数中[count]不能为负数
throw new S7CommException("[count] in the S7 parameter cannot be negative");
}
if (parameter.getDataType() == EDataType.STRING && parameter.getCount() > 254) {
// S7参数中字符串类型类型数据的[count]不能大于254
throw new S7CommException("The [count] value of the string type in the S7 parameter cannot be greater than 254");
}
if (parameter.getDataType() != EDataType.BYTE && parameter.getDataType() != EDataType.STRING && parameter.getCount() > 1) {
// S7参数中只有[type]=字节和字符串类型数据的[count]才能大于1,其他必须等于1
throw new S7CommException("In the S7 parameter, only [type]= bytes and [count] of string type data can be greater than 1, and the rest must be equal to 1");
}
}
/**
* 根据条件读取数据
*
* @param s7ParseDataList 条件
*/
private void readDataByCondition(List s7ParseDataList) {
if (s7ParseDataList.isEmpty()) {
// 解析出的注解数据个数为空,无法读取数据
throw new S7CommException("The number of parsed annotation data is empty, and the data cannot be read");
}
// 读取PLC数据
List requestItems = s7ParseDataList.stream().map(S7ParseData::getRequestItem).collect(Collectors.toList());
List dataItems = this.s7PLC.readS7Data(requestItems);
if (s7ParseDataList.size() != dataItems.size()) {
// 所需的字段解析项个数与返回的数据项数量不一致,错误
throw new S7CommException("The number of field parsing items required is inconsistent with the number of data items returned");
}
// 提取数据
for (int i = 0; i < dataItems.size(); i++) {
s7ParseDataList.get(i).setDataItem(dataItems.get(i));
}
}
/**
* 构建S7ParseData数据
*
* @param p S7Parameter数据
* @param field 对应字段
* @return S7ParseData数据
*/
private S7ParseData createS7ParseData(S7Parameter p, Field field) {
this.checkS7Variable(p);
// 组装S7解析数据
S7ParseData s7ParseData = new S7ParseData();
s7ParseData.setDataType(p.getDataType());
s7ParseData.setCount(p.getCount());
s7ParseData.setField(field);
if (p.getDataType() == EDataType.BOOL) {
s7ParseData.setRequestItem(AddressUtil.parseBit(p.getAddress()));
} else if (p.getDataType() == EDataType.STRING) {
RequestItem requestItem = AddressUtil.parseByte(p.getAddress(), 1 + p.getCount() * p.getDataType().getByteLength());
// 为什么字节索引+1,为了避免修改PLC中string[60]类型的第一个字节数据,该数据为字符串的允许最大长度
// S1200(非S200Smart):数据类型为 string 的操作数可存储多个字符,最多可包括 254 个字符。字符串中的第一个字节为总长度,第二个字节为有效字符数量。
// S200SMART:字符串由变量存储时,字符串长度为0至254个字符,最长为255个字节,其中第一个字符为长度字节
int offset = this.s7PLC.getPlcType() == EPlcType.S200_SMART ? 0 : 1;
requestItem.setByteAddress(requestItem.getByteAddress() + offset);
s7ParseData.setRequestItem(requestItem);
} else {
s7ParseData.setRequestItem(AddressUtil.parseByte(p.getAddress(), p.getCount() * p.getDataType().getByteLength()));
}
return s7ParseData;
}
// region read
@Override
public T read(Class targetClass) {
List s7ParseDataList = this.parseBean(targetClass);
this.readDataByCondition(s7ParseDataList);
return this.fillData(targetClass, s7ParseDataList);
}
/**
* 读取数据
*
* @param parameters 参数配置列表
* @return 结果列表
*/
public List read(List parameters) {
List s7ParseDataList = this.parseBean(parameters);
this.readDataByCondition(s7ParseDataList);
this.fillData(parameters, s7ParseDataList);
return parameters;
}
/**
* 提取数据
*
* @param targetClass 目标类型
* @param s7ParseDataList S7解析数据列表
* @param 类型
* @return 目标类型的实体对象
*/
private T fillData(Class targetClass, List s7ParseDataList) {
try {
final T result = targetClass.newInstance();
for (S7ParseData item : s7ParseDataList) {
this.fillField(result, item);
}
return result;
} catch (Exception e) {
throw new S7CommException("Serialization fetch data error:" + e.getMessage(), e);
}
}
/**
* 填充数据
*
* @param parameters 参数列表
* @param s7ParseDataList 解析列表
*/
private void fillData(final List parameters, final List s7ParseDataList) {
try {
for (int i = 0; i < s7ParseDataList.size(); i++) {
S7ParseData item = s7ParseDataList.get(i);
S7Parameter parameter = parameters.get(i);
this.fillField(parameter, item);
}
} catch (Exception e) {
throw new S7CommException("Serialization fetch data error:" + e.getMessage(), e);
}
}
/**
* 填充字段数据
*
* @param result 参数对象
* @param item 数据对象
* @param 泛型
* @throws IllegalAccessException 异常
*/
private void fillField(T result, S7ParseData item) throws IllegalAccessException {
ByteReadBuff buff = new ByteReadBuff(item.getDataItem().getData());
item.getField().setAccessible(true);
switch (item.getDataType()) {
case BOOL:
item.getField().set(result, buff.getBoolean(0));
break;
case BYTE:
item.getField().set(result, buff.getBytes(item.getCount()));
break;
case UINT16:
item.getField().set(result, buff.getUInt16());
break;
case INT16:
item.getField().set(result, buff.getInt16());
break;
case TIME:
case UINT32:
item.getField().set(result, buff.getUInt32());
break;
case INT32:
item.getField().set(result, buff.getInt32());
break;
case FLOAT32:
item.getField().set(result, buff.getFloat32());
break;
case FLOAT64:
item.getField().set(result, buff.getFloat64());
break;
case STRING:
int length = buff.getByteToInt(0);
item.getField().set(result, buff.getString(1, Math.min(length, item.getCount()), Charset.forName("GB2312")));
break;
case DATE:
LocalDate date = LocalDate.of(1990, 1, 1).plusDays(buff.getUInt16());
item.getField().set(result, date);
break;
case TIME_OF_DAY:
LocalTime time = LocalTime.ofSecondOfDay(buff.getUInt32() / 1000);
item.getField().set(result, time);
break;
case DTL:
int year = buff.getUInt16();
int month = buff.getByteToInt();
int dayOfMonth = buff.getByteToInt();
int week = buff.getByteToInt();
int hour = buff.getByteToInt();
int minute = buff.getByteToInt();
int second = buff.getByteToInt();
long nanoOfSecond = buff.getUInt32();
LocalDateTime dateTime = LocalDateTime.of(year, month, dayOfMonth, hour, minute, second, (int) nanoOfSecond);
item.getField().set(result, dateTime);
break;
default:
throw new S7CommException("Data type not recognized");
}
}
// endregion
// region write
@Override
public void write(T targetBean) {
// 解析参数
List s7ParseDataList = this.parseBean(targetBean.getClass());
if (s7ParseDataList.isEmpty()) {
// 解析出的注解数据个数为空,无法读取数据
throw new S7CommException("The number of parsed annotation data is empty, and the data cannot be read");
}
// 填充字节数据
s7ParseDataList = this.extractData(targetBean, s7ParseDataList);
// 写入PLC
List requestItems = s7ParseDataList.stream().map(S7ParseData::getRequestItem).collect(Collectors.toList());
List dataItems = s7ParseDataList.stream().map(S7ParseData::getDataItem).collect(Collectors.toList());
this.s7PLC.writeS7Data(requestItems, dataItems);
}
/**
* 写入数据
*
* @param parameters 参数列表
*/
public void write(List parameters) {
// 解析参数
List s7ParseDataList = this.parseBean(parameters);
if (s7ParseDataList.size() != parameters.size()) {
// 解析出的数据个数与传入的数据个数不一致
throw new S7CommException("The number of parsed data is inconsistent with the number of incoming data");
}
// 填充字节数据
s7ParseDataList = this.extractData(parameters, s7ParseDataList);
// 写入PLC
List requestItems = s7ParseDataList.stream().map(S7ParseData::getRequestItem).collect(Collectors.toList());
List dataItems = s7ParseDataList.stream().map(S7ParseData::getDataItem).collect(Collectors.toList());
this.s7PLC.writeS7Data(requestItems, dataItems);
}
/**
* 提取数据
*
* @param targetBean 目标对象
* @param s7ParseDataList 解析后的数据列表
* @param 类型
* @return 有效的解析后的数据列表
*/
private List extractData(T targetBean, List s7ParseDataList) {
try {
for (S7ParseData item : s7ParseDataList) {
item.getField().setAccessible(true);
Object data = item.getField().get(targetBean);
if (data == null) {
continue;
}
this.extractField(item, data);
}
return s7ParseDataList.stream().filter(x -> x.getDataItem() != null).collect(Collectors.toList());
} catch (Exception e) {
// 序列化填充字节数据错误
throw new S7CommException("Serialized fill byte data error:" + e.getMessage(), e);
}
}
/**
* 提取数据
*
* @param parameters 参数数据列表
* @param s7ParseDataList 解析后的数据列表
* @return 有效的解析后的数据列表
*/
private List extractData(List parameters, List s7ParseDataList) {
try {
for (int i = 0; i < s7ParseDataList.size(); i++) {
S7ParseData item = s7ParseDataList.get(i);
S7Parameter parameter = parameters.get(i);
if (parameter.getValue() == null) {
throw new S7CommException("The value of the parameter cannot be null");
}
this.extractField(item, parameter.getValue());
}
return s7ParseDataList.stream().filter(x -> x.getDataItem() != null).collect(Collectors.toList());
} catch (Exception e) {
// 序列化填充字节数据错误
throw new S7CommException("Serialized fill byte data error:" + e.getMessage(), e);
}
}
/**
* 提取字段数
*
* @param item S7的解析数据
* @param data 数据源
*/
private void extractField(S7ParseData item, Object data) {
switch (item.getDataType()) {
case BOOL:
item.setDataItem(DataItem.createReqByBoolean((Boolean) data));
break;
case BYTE:
item.setDataItem(DataItem.createReqByByte(ByteReadBuff.newInstance((byte[]) data)
.getBytes(item.getCount())));
break;
case UINT16:
item.setDataItem(DataItem.createReqByByte(ByteWriteBuff.newInstance(2)
.putShort((Integer) data).getData()));
break;
case INT16:
item.setDataItem(DataItem.createReqByByte(ByteWriteBuff.newInstance(2)
.putShort((Short) data).getData()));
break;
case TIME:
case UINT32:
item.setDataItem(DataItem.createReqByByte(ByteWriteBuff.newInstance(4)
.putInteger((Long) data).getData()));
break;
case INT32:
item.setDataItem(DataItem.createReqByByte(ByteWriteBuff.newInstance(4)
.putInteger((Integer) data).getData()));
break;
case FLOAT32:
item.setDataItem(DataItem.createReqByByte(ByteWriteBuff.newInstance(4)
.putFloat((Float) data).getData()));
break;
case FLOAT64:
item.setDataItem(DataItem.createReqByByte(ByteWriteBuff.newInstance(8)
.putDouble((Double) data).getData()));
break;
case STRING:
byte[] bytes = ((String) data).getBytes(Charset.forName("GB2312"));
int actualLength = Math.min(bytes.length, item.getCount());
byte[] targetBytes = new byte[1 + actualLength];
targetBytes[0] = (byte) actualLength;
System.arraycopy(bytes, 0, targetBytes, 1, actualLength);
item.setDataItem(DataItem.createReqByByte(targetBytes));
// 根据实际情况获取最小字符串长度+1,重新更新待写入的数据长度
item.getRequestItem().setCount(targetBytes.length);
break;
case DATE:
// TODO: 后面时间采用工具类
LocalDate start = LocalDate.of(1990, 1, 1);
long date = ((LocalDate) data).toEpochDay() - start.toEpochDay();
item.setDataItem(DataItem.createReqByByte(ByteWriteBuff.newInstance(2)
.putShort((short) date).getData()));
break;
case TIME_OF_DAY:
long timeOfDay = ((LocalTime) data).toSecondOfDay() * 1000L;
item.setDataItem(DataItem.createReqByByte(ByteWriteBuff.newInstance(4)
.putInteger(timeOfDay).getData()));
break;
case DTL:
LocalDateTime dateTime = (LocalDateTime) data;
byte[] dateTimeData = ByteWriteBuff.newInstance(12)
.putShort(dateTime.getYear())
.putByte(dateTime.getMonthValue())
.putByte(dateTime.getDayOfMonth())
.putByte(dateTime.getDayOfWeek().getValue())
.putByte(dateTime.getHour())
.putByte(dateTime.getMinute())
.putByte(dateTime.getSecond())
.putInteger(dateTime.getNano())
.getData();
item.setDataItem(DataItem.createReqByByte(dateTimeData));
break;
default:
// 无法识别数据类型
throw new S7CommException("Data type not recognized");
}
}
// endregion
}