springboot 连接西门子plc,读取对应的值,并修改到数据库
需求:服务器连接plc,读取数据,之后写入到数据库,但是要求速度很快,而且plc中命令对应的值是不断变化的,这个变化,服务器要实时的看到;本地测试,可以通过博途
一、代码实现
1. maven依赖
java
<dependency>
<groupId>com.github.xingshuangs</groupId>
<artifactId>iot-communication</artifactId>
<version>1.4.4</version>
</dependency>
2. 入口函数
java
@SpringBootApplication
@MapperScan("com.gotion.modules.dao")
public class FamenYaokongApplication implements ApplicationRunner {
private static Logger logger = LoggerFactory.getLogger(FamenYaokongApplication.class);
public static void main(String[] args) {
SpringApplication.run(FamenYaokongApplication.class, args);
}
@Autowired
private OtherProgram otherProgram;
@Override
public void run(ApplicationArguments args) throws Exception {
// 创建子线程
Thread thread = new Thread(otherProgram);
// 这句话保证主线程停掉的时候,子线程持续运行
thread.setDaemon(true);
// 启动子线程
thread.start();
}
}
3. 子线程类
java
package com.gotion.modules.task;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.github.xingshuangs.iot.protocol.common.serializer.ByteArraySerializer;
import com.github.xingshuangs.iot.protocol.s7.enums.EPlcType;
import com.github.xingshuangs.iot.protocol.s7.service.S7PLC;
import com.gotion.modules.dao.SysPlcDao;
import com.gotion.modules.dao.SysPlcMlDao;
import com.gotion.modules.entity.SysPlcEntity;
import com.gotion.modules.entity.SysPlcMlEntity;
import com.gotion.modules.plc.PlcReadDataUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @Author: Administrator
* @Date: 2023/11/5
* @Description:
*/
@Service
public class OtherProgram implements Runnable {
private static Logger logger = LoggerFactory.getLogger(OtherProgram.class);
@Autowired
private SysPlcDao plcIpDao;
@Autowired
private SysPlcMlDao plcMlDao;
@Override
public void run() {
// 在这里编写子线程的执行逻辑
// 可以调用其他程序的入口方法或执行其他操作
logger.info("plc startTime:" + System.currentTimeMillis());
QueryWrapper<SysPlcEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.select("id", "ip_address").eq("is_flag", 1);
List<SysPlcEntity> ipList = plcIpDao.selectList(queryWrapper);
if (!ipList.isEmpty()) {
ipList.stream().forEach(plcIp -> {
// 建立连接
S7PLC s7PLC = new S7PLC(EPlcType.S1200, plcIp.getIpAddress()); // 使用plcIp的IP地址
while (true) {
// 构建序列化对象
ByteArraySerializer serializer = ByteArraySerializer.newInstance();
// 获取对应plc下db1的命令最大字节的数字
SysPlcMlEntity db1PlcMl = plcMlDao.getOneOrderByIdDescLimitOneAndIpIdAndContentLike(plcIp.getId(), "DB1");
SysPlcMlEntity db3PlcMl = plcMlDao.getOneOrderByIdDescLimitOneAndIpIdAndContentLike(plcIp.getId(), "DB3");
SysPlcMlEntity db4PlcMl = plcMlDao.getOneOrderByIdDescLimitOneAndIpIdAndContentLike(plcIp.getId(), "DB4");
SysPlcMlEntity db5PlcMl = plcMlDao.getOneOrderByIdDescLimitOneAndIpIdAndContentLike(plcIp.getId(), "DB5");
SysPlcMlEntity db6PlcMl = plcMlDao.getOneOrderByIdDescLimitOneAndIpIdAndContentLike(plcIp.getId(), "DB6");
SysPlcMlEntity db7PlcMl = plcMlDao.getOneOrderByIdDescLimitOneAndIpIdAndContentLike(plcIp.getId(), "DB7");
//-----------------读取plc1的字节 DBW两个字节,DBD4个字节 DBX/B一个字节 看最后一个以什么结尾,就在对应的字节数上添加2或者4或者1
// DB1地址范围是0~220+4 224
if (db1PlcMl != null) {
byte[] db1Datas = s7PLC.readByte("DB1.DBD0", PlcReadDataUtils.getMaxByteOffsite(db1PlcMl));
if (db1Datas != null && db1Datas.length > 0) {
//DB1数据块
PlcReadDataUtils.readData(plcIp.getId(), "DB1", serializer, db1Datas, plcMlDao);
}
}
// DB3地址范围是0~284+4 288 DBW两个字节,DBD4个字节 B一个字节
if (db3PlcMl != null) {
byte[] db3Datas = s7PLC.readByte("DB3.DBW0", PlcReadDataUtils.getMaxByteOffsite(db3PlcMl));
if (db3Datas != null && db3Datas.length > 0) {
//DB3数据块
PlcReadDataUtils.readData(plcIp.getId(), "DB3", serializer, db3Datas, plcMlDao);
}
}
if (db4PlcMl != null) {
// DB4地址范围是0~286 287
byte[] db4Datas = s7PLC.readByte("DB4.DBD0", PlcReadDataUtils.getMaxByteOffsite(db4PlcMl));
if (db4Datas != null && db4Datas.length>0) {
// DB4数据块
PlcReadDataUtils.readData(plcIp.getId(), "DB4", serializer, db4Datas, plcMlDao);
}
}
if (db5PlcMl != null) {
// DB5地址范围是0~374+2 376
byte[] db5Datas = s7PLC.readByte("DB5.DBX0.0", PlcReadDataUtils.getMaxByteOffsite(db5PlcMl));
if (db5Datas != null && db5Datas.length > 0) {
// DB5数据块
PlcReadDataUtils.readData(plcIp.getId(), "DB5", serializer, db5Datas, plcMlDao);
}
}
if (db6PlcMl != null) {
byte[] db6Datas = s7PLC.readByte("DB6.DBX0.0", PlcReadDataUtils.getMaxByteOffsite(db6PlcMl));
if (db6Datas != null && db6Datas.length >0) {
PlcReadDataUtils.readData(plcIp.getId(),"DB6", serializer, db6Datas, plcMlDao);
}
}
if (db7PlcMl != null) {
// DB7
byte[] db7Datas = s7PLC.readByte("DB7.DBX0.0", PlcReadDataUtils.getMaxByteOffsite(db7PlcMl));
if (db7Datas != null && db7Datas.length > 0) {
// DB7数据块
PlcReadDataUtils.readData(plcIp.getId(),"DB7", serializer, db7Datas, plcMlDao);
}
}
logger.info("plc endTime:" + System.currentTimeMillis());
}
});
}
}
}
- dao层和sql语句
java
/**
* 根据id和db块的命令查询最新的一条数据,目的:获取最大的命令值
* @param ipId
* @param mlContent
* @return
*/
SysPlcMlEntity getOneOrderByIdDescLimitOneAndIpIdAndContentLike(@Param("ipId") Long ipId, @Param("mlContent") String mlContent);
xml
<select id="getOneOrderByIdDescLimitOneAndIpIdAndContentLike" resultType="com.gotion.modules.entity.SysPlcMlEntity">
SELECT id,ip_id,type_id,name,ml_name,ml_content,ml_value FROM `sys_plc_ml`
<where>
<if test="ipId != null">
and ip_id = #{ipId}
</if>
<if test="mlContent != null and mlContent.trim() != ''">
and ml_content like concat(#{mlContent},'%')
</if>
</where>
ORDER BY id DESC LIMIT 1
</select>
- 工具类
java
package com.gotion.modules.plc;
import com.github.xingshuangs.iot.protocol.common.enums.EDataType;
import com.github.xingshuangs.iot.protocol.common.serializer.ByteArrayParameter;
import com.github.xingshuangs.iot.protocol.common.serializer.ByteArraySerializer;
import com.gotion.modules.dao.SysPlcMlDao;
import com.gotion.modules.entity.SysPlcMlEntity;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: Administrator
* @Date: 2023/11/5
* @Description:
*/
public class PlcReadDataUtils {
public static Integer getMaxByteOffsite(SysPlcMlEntity plcMl) {
int count = 0;
String[] mlContents = plcMl.getMlContent().split("\\.");
String byteOffset = mlContents[1].replaceAll("[^0-9]", "");
if (plcMl.getMlContent().contains("DBD")) {
count = Integer.valueOf(byteOffset) + 4;
} else if (plcMl.getMlContent().contains("DBW")) {
count = Integer.valueOf(byteOffset) + 2;
} else if (plcMl.getMlContent().contains("DBX")) {
count = Integer.valueOf(byteOffset) + 1;
}
return count;
}
public static List<ByteArrayParameter> readData(Long ipId, String dbNumber, ByteArraySerializer serializer, byte[] datas, SysPlcMlDao plcMlDao) {
// 查询plc1对应的db1的数据集合
List<SysPlcMlEntity> plcMlList = plcMlDao.getListByIpIdAndMlContentLike(ipId, dbNumber);
List<ByteArrayParameter> db1ParameterList = new ArrayList<>();
plcMlList.stream().forEach(plcMl-> {
String[] mlContents = plcMl.getMlContent().split("\\.");
String byteOffset = mlContents[1].replaceAll("[^0-9]", "");
// 先判断DB1.DBX0.0 第二个点后面有没有小数,没有就设置为0
String bitOffset = "0";
if (mlContents.length > 2) {
bitOffset = mlContents[2];
}
if (plcMl.getMlContent().contains("DBD")) {
db1ParameterList.add(new ByteArrayParameter(Integer.valueOf(byteOffset), Integer.valueOf(bitOffset), 1, EDataType.FLOAT32));
}
if (plcMl.getMlContent().contains("DBW")) {
db1ParameterList.add(new ByteArrayParameter(Integer.valueOf(byteOffset), Integer.valueOf(bitOffset), 1, EDataType.INT16));
}
if (plcMl.getMlContent().contains("DBX")) {
db1ParameterList.add(new ByteArrayParameter(Integer.valueOf(byteOffset), Integer.valueOf(bitOffset),1, EDataType.BOOL));
}
});
// 读取db1的 字节数据
List<ByteArrayParameter> byteList = serializer.extractParameter(db1ParameterList, datas);
for (int i = 0; i < plcMlList.size(); i++) {
plcMlList.get(i).setMlValue(byteList.get(i).getValue().toString());
}
plcMlDao.batchUpdateMlList(plcMlList);
return byteList;
}
}
- 数据库结构
二、解释:
思路:有多个plc,先查出plc的数量,根据plc地址创建连接,然后根据命令读取值
DB5.DBW2.1
:数据块5,字节索引:2,位索引:1
DB4.DBW3
:数据块4,字节索引:3,位索引:0
DB5.DBD2.3
:数据块5,字节索引:2,位索引:3
DB5.DBD2
:数据块:5,字节索引:2,位索引:0
DB4.DBX1.1
:数据块:4,字节索引:1,位索引:1
DB4.DBX1
:数据块:4,字节索引:1,位索引:0
每个命令后面的数据分别代表字节索引和位索引
我代码的逻辑:
(1)获取最大的字节数:先查询出每个plc中,每个db块的最大的字节索引,命令是连续的,我就从数据库中倒叙查询每个plc中每个db块,然后取一条数据;
SysPlcMlEntity db1PlcMl = plcMlDao.getOneOrderByIdDescLimitOneAndIpIdAndContentLike(plcIp.getId(), "DB1");
(2)因为每个db块的初始字节索引和位索引都是0,所以代码中直接写死初始值,根据初始值和每个db块的数量读取plc字节数组;
byte[] db1Datas = s7PLC.readByte("DB1.DBD0", PlcReadDataUtils.getMaxByteOffsite(db1PlcMl));
(3)解析读取到的字节数组:
① 由于要读取字节数组,同时把获取到的值写进数据库,所以我需要根据ip和db块查询数据库对应的集合数据:
List<SysPlcMlEntity> plcMlList = plcMlDao.getListByIpIdAndMlContentLike(ipId, dbNumber);
②循环集合plcMlList
,然后获取单个命令,截取后边的字节索引和位索引,然后根据字节索引和位索引读取每个命令得到对象ByteArrayParameter
,添加到集合List中
③读取数据:根据字节数组和对象集合读取数据
List<ByteArrayParameter> byteList = serializer.extractParameter(db1ParameterList, datas);
④赋值,修改数据库
通过fori
循环,将获取的值赋值给每个对象,最后修改数据。由于我们解析字节获取的值和我们查询数据库的值的长度是一样的,所以循环一个集合的长度就可以的。