Java连接西门子PLC(S7-1200)及读写操作

1 ,项目基于springboot,maven引入:


        <dependency>
            <groupId>com.github.s7connector</groupId>
            <artifactId>s7connector</artifactId>
            <version>2.1</version>
        </dependency>

2,自定义PLC配置,配置包括多个PLC,每个PLC多DB块(后面设计每个DB块对应一个实体类,用于数据读写),每个DB块包含多种类型数据(每个数据类型对应实体类的成员变量)

package service.connect.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import java.util.List;

/**
 * 通过TCP对接plc设备配置
 */

@Configuration
@ConfigurationProperties(prefix = "spring.plc")
public class PLCSetting {

    /**
     * PLC设备列表
     */
    private List<Device> devices;

    public List<Device> getDevices() {
        return devices;
    }

    public void setDevices(List<Device> devices) {
        this.devices = devices;
    }

    public static class Device {

        /**
         * PLC设备IP地址
         */
        private String ip;

        /**
         * PLC设备端口
         */
        private Integer port;

        /**
         * 连接超时,单位:毫秒<br/>
         * 默认5000ms
         */
        private Integer connectTimeout = 5000;

        /**
         * 默认0
         */
        private Integer rack = 0;

        /**
         * 默认1
         */
        private Integer slot = 1;

        /**
         * 一个设备可以包含多个数据块
         */
        List<DataBlock> dataBlock;

        public String getIp() {
            return ip;
        }

        public void setIp(String ip) {
            this.ip = ip;
        }

        public Integer getPort() {
            return port;
        }

        public void setPort(Integer port) {
            this.port = port;
        }

        public Integer getConnectTimeout() {
            return connectTimeout;
        }

        public void setConnectTimeout(Integer connectTimeout) {
            this.connectTimeout = connectTimeout;
        }

        public Integer getRack() {
            return rack;
        }

        public void setRack(Integer rack) {
            this.rack = rack;
        }

        public Integer getSlot() {
            return slot;
        }

        public void setSlot(Integer slot) {
            this.slot = slot;
        }

        public List<DataBlock> getDataBlock() {
            return dataBlock;
        }

        public void setDataBlock(List<DataBlock> dataBlock) {
            this.dataBlock = dataBlock;
        }
    }

    public static class DataBlock {

        /**
         * PLC数据块编号
         */
        private Integer databaseNumber;

        /**
         * 偏移量 默认为0
         */
        private Integer offset = 0;

        /**
         * 是否轮询、重复读取数据,默认:true
         */
        private boolean readRepeat = true;

        /**
         * 数据读取间隔,单位毫秒,默认100ms
         */
        private Integer readRepeatInterval = 100;

        /**
         * 数据对应的实体类对象全路径,数据实体配置数据地址偏移量、长度等相关信息
         */
        private String entityClassName;


        public Integer getDatabaseNumber() {
            return databaseNumber;
        }

        public void setDatabaseNumber(Integer databaseNumber) {
            this.databaseNumber = databaseNumber;
        }

        public Integer getOffset() {
            return offset;
        }

        public void setOffset(Integer offset) {
            this.offset = offset;
        }

        public boolean isReadRepeat() {
            return readRepeat;
        }

        public void setReadRepeat(boolean readRepeat) {
            this.readRepeat = readRepeat;
        }

        public Integer getReadRepeatInterval() {
            return readRepeatInterval;
        }

        public void setReadRepeatInterval(Integer readRepeatInterval) {
            this.readRepeatInterval = readRepeatInterval;
        }

        public String getEntityClassName() {
            return entityClassName;
        }

        public void setEntityClassName(String entityClassName) {
            this.entityClassName = entityClassName;
        }
    }
}

3,yml配置示例(配置项参考上面的配置类):这里使用两种实体类(ExampleBlock1、ExampleBlock2)写法读取同一个DB块(database-number: 2)的数据

spring:
  plc:
    devices:
      - ip: 192.168.0.1
        port: 102
        slot: 1
        rack: 0
        dataBlock:
          - database-number: 2
            read-repeat: true
            read-repeat-interval: 1000
            offset: 0
            entity-class-name: service.connect.entity.plc.ExampleBlock1
          - database-number: 2
            read-repeat: true
            read-repeat-interval: 1000
            offset: 0
            entity-class-name: service.connect.entity.plc.ExampleBlock2

4,连接与操作,类比较长,此处做拆分备注。

    4.1,项目启动时初始化连接:注入配置,定义配置缓存Map(用于后续执行读写操作),定义线程池(按需而定,我这里需要定时读取数据)

    @Resource
    private PLCSetting plcSetting;

    Map<String, DataBlockInfo> dataBlockInfoMap = new HashMap<>();

    private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(16);

    @PostConstruct
    public void init() {

        if (!CollectionUtils.isEmpty(plcSetting.getDevices())) {

            plcSetting.getDevices().forEach(plc -> {

                S7Connector s7Connector = S7ConnectorFactory
                        .buildTCPConnector()
                        .withHost(plc.getIp())
                        .withPort(plc.getPort())
                        .withTimeout(plc.getConnectTimeout()) //连接超时时间
                        .withRack(plc.getRack())
                        .withSlot(plc.getSlot())
                        .build();


                S7Serializer s7Serializer = S7SerializerFactory.buildSerializer(s7Connector);

                if (!CollectionUtils.isEmpty(plc.getDataBlock())) {

                    plc.getDataBlock().forEach(dataBlock -> {

                        //开启轮询读取数据
                        if (dataBlock.isReadRepeat()) {

                            try {
                                Class<?> clazz = getClass().getClassLoader().loadClass(dataBlock.getEntityClassName());

                                executorService.scheduleWithFixedDelay(() -> {
                                    try {
                                         //读取数据
                                        Object data = s7Serializer.dispense(clazz, dataBlock.getDatabaseNumber(), dataBlock.getOffset());

                                        //TODO data
                                        if (logger.isWarnEnabled()) {
                                            logger.info("{}: [{}]", data.getClass(), data);
                                        }
                                    } catch (Exception e) {
                                        e.printStackTrace();
                                    }

                                }, 1000, dataBlock.getReadRepeatInterval(), TimeUnit.MILLISECONDS);

                            } catch (ClassNotFoundException e) {
                                e.printStackTrace();
                            }
                        }

                        //缓存连接信息
                        DataBlockInfo dataBlockInfo = new DataBlockInfo();
                        dataBlockInfo.setS7Connector(s7Connector);
                        dataBlockInfo.setS7Serializer(s7Serializer);
                        dataBlockInfo.setDataBlock(dataBlock);

                        dataBlockInfoMap.put(dataBlock.getEntityClassName(), dataBlockInfo);
                    });
                }
            });
        }

    }

数据块缓存用的内部类:

    public static class DataBlockInfo {

        private PLCSetting.DataBlock dataBlock;
        private S7Serializer s7Serializer;
        private S7Connector s7Connector;

        public PLCSetting.DataBlock getDataBlock() {
            return dataBlock;
        }

        public void setDataBlock(PLCSetting.DataBlock dataBlock) {
            this.dataBlock = dataBlock;
        }

        public S7Serializer getS7Serializer() {
            return s7Serializer;
        }

        public void setS7Serializer(S7Serializer s7Serializer) {
            this.s7Serializer = s7Serializer;
        }

        public S7Connector getS7Connector() {
            return s7Connector;
        }

        public void setS7Connector(S7Connector s7Connector) {
            this.s7Connector = s7Connector;
        }
    }

    4.2,数据写入

        4.2.1,s7connector自带的store方法:通过DB块配置的number、offset以及实体类来保存数据,但是这个方法会刷新实体类所有成员属性对应的数据,如果属性没有赋值,也会覆盖原有的数据。

   /**
     * 保存PLC数据,整体保存,保存所有数据
     */
    public <T> void save(T t) {
        if (dataBlockInfoMap.containsKey(t.getClass().getName())) {
            DataBlockInfo dataBlockInfo = dataBlockInfoMap.get(t.getClass().getName());
            dataBlockInfo.getS7Serializer().store(t, dataBlockInfo.getDataBlock().getDatabaseNumber(), dataBlockInfo.getDataBlock().getOffset());
        } else {
            logger.error("[{}] 对应的数据块未配置,或者读取PLC配置失败!", t.getClass().getName());
        }
    }

        4.2.2,我自己的需求是当属性为null时不做处理:注意:parseBytes方法中关于值为String类型的处理参考

com.github.s7connector.impl.serializer.converter.StringConverter的insert方法,如有其它需求,可以参考该包下的其它Converter
    /**
     * 保存PLC数据,单属性保存,保存所有不为null的属性
     */
    public <T> void saveBySingle(T t) {
        if (dataBlockInfoMap.containsKey(t.getClass().getName())) {
            try {

                DataBlockInfo dataBlockInfo = dataBlockInfoMap.get(t.getClass().getName());

                Arrays.stream(t.getClass().getFields()).forEach(field -> {
                    try {
                        if (!ObjectUtils.isEmpty(field.get(t))) {
                            S7Variable s7Variable = field.getAnnotation(S7Variable.class);

                            if (!ObjectUtils.isEmpty(s7Variable)) {
                                byte[] bytes = parseBytes(field.get(t), s7Variable);

                                dataBlockInfo.getS7Connector().write(DaveArea.DB, dataBlockInfo.getDataBlock().getDatabaseNumber(), s7Variable.byteOffset(), bytes);
                            } else {
                                logger.error("类[{}]的属性[{}]需要添加注解标签", t.getClass().getName(), field.getName());
                            }
                        }
                    } catch (IllegalAccessException e) {
                        logger.error("保存PLC数据失败", e);
                    }
                });

            } catch (Exception e) {
                e.printStackTrace();
            }

        } else {
            logger.error("[{}] 对应的数据块未配置,或者读取PLC配置失败!", t.getClass().getName());
        }
    }

    private byte[] parseBytes(Object value, S7Variable s7Variable) {
        byte[] bytes = null;

        if (value instanceof Integer) {
            bytes = new byte[2];
            intToBytes(((Integer) value).intValue(), bytes);
        } else if (value instanceof byte[]) {
            bytes = (byte[]) value;
        } else if (value instanceof String) {
            byte[] buffer = ((String) value).getBytes();

            bytes = new byte[buffer.length + 2];
            bytes[0] = (byte) s7Variable.size();
            bytes[1] = (byte) ((String) value).length();

            for (int i = 0; i < buffer.length; ++i) {
                bytes[i + 2] = (byte) (buffer[i] & 255);
            }
        }

        return bytes;
    }

    /**
     * PLC中word类型占用2个byte
     */
    private void intToBytes(int number, byte[] outBytes) {
        outBytes[0] = (byte) (number >> 8);
        outBytes[1] = (byte) number;
    }

        4.2.3,DB块对应的实体类:偏移量不能写错

public class ExampleBlock1 implements Serializable {

    @S7Variable(byteOffset = 0, arraySize = 16, type = S7Type.WORD)
    public Integer[] integer;

    @S7Variable(byteOffset = 32, size = 50, type = S7Type.STRING)
    public String string0;

    //toString...
}

public class ExampleBlock2 implements Serializable {

    @S7Variable(byteOffset = 0, size = 2, type = S7Type.WORD)
    public Integer integer0;

    @S7Variable(byteOffset = 2, size = 2, type = S7Type.WORD)
    public Integer integer1;

    // 中间还有很多。。。太长就省略了

    @S7Variable(byteOffset = 28, size = 2, type = S7Type.WORD)
    public Integer integer14;

    @S7Variable(byteOffset = 30, size = 2, type = S7Type.WORD)
    public Integer integer15;

    @S7Variable(byteOffset = 32, size = 50, type = S7Type.STRING)
    public String string0;

    //toString...
}

最后来博图的截图

 数据读取执行结果:

2022-01-19 16:47:35.256 [pool-6-thread-2] INFO  c.d.p.s.connect.service.PLCConnect - class city.dekun.park.service.connect.entity.plc.ExampleBlock2: [integer0[0], integer1[18], integer2[255], integer3[65535], integer4[0], integer5[0], integer6[0], integer7[0], integer8[0], integer9[0], integer10[0], integer11[0], integer12[0], integer13[0], integer14[0], integer15[18], string0[asdfasfd] ]
2022-01-19 16:47:35.287 [pool-6-thread-10] INFO  c.d.p.s.connect.service.PLCConnect - class city.dekun.park.service.connect.entity.plc.ExampleBlock1: [integer[[Ljava.lang.Integer;@11f3bbfe], string0[asdfasfd] ]

评论 34
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值