springboot 连接西门子plc,读取对应的值,并修改到数据库

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());
                }
            });
        }
    }
}
  1. 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>
  1. 工具类
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;
    }
}
  1. 数据库结构



二、解释:

思路:有多个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循环,将获取的值赋值给每个对象,最后修改数据。由于我们解析字节获取的值和我们查询数据库的值的长度是一样的,所以循环一个集合的长度就可以的。

相关推荐
milk_yan1 小时前
MinIO的安装与使用
linux·数据仓库·spring boot
程序员徐师兄3 小时前
Java 基于 SpringBoot 的校园外卖点餐平台微信小程序(附源码,部署,文档)
java·spring boot·微信小程序·校园外卖点餐·外卖点餐小程序·校园外卖点餐小程序
chengpei1473 小时前
chrome游览器JSON Formatter插件无效问题排查,FastJsonHttpMessageConverter导致Content-Type返回不正确
java·前端·chrome·spring boot·json
Q_27437851093 小时前
springboot基于微信小程序的周边游小程序
spring boot·微信小程序·小程序
计算机学姐4 小时前
基于微信小程序的民宿预订管理系统
java·vue.js·spring boot·后端·mysql·微信小程序·小程序
奈葵5 小时前
Spring Boot/MVC
java·数据库·spring boot
落霞的思绪5 小时前
Redis实战(黑马点评)——涉及session、redis存储验证码,双拦截器处理请求
spring boot·redis·缓存
liuyunshengsir6 小时前
Spring Boot 使用 Micrometer 集成 Prometheus 监控 Java 应用性能
java·spring boot·prometheus
何中应6 小时前
Spring Boot中选择性加载Bean的几种方式
java·spring boot·后端
2013crazy7 小时前
Java 基于 SpringBoot+Vue 的校园兼职平台(附源码、部署、文档)
java·vue.js·spring boot·兼职平台·校园兼职·兼职发布平台