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

news2025/1/16 0:53:38

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

需求:服务器连接plc,读取数据,之后写入到数据库,但是要求速度很快,而且plc中命令对应的值是不断变化的,这个变化,服务器要实时的看到;本地测试,可以通过博途
一、代码实现
1. maven依赖
<dependency>
   <groupId>com.github.xingshuangs</groupId>
    <artifactId>iot-communication</artifactId>
    <version>1.4.4</version>
</dependency>
2. 入口函数
@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. 子线程类
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语句
/**
* 根据id和db块的命令查询最新的一条数据,目的:获取最大的命令值
* @param ipId
* @param mlContent
* @return
*/
SysPlcMlEntity getOneOrderByIdDescLimitOneAndIpIdAndContentLike(@Param("ipId") Long ipId, @Param("mlContent") String mlContent);
<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. 工具类
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循环,将获取的值赋值给每个对象,最后修改数据。由于我们解析字节获取的值和我们查询数据库的值的长度是一样的,所以循环一个集合的长度就可以的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1174640.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python基础入门例程46-NP46 菜品的价格(条件语句)

最近的博文&#xff1a; Python基础入门例程45-NP45 禁止重复注册&#xff08;条件语句&#xff09;-CSDN博客 Python基础入门例程44-NP44 判断列表是否为空&#xff08;条件语句&#xff09;-CSDN博客 Python基础入门例程43-NP43 判断布尔值&#xff08;条件语句&#xff0…

[原创]Cadence17.4,win64系统,构建CIS库

目录 1、背景介绍 2、具体操作流程 3、遇到问题、分析鉴别问题、解决问题 4、借鉴链接并评论 1、背景介绍 CIS库&#xff0c;绘制原理图很方便&#xff0c;但是需要在Cadence软件与数据库之间建立联系&#xff0c;但是一直不成功&#xff0c;花费半天时间才搞明白如何建立关系并…

思维模型 门槛效应/登门槛效应

本系列文章 主要是 分享 思维模型&#xff0c;涉及各个领域&#xff0c;重在提升认知。跨过一个个门槛&#xff0c;走向你该走向的“深渊”和“光明”。 说明&#xff1a;后面 门槛效应/登门槛效应 均使用门槛效应替代 1 门槛效应的应用 1.1 营销策略中的门槛效应 免费试用&…

二维码智慧门牌管理系统升级:一键报警让你的生活更安全!

文章目录 前言一、升级解决方案的特点二、实施步骤 前言 随着科技的不断进步&#xff0c;我们的生活正在逐渐变得更加智能化。可以想象一下&#xff0c;如果你家的门牌也能拥有这种智能升级&#xff0c;将会带来怎样的改变&#xff1f;今天&#xff0c;让我们一起探讨这令人兴…

【Windows】Google和火狐浏览器禁用更新的操作方式

想必很多网民常用的浏览器是Edge&#xff0c;Google&#xff0c;火狐这三种&#xff0c;但是浏览器都有后台自动更新&#xff0c;更新提示会一直显示&#xff0c;要用户去点击才关掉&#xff0c;有点强迫症的用户就会想要把它一直关掉&#xff0c;可每次打开都关不掉&#xff0…

Qt下使用动画框架实现动画520

文章目录 前言一、动画框架的介绍二、示例完整代码三、QtCreator官方示例总结 前言 文章中引用的内容均来自这本书中的原文&#xff1a;【Qt Creator快速入门_霍亚飞编著】。可以通过更改本文示例pro文件中的条件编译&#xff0c;运行示例1和示例2来查看串行动画组和并行动画组…

Qt应用开发--国产工业开发板T113-i的部署教程

Qt在工业上的使用场景包括工业自动化、嵌入式系统、汽车行业、航空航天、医疗设备、制造业和物联网应用。Qt被用来开发工业设备的用户界面、控制系统、嵌入式应用和其他工业应用&#xff0c;因其跨平台性和丰富的功能而备受青睐。 Qt能够为工业领域带来什么好处&#xff1a; - …

基于SSM的网吧计费管理系统(有报告)。Javaee项目,ssm项目。

演示视频&#xff1a; 基于SSM的网吧计费管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xff0c;通…

Docker容器技术实战4

11、docker安全 proc未被隔离&#xff0c;所以在容器内和宿主机上看到的东西是一样的 容器资源控制 cpu资源限制 top命令&#xff0c;查看cpu使用率 ctrlpq防止退出回收&#xff0c;容器会直接调用cgroup&#xff0c;自动创建容器id的目录 cpu优先级设定 测试时只保留一个cpu…

Spring的总结

SpringFramework 认识SpringFramework 最首先&#xff0c;我们先要认识Spring和SpringFramework两者之间有什么联系&#xff1f; 广义上的 Spring 泛指以 Spring Framework 为基础的 Spring 技术栈。 狭义的 Spring 特指 Spring Framework&#xff0c;通常我们将它称为 Spr…

Linux多值判断利用case...esac判断

利用这个判断&#xff0c;一定要注意格式的运用&#xff0c;非常容易出错 case $1 in #判断变量的值 "hello") #双引号注意&#xff0c;右括号 echo " afdbab " #语句段&#xff0c;没啥说的 ;; #两个分号结束第一个判断&#xff0c…

python图像处理 ——图像分块

python图像处理 ——图像分块 前言一、分块与合并1.读取原始图像2.网格划分&#xff0c;将图像划分为m*n块3.网格合并 二、代码 前言 根据图像尺寸创建一个 ( m 1 ) ( n 1 ) 个均匀的网格顶点坐标&#xff0c;对于图像块来说每个图像块的左上角和右下角可以唯一确定一个图像…

路由过滤路由引入

目录 一、实验拓扑 二、实验需求 三、实验步骤 1、配置IP地址 2、配置RIP和OSPF 3、配置路由引入 4、使用路由过滤&#xff0c;使 R4 无法学到 R1 的业务网段路由&#xff0c;要求使用 prefix-list 进行匹配 5、OSPF 区域中不能出现 RIP 协议报文 一、实验拓扑 二、实…

【Linux】:Linux项目自动化构建工具——make/Makefile || Linux第一个小程序——进度条(简单版本)

在本章开始给大家分享一个图片 希望对你有帮助 在这里插入图片描述 &#x1f3c6;前言 在开始本章之前 我们需要回顾一下上节课的函数的动静态库的优缺点 动态库的优点&#xff1a; 比较节省资源&#xff08;这里说的资源不仅仅是磁盘资源 也包括网络资源 内存资源等等&#…

SpringBoot项目多环境开发

1.yml文件&#xff08;旧&#xff09; 说明&#xff1a;旧的写法。 #应用环境 spring:profiles:active: dev --- #设置环境#生产环境 spring:profiles: pro server:port: 81--- #开发环境 spirng:profiles: dev server:port: 81--- #测试环境 spring:profiles: test server:p…

修改一下第二次课服务枚举等问题

关于AutoRuns 的总结里面&#xff0c;有个错误&#xff0c;Image hijacks 这个准确的描述应该是镜像劫持 和系统运行相关的image&#xff0c;我们通常指的是二进制镜像文件 Image hijacks镜像劫持 简单来说就是&#xff0c;在注册表中&#xff0c;有部分设置&#xff0c;是规…

阿里云二级域名绑定与宝塔Nginx反向代理配置

在阿里或者腾讯...各大域名商买好域名&#xff0c;备案解析好&#xff0c;目标URL&#xff0c;是真正的地址&#xff0c;比如一些端口&#xff0c;后者会自动填写。 注意ssl配置好&#xff0c;这里不要带反代端口

分享一次无线话筒和接收机的配对经历BK9521/9522

最近老婆喜欢上了唱歌。我就需要为她准备歌曲和设备。装了台点歌机&#xff0c;买了软件&#xff0c;用4天的时间下了4T容量的歌曲&#xff0c;听过的没听过的都在里面&#xff0c;真的是太多了。 有了歌曲&#xff0c;就要有唱歌设备了。当我准备买无线话筒的时候&#xff0c…

Javascript知识点详解:正则表达式

目录 RegExp 对象 概述 实例属性 实例方法 RegExp.prototype.test() RegExp.prototype.exec() 字符串的实例方法 String.prototype.match() String.prototype.search() String.prototype.replace() String.prototype.split() 匹配规则 字面量字符和元字符 转义符…

5.数据表基本操作

目录 1.创建数据表 创建数据表的语法格式&#xff1a; 查看当前数据库的表&#xff1a; 主键 1.单字段主键 (1)在定义列的同时指定主键&#xff0c;语法规则如下&#xff1a; (2)在定义完所有列之后指定主键。 2.多字段联合主键 外键&#xff1a; 非空约束&#xff1…