Springboot connects to Siemens plc, reads the corresponding value, and modifies it to the database

springboot connects to Siemens plc, reads the corresponding value, and modifies it to the database

Requirements: The server connects to the plc, reads the data, and then writes it to the database, but the speed is required, and the values corresponding to the commands in the plc are constantly changing. The server must see this change in real time; local testing, You can use the blog
1. Code implementation
1. maven dependency
<dependency>
   <groupId>com.github.xingshuangs</groupId>
    <artifactId>iot-communication</artifactId>
    <version>1.4.4</version>
</dependency>
2. Entry function
@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 {<!-- -->
        //Create child thread
        Thread thread = new Thread(otherProgram);
        // This sentence ensures that when the main thread stops, the child thread continues to run.
        thread.setDaemon(true);
        //Start child thread
        thread.start();
    }
}
3. Sub-thread class
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() {<!-- -->
        //Write the execution logic of the sub-thread here
        // You can call the entry methods of other programs or perform other operations
        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 -> {<!-- -->
                // establish connection
                S7PLC s7PLC = new S7PLC(EPlcType.S1200, plcIp.getIpAddress()); // Use the IP address of plcIp
                while (true) {<!-- -->
                    //Construct serialized object
                    ByteArraySerializer serializer = ByteArraySerializer.newInstance();
                    // Get the maximum byte number corresponding to the command of db1 under plc
                    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");
                    //-----------------Read the bytes of plc1, DBW two bytes, DBD4 bytes, DBX/B one byte, see what the last one ends with, just Add 2 or 4 or 1 to the corresponding number of bytes
                    // DB1 address range is 0~220 + 4 224
                    if (db1PlcMl != null) {<!-- -->
                        byte[] db1Datas = s7PLC.readByte("DB1.DBD0", PlcReadDataUtils.getMaxByteOffsite(db1PlcMl));
                        if (db1Datas != null & amp; & amp; db1Datas.length > 0) {<!-- -->
                            //DB1 data block
                            PlcReadDataUtils.readData(plcIp.getId(), "DB1", serializer, db1Datas, plcMlDao);
                        }
                    }
                    // DB3 address range is 0~284 + 4 288 DBW two bytes, DBD4 bytes and B one byte
                    if (db3PlcMl != null) {<!-- -->
                        byte[] db3Datas = s7PLC.readByte("DB3.DBW0", PlcReadDataUtils.getMaxByteOffsite(db3PlcMl));
                        if (db3Datas != null & amp; & amp; db3Datas.length > 0) {<!-- -->
                            //DB3 data block
                            PlcReadDataUtils.readData(plcIp.getId(), "DB3", serializer, db3Datas, plcMlDao);
                        }
                    }
                    if (db4PlcMl != null) {<!-- -->
                        // DB4 address range is 0~286 287
                        byte[] db4Datas = s7PLC.readByte("DB4.DBD0", PlcReadDataUtils.getMaxByteOffsite(db4PlcMl));
                        if (db4Datas != null & amp; & amp; db4Datas.length>0) {<!-- -->
                            // DB4 data block
                            PlcReadDataUtils.readData(plcIp.getId(), "DB4", serializer, db4Datas, plcMlDao);
                        }
                    }
                    if (db5PlcMl != null) {<!-- -->
                        //The DB5 address range is 0~374 + 2 376
                        byte[] db5Datas = s7PLC.readByte("DB5.DBX0.0", PlcReadDataUtils.getMaxByteOffsite(db5PlcMl));
                        if (db5Datas != null & amp; & amp; db5Datas.length > 0) {<!-- -->
                            // DB5 data block
                            PlcReadDataUtils.readData(plcIp.getId(), "DB5", serializer, db5Datas, plcMlDao);
                        }
                    }
                    if (db6PlcMl != null) {<!-- -->
                        byte[] db6Datas = s7PLC.readByte("DB6.DBX0.0", PlcReadDataUtils.getMaxByteOffsite(db6PlcMl));
                        if (db6Datas != null & amp; & amp; 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 & amp; & amp; db7Datas.length > 0) {<!-- -->
                            // DB7 data block
                            PlcReadDataUtils.readData(plcIp.getId(),"DB7", serializer, db7Datas, plcMlDao);
                        }
                    }
                    logger.info("plc endTime:" + System.currentTimeMillis());
                }
            });
        }
    }
}
  1. dao layer and sql statement
/**
* Query the latest piece of data based on the command of id and db block. Purpose: to obtain the maximum command value
* @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. Tools
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) {<!-- -->
        //Query the data collection of db1 corresponding to plc1
        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]", "");
            // First determine whether there is a decimal after the second point of DB1.DBX0.0, if not, set it to 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));
            }
        });
        //Read the byte data of 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. Database structure



2. Explanation:

Idea: There are multiple plcs. First find out the number of plcs, create a connection based on the plc address, and then read the value according to the command.
DB5.DBW2.1: Data block 5, byte index: 2, bit index: 1
DB4.DBW3: Data block 4, byte index: 3, bit index: 0
DB5.DBD2.3: Data block 5, byte index: 2, bit index: 3
DB5.DBD2: Data block: 5, byte index: 2, bit index: 0
DB4.DBX1.1: Data block: 4, byte index: 1, bit index: 1
DB4.DBX1: Data block: 4, byte index: 1, bit index: 0

The data after each command represents the byte index and bit index respectively

The logic of my code:
(1) Get the maximum number of bytes: First query the maximum byte index of each db block in each plc. The commands are consecutive. Then I query each db block in each plc in reverse from the database. Then take a piece of data;
SysPlcMlEntity db1PlcMl = plcMlDao.getOneOrderByIdDescLimitOneAndIpIdAndContentLike(plcIp.getId(), "DB1");
(2) Because the initial byte index and bit index of each DB block are both 0, the initial value is written directly in the code, and the plc byte array is read according to the initial value and the number of each DB block;
byte[] db1Datas = s7PLC.readByte("DB1.DBD0", PlcReadDataUtils.getMaxByteOffsite(db1PlcMl));
(3) Parse the read byte array:
① Since I need to read the byte array and write the obtained value into the database at the same time, I need to query the corresponding collection data of the database based on the ip and db blocks:
List plcMlList = plcMlDao.getListByIpIdAndMlContentLike(ipId, dbNumber);
② Loop through the collection plcMlList , then obtain a single command, intercept the following byte index and bit index, and then read each command according to the byte index and bit index to obtain the object ByteArrayParameter , added to the collection List
③Read data: Read data based on byte array and object collection
List byteList = serializer.extractParameter(db1ParameterList, datas);
④Assign value and modify the database
Through the fori loop, the obtained value is assigned to each object, and the data is finally modified. Since the value we get by parsing the bytes is the same length as the value we query the database, we can just loop through the length of a collection.