SpringBoot 2.2.6 集成ModbusTCP协议 请求从站获取数据

概要

  1. 转载地址
  2. 通过后端集成ModbusTCP协议,主动请求从站获取数据。
  3. 因甲方方案调整,此代码最终未采用,故也未进行更多的处理与优化。

什么是Modbus TCP

image.png

如何集成Modbus TCP MASTER

  1. 仿真机下载地址 仿真机有主站和从站 需要模拟从站 下载 Modbus Slave 激活码 5455415451475662

  2. 添加依赖

implementation 'com.digitalpetri.modbus:modbus-master-tcp:1.1.0'
  1. 贴代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.ExecutionException;

@RestController
@RequestMapping("/modbus")
public class ModbusApi {

    private static final Logger LOGGER = LoggerFactory.getLogger(ModbusApi.class);

    @Autowired
    private ModbusService modbusService;

    @ResponseBody
    @GetMapping
    public void get(int address, int unitId, ModbusEunm modbusEunm, int typeId) throws ExecutionException, InterruptedException {
        modbusService.get(address, unitId, modbusEunm, typeId);
    }
}

import com.syxp.dlsesp.base.BaseService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.concurrent.ExecutionException;

@Service
public class ModbusService extends BaseService<ModbusEntity> {

    private static final Logger LOGGER = LoggerFactory.getLogger(ModbusService.class);

    @Autowired
    private ModbusMasterTCP modbusMasterTCP;

    public void get(int address, int unitId, ModbusEunm modbusEunm, int typeId) throws ExecutionException, InterruptedException {
        Number number;
        switch (modbusEunm) {
            case COILS:
                Boolean coils = modbusMasterTCP.readCoils(address, unitId, typeId);
                number = coils ? 1 : 0;
                break;
            case DISCRETE_INPUTS:
                Boolean discreteInputs = modbusMasterTCP.readDiscreteInputs(address, unitId, typeId);
                number = discreteInputs ? 1 : 0;
                break;
            case HOLDING_REGISTERS:
                number = modbusMasterTCP.readHoldingRegisters(address, unitId, typeId);
                break;
            case INPUT_REGISTERS:
                number = modbusMasterTCP.readInputRegisters(address, unitId, typeId);
                break;
            default:
                number = -1;
        }
        ModbusEntity modbusEntity = new ModbusEntity();
        modbusEntity.setAddress(address);
        modbusEntity.setfId(modbusEunm.getCode());
        modbusEntity.setsId(unitId);
        modbusEntity.setData(number.doubleValue());
        this.save(modbusEntity);
        LOGGER.info(number.toString());
    }
}

import com.digitalpetri.modbus.codec.Modbus;
import com.digitalpetri.modbus.master.ModbusTcpMaster;
import com.digitalpetri.modbus.master.ModbusTcpMasterConfig;
import com.digitalpetri.modbus.requests.ReadCoilsRequest;
import com.digitalpetri.modbus.requests.ReadDiscreteInputsRequest;
import com.digitalpetri.modbus.requests.ReadHoldingRegistersRequest;
import com.digitalpetri.modbus.requests.ReadInputRegistersRequest;
import com.digitalpetri.modbus.responses.ReadCoilsResponse;
import com.digitalpetri.modbus.responses.ReadDiscreteInputsResponse;
import com.digitalpetri.modbus.responses.ReadHoldingRegistersResponse;
import com.digitalpetri.modbus.responses.ReadInputRegistersResponse;
import com.serotonin.modbus4j.code.DataType;
import io.netty.buffer.ByteBuf;
import io.netty.util.ReferenceCountUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

@Component
public class ModbusMasterTCP {

    public static String address;
    public static int port;

    public String getAddress() {
        return address;
    }

    @Value("${modbusAddress}")
    public void setAddress(String address) {
        ModbusMasterTCP.address = address;
    }

    public int getPort() {
        return port;
    }

    @Value("${modbusPort}")
    public void setPort(int port) {
        ModbusMasterTCP.port = port;
    }

    static ModbusTcpMaster master;

    /**
     * 获取TCP协议的Master
     *
     * @return
     */
    @Bean
    public void initModbusTcpMaster() {
        if (master == null) {
            // 创建配置
            ModbusTcpMasterConfig config = new ModbusTcpMasterConfig.Builder(ModbusMasterTCP.address).setPort(ModbusMasterTCP.port).build();
            master = new ModbusTcpMaster(config);
        }
    }

    /***
     * 释放资源
     */
    public void release() {
        if (master != null) {
            master.disconnect();
        }
        Modbus.releaseSharedResources();
    }

    /**
     * 读取Coils开关量
     *
     * @param address 寄存器开始地址
     * @param unitId  ID
     * @return 读取值
     * @throws InterruptedException 异常
     * @throws ExecutionException   异常
     */
    public Boolean readCoils(int address, int unitId, int typeId)
            throws InterruptedException, ExecutionException {
        Boolean result = null;
        CompletableFuture<ReadCoilsResponse> future = master.sendRequest(new ReadCoilsRequest(address, DataType.getRegisterCount(typeId)),
                unitId);
        ReadCoilsResponse readCoilsResponse = future.get();// 工具类做的同步返回.实际使用推荐结合业务进行异步处理
        if (readCoilsResponse != null) {
            ByteBuf buf = readCoilsResponse.getCoilStatus();
            result = buf.readBoolean();
            ReferenceCountUtil.release(readCoilsResponse);
        }
        return result;
    }

    /**
     * 读取readDiscreteInputs开关量
     *
     * @param address 寄存器开始地址
     * @param unitId  ID
     * @return 读取值
     * @throws InterruptedException 异常
     * @throws ExecutionException   异常
     */
    public Boolean readDiscreteInputs(int address, int unitId, int typeId)
            throws InterruptedException, ExecutionException {
        Boolean result = null;
        CompletableFuture<ReadDiscreteInputsResponse> future = master
                .sendRequest(new ReadDiscreteInputsRequest(address, DataType.getRegisterCount(typeId)), unitId);
        ReadDiscreteInputsResponse discreteInputsResponse = future.get();// 工具类做的同步返回.实际使用推荐结合业务进行异步处理
        if (discreteInputsResponse != null) {
            ByteBuf buf = discreteInputsResponse.getInputStatus();
            result = buf.readBoolean();
            ReferenceCountUtil.release(discreteInputsResponse);
        }
        return result;
    }

    /**
     * 读取HoldingRegister数据
     *
     * @param address 寄存器地址
     * @param unitId  id
     * @return 读取结果
     * @throws InterruptedException 异常
     * @throws ExecutionException   异常
     */
    public Number readHoldingRegisters(int address, int unitId, int typeId)
            throws InterruptedException, ExecutionException {
        Number result = null;
        CompletableFuture<ReadHoldingRegistersResponse> future = master
                .sendRequest(new ReadHoldingRegistersRequest(address, DataType.getRegisterCount(typeId)), unitId);
        ReadHoldingRegistersResponse readHoldingRegistersResponse = future.get();// 工具类做的同步返回.实际使用推荐结合业务进行异步处理
        if (readHoldingRegistersResponse != null) {
            ByteBuf buf = readHoldingRegistersResponse.getRegisters();
            result = this.getJavaType(buf, typeId);
            ReferenceCountUtil.release(readHoldingRegistersResponse);
        }
        return result;
    }

    /**
     * 读取InputRegisters模拟量数据
     *
     * @param address 寄存器开始地址
     * @param unitId  ID
     * @return 读取值
     * @throws InterruptedException 异常
     * @throws ExecutionException   异常
     */
    public Number readInputRegisters(int address, int unitId, int typeId)
            throws InterruptedException, ExecutionException {
        Number result = null;
        CompletableFuture<ReadInputRegistersResponse> future = master
                .sendRequest(new ReadInputRegistersRequest(address, DataType.getRegisterCount(typeId)), unitId);
        ReadInputRegistersResponse readInputRegistersResponse = future.get();// 工具类做的同步返回.实际使用推荐结合业务进行异步处理
        if (readInputRegistersResponse != null) {
            ByteBuf buf = readInputRegistersResponse.getRegisters();
            result = this.getJavaType(buf, typeId);
            ReferenceCountUtil.release(readInputRegistersResponse);
        }
        return result;
    }


    /**
     * 转换slave数据格式
     *
     * @param buf    字节容器
     * @param typeId 数据库类型id 类型id参考 com.serotonin.modbus4j.code.DataType
     * @return 转换后的数据格式
     */
    private Number getJavaType(ByteBuf buf, int typeId) {
        switch (typeId) {
            case 2:
            case 5:
            case 7:
            case 10:
            case 12:
            case 17:
            case 20:
            case 22:
            case 25:
                return buf.readInt();
            case 3:
            case 16:
            case 23:
                return buf.readShort();
            case 4:
            case 6:
            case 11:
            case 13:
            case 24:
                return buf.readLong();
            case 14:
            case 15:
                return buf.readDouble();
            case 8:
            case 9:
            case 18:
            case 21:
                return buf.readFloat();
            default:
                return null;
        }

    }


}

public enum ModbusEunm {

    /**
     * 读线圈
     */
    COILS("01"),
    /**
     * 读离散输入
     */
    DISCRETE_INPUTS("02"),
    /**
     * 读保存寄存器
     */
    HOLDING_REGISTERS("03"),
    /**
     * 读输入寄存器
     */
    INPUT_REGISTERS("04");

    private String code;

    ModbusEunm(String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }
}

import com.syxp.dlsesp.base.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;

@Entity
@Table(name = "T_MODBUS")
@ApiModel(description = "Modbus元数据")
public class ModbusEntity extends BaseEntity {

    @Column(columnDefinition = "DOUBLE(9,2)")
    private Double data;


    @Column
    @ApiModelProperty(value = "从机编号")
    private Integer sId;

    @Column
    @ApiModelProperty(value = "功能编码")
    private String fId;

    @Column
    @ApiModelProperty(value = "地址编号")
    private Integer address;

    public Double getData() {
        return data;
    }

    public void setData(Double data) {
        this.data = data;
    }

    public Integer getsId() {
        return sId;
    }

    public void setsId(Integer sId) {
        this.sId = sId;
    }

    public String getfId() {
        return fId;
    }

    public void setfId(String fId) {
        this.fId = fId;
    }

    public Integer getAddress() {
        return address;
    }

    public void setAddress(Integer address) {
        this.address = address;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341