java调用串口modbus重连踩坑记

 

 

故事背景:

我们在项目中有一些采集数据的需求,在实现的时候我们实现了一些驱动来进行不同通信协议的适配,包括modbus,TCP等通信协议。在串口modbus(serialModbus)采集过程中,我们发现重新连接一直失败。通过我的研究发现,在jssc中,串口通信并不需要手动进行重连,因此删除了重连代码解决了问题。为了证明串口通信无需重连并且查找重连失败的原因,做了以下记录。

jar包使用:

jamod-2.0.0-SNAPSHOT.jar:这边我们对网络上的jar包进行了挑选,选择了可以使用的,加入我们的maven私服,版本号定位2.0.0.

jssc:jssc-2.6.2.jar:这个版本目前maven服务器中好像也没有,不过没有进行尝试,最新版可能也可以使用。

jssc中连接失败的原因:

关闭端口实现原理

以下为jssc中关闭端口的代码:

public boolean closePort() throws SerialPortException {
    this.checkPortOpened("closePort()");
    if (this.eventListenerAdded) {
        this.removeEventListener();
    }
    boolean returnValue = this.serialInterface.closePort(this.portHandle);
    if (returnValue) {
        this.maskAssigned = false;
        this.portOpened = false;
    }
    return returnValue;
}

可以看出关闭接口一共做了两件事:

  1. 移出listener
  2. 调用this.serialInterface的关闭功能。

打开端口实现原理

public boolean openPort() throws SerialPortException {
    if (this.portOpened) {
        throw new SerialPortException(this.portName, "openPort()", "Port already opened");
    } else if (this.portName == null) {
        throw new SerialPortException(this.portName, "openPort()", "Null not permitted");
    } else {
        boolean useTIOCEXCL = System.getProperty("JSSC_NO_TIOCEXCL") == null && System.getProperty("JSSC_NO_TIOCEXCL".toLowerCase()) == null;
        this.portHandle = this.serialInterface.openPort(this.portName, useTIOCEXCL);
        if (this.portHandle == -1L) {
            throw new SerialPortException(this.portName, "openPort()", "Port busy");
        } else if (this.portHandle == -2L) {
            throw new SerialPortException(this.portName, "openPort()", "Port not found");
        } else if (this.portHandle == -3L) {
            throw new SerialPortException(this.portName, "openPort()", "Permission denied");
        } else if (this.portHandle == -4L) {
            throw new SerialPortException(this.portName, "openPort()", "Incorrect serial port");
        } else {
            this.portOpened = true;
            return true;
        }
    }
}

重新连接发生了什么?

当程序执行重连流程时,this.portHandle返回结果为2104,此时连接并没有打开,但是因为

portHandle返回值不在错误代码中,所以串口依然可以连接成功。

下图为首次连接成功的内存模型:

下图为连接成功的内存模型

可以看出重新连接之后,serialPort对象发生了改变:

而此时,ModbusSerialTransaction中的serialPort还是重新连接之前的serialPort,并没有进行改变。

那么导致此现象的问题是什么呢?

查看ModbusSerialTransaction的创建逻辑

下图为程序中创建驱动时创建transaction

protected ModbusTransaction createModbusTransaction(TransportConfig config) {
        ModbusSerialTransaction transaction =  new ModbusSerialTransaction(serialConnection);
        transaction.setTransDelayMS(5);

        return transaction;
    }

这里引用到中执行以下功能:

public void setSerialConnection(SerialConnection con) {
        this.m_SerialCon = con;
        this.m_IO = con.getModbusTransport();
    }

通过查看ModbusSerialTransaction的代码(这里未列出),发现ModbusSerialTransaction并没有重连功能,全部参数都会在初始化的时候由传入的serialConnection。因此如果重新创建了连接,必须要重新创建ModbusSerialTransaction才可以使用新的连接,而目前重连的操作只是重新建立了新的SerialPort,而当前ModbusSerialTransaction仍在使用的7428的SerialPort已经被关闭了,因此导致发送数据失败。

而初始化加载的ModbusSerialTransaction又会与驱动一一对应,因此只有在每次新增加驱动的时候,才会创建新的ModbusSerialTransaction。

作为验证,我们在不删除连接的情况下重新创建驱动,可以看到可以正常采集。

为什么串口modbus通信无需重新连接?

虽然上面发现了问题所在,但是还是不能解决问题。为了解决问题,我们分析一下串口modbus工作原理。

串口modbus工作原理

首先我们看一下serialModbusTransport的实现:

public class SerialModubsTransport extends ModbusTransport {
    private static final Logger log = LoggerFactory.getLogger(SerialModubsTransport.class);

    private SerialConnection serialConnection;

    public SerialModubsTransport(SerialModbusConfig config) {
        super(config);

        SerialParameters serialParameters = new SerialParameters();
        serialParameters.setBaudRate( config.getBaudRate());
        serialParameters.setDatabits(config.getDataBits());
        serialParameters.setPortName( config.getPortName());
        serialParameters.setStopbits( config.getStopBit());
        serialParameters.setParity(config.getParity());
        serialParameters.setReceiveTimeout(getTimeOutConfig(config));
        serialParameters.setEncoding(config.getEncoding());

        this.serialConnection = new SerialConnection(serialParameters);
    }

    @Override
    public void reconnect() {
        log.info("reconnect!");
        setAlive(true);
    }

    @Override
    public void connect() {
        try {
            serialConnection.open();
            setAlive(true);
            log.info("successfully to connect {}", getProtocol());
        } catch (Exception e) {
            setAlive(false);
            log.error("Failed to connect:{}", getProtocol());
        }
    }

    @Override
    public void close() throws IOException {
        setAlive(false);

        if(serialConnection !=null){
           serialConnection.close();
           log.info("close port {}", getProtocol());
        }
    }

    @Override
    protected ModbusTransaction createModbusTransaction(TransportConfig config) {
        ModbusSerialTransaction transaction =  new ModbusSerialTransaction(serialConnection);
        transaction.setTransDelayMS(5);

        return transaction;
    }
}

采集数据的时候,程序调用 modbusTransaction.execute() 进行数据采集,modbusTransaction中通过调用jamodTransaction.execute() 执行。

执行代码:

通过调试我们发现,当连接断开时,m_OutputStream可以正常写入数据,但是接收响应会抛出异常。

发送数据功能正常:

数据发送成功,接下来接收响应,读取串口返回值,readResponse中会调用m_InputStreamreadResponse方法:

如果连接存在,则uid返回数值,如果连接丢失,则会抛出异常:

ModbusSerialTransaction中可以捕获异常,retry一定的次数:

最后如果连续失败,则抛出ModbusIOException被程序捕获。

由此我们猜想,串口通信并不需要建立连接,传递数据使用输入输出流,只要串口硬件存在且可以被程序发现,则发送数据都可以成功。当串口连接有设备并且能够响应请求时,则接收到响应数据,至此通信成功。

同样我们使用jssc工具实现了DLT645/2007电表采集协议。

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值