单板机上Android通过Modbus RTU操控外设

公司的一个产品设计项目,采用了搭载Android系统的单板机来操控外设和大量的传感器。

单板机上有两个串口,外设也是在串口的PLC管控下的。

不过,Android系统有两个问题:

1) Android不支持串口读写:

        我采用了android_serialport_api第三方类库来解决了,参见让android_serialport_api支持奇偶校验、数据位、停止位等参数;

2) Android没有Modbus类库,任何的类库:

        严格说起来,Modbus类库还真找到一个:Android Modbus的实现--Modbus4Android。不过Modbus4Android类库是个半成品,且只支持Modbus TCP,不支持Modbus RTU,所以放弃。

        幸而,我曾经做过Python的项目,用到过Python的modbus_tk类库,这是一个非常成熟的类库,功能很完备性能很稳定。而且,作为一个开源类库,自然是有源代码的。于是,自然就想到了借鉴、改编、抄袭。。。(无论怎么说吧)

先上源代码(改编结果):

/*
    Modbus RTU master
 */
public class ModbusMaster {
    private int timeout = 1000;
    private SerialPort port;

    public ModbusMaster(SerialPort port) {
        this.port = port;
    }

    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    /**
     * 执行modbus读写操纵
     * @param slave 取值  1-255
     * @param function_code 功能码,取值 1-6,参见 {@link ModbusFunction}
     * @param starting_address 读写起止地址,取值 0-65535
     * @param quantity_of_x 每次读写数量,取值1-255
     * @param output_value 写入数值,仅在写操作时有效
     * @return 读取到的数值
     * @throws IOException
     * @throws ModbusError
     */
    synchronized public int[] execute(int slave, int function_code, int starting_address, int quantity_of_x, int output_value) throws IOException, ModbusError {
        if (slave < 0 || slave > 255) {
            throw new ModbusError(ModbusErrorType.ModbusInvalidArgumentError, "Invalid slave " + slave);
        }
        if (starting_address < 0 || starting_address > 0xffff) {
            throw new ModbusError(ModbusErrorType.ModbusInvalidArgumentError, "Invalid starting_address " + starting_address);
        }
        if (quantity_of_x < 1 || quantity_of_x > 0xff) {
            throw new ModbusError(ModbusErrorType.ModbusInvalidArgumentError, "Invalid quantity_of_x " + quantity_of_x);
        }

        boolean is_read_function = false;
        int expected_length = 0;
        // 构造request
        ByteArrayWriter request = new ByteArrayWriter();
        request.writeInt8(slave);
        if (function_code == ModbusFunction.READ_COILS || function_code == ModbusFunction.READ_DISCRETE_INPUTS) {
            is_read_function = true;
            request.writeInt8(function_code);
            request.writeInt16(starting_address);
            request.writeInt16(quantity_of_x);

            expected_length = (int) Math.ceil(0.1d * quantity_of_x / 8.0) + 5;
        } else if (function_code == ModbusFunction.READ_INPUT_REGISTERS || function_code == ModbusFunction.READ_HOLDING_REGISTERS) {
            is_read_function = true;
            request.writeInt8(function_code);
            request.writeInt16(starting_address);
            request.writeInt16(quantity_of_x);

            expected_length = 2 * quantity_of_x + 5;
        } else if (function_code == ModbusFunction.WRITE_SINGLE_COIL || function_code == ModbusFunction.WRITE_SINGLE_REGISTER) {
            if (function_code == ModbusFunction.WRITE_SINGLE_COIL)
                if (output_value != 0) output_value = 0xff00;
            request.writeInt8(function_code);
            request.writeInt16(starting_address);
            request.writeInt16(output_value);

            expected_length = 8;
        } else {
            throw new ModbusError(ModbusErrorType.ModbusFunctionNotSupportedError, "Not support function " + function_code);
        }

        byte[] bytes = request.toByteArray();

        int crc = CRC16.compute(bytes);
        request.writeInt16Reversal(crc);
        // 发送到设备
        bytes = request.toByteArray();
        port.getOutputStream().write(bytes);
        // 从设备接收反馈
        byte[] responseBytes;
        try (ByteArrayWriter response = new ByteArrayWriter()) {
            ThreadUtil.sleep(150);
            final int finalExpected_length = expected_length;
            final boolean[] complete = new boolean[1];
            boolean done = TimeoutUtil.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        byte[] bytes = new byte[64];
                        while (!complete[0]) {
                            if (port.getInputStream().available() > 0) {
                                int len = port.getInputStream().read(bytes, 0, bytes.length);
                                if (len > 0) {
                                    response.write(bytes, 0, len);
                                    if (response.size() >= finalExpected_length) {
                                        break;
                                    }
                                }
                            }
                            ThreadUtil.sleep(1);
                        }
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
            }, timeout);
            complete[0] = true;
            response.flush();

            if (!done) {
                throw new ModbusError(ModbusErrorType.ModbusTimeoutError, String.format("Timeout of %d ms.", timeout));
            }
            responseBytes = response.toByteArray();
        }

        if (responseBytes == null || responseBytes.length != expected_length) {
            throw new ModbusError(ModbusErrorType.ModbusInvalidResponseError, "Response length is invalid " + responseBytes.length);
        }

        ByteArrayReader reader = new ByteArrayReader(responseBytes);
        int responseSlave = reader.readInt8();
        if (responseSlave != slave) {
            throw new ModbusError(ModbusErrorType.ModbusInvalidResponseError,
                    String.format("Response slave %d is different from request slave %d", responseSlave, slave));
        }

        int return_code = reader.readInt8();
        if (return_code > 0x80) {
            int error_code = reader.readInt8();
            throw new ModbusError(error_code);
        }

        int data_length = 0;
        if (is_read_function) {
            // get the values returned by the reading function
            data_length = reader.readInt8();
            int actualLength = responseBytes.length - 5;
            if (data_length != actualLength) {
                throw new ModbusError(ModbusErrorType.ModbusInvalidResponseError,
                        String.format("Byte count is %d while actual number of bytes is %d. ", data_length, actualLength));
            }
        }
        // 读取反馈数据
        int[] result = new int[quantity_of_x];
        if (function_code == ModbusFunction.READ_COILS || function_code == ModbusFunction.READ_DISCRETE_INPUTS) {
            bytes = new byte[data_length];
            for (int i = 0; i < data_length; i++) {
                bytes[i] = (byte) reader.readInt8();
            }
            BitSet bits = BitSet.valueOf(bytes);
            for (int i = 0; i < quantity_of_x; i++) {
                result[i] = bits.get(i) ? 1 : 0;
            }
        } else if (function_code == ModbusFunction.READ_INPUT_REGISTERS || function_code == ModbusFunction.READ_HOLDING_REGISTERS) {
            for (int i = 0; i < quantity_of_x; i++) {
                result[i] = reader.readInt16();
            }
        } else if (function_code == ModbusFunction.WRITE_SINGLE_COIL || function_code == ModbusFunction.WRITE_SINGLE_REGISTER) {
            result[0] = reader.readInt16();
            //result[1] = reader.readInt16();
        }
        return result;
    }

    public int[] readCoils(int slave, int startAddress, int numberOfPoints) throws IOException, ModbusError {
        return execute(slave, ModbusFunction.READ_COILS, startAddress, numberOfPoints, 0);
    }

    public int[] readHoldingRegisters(int slave, int startAddress, int numberOfPoints) throws IOException, ModbusError {
        return execute(slave, ModbusFunction.READ_HOLDING_REGISTERS, startAddress, numberOfPoints, 0);
    }

    public int[] readInputRegisters(int slave, int startAddress, int numberOfPoints) throws IOException, ModbusError {
        return execute(slave, ModbusFunction.READ_INPUT_REGISTERS, startAddress, numberOfPoints, 0);
    }

    public int[] readInputs(int slave, int startAddress, int numberOfPoints) throws IOException, ModbusError {
        return execute(slave, ModbusFunction.READ_DISCRETE_INPUTS, startAddress, numberOfPoints, 0);
    }

    public void writeSingleCoil(int slave, int address, boolean value) throws IOException, ModbusError {
        execute(slave, ModbusFunction.WRITE_SINGLE_COIL, address, 1, value ? 1 : 0);
    }

    public void writeSingleRegister(int slave, int address, int value) throws IOException, ModbusError {
        execute(slave, ModbusFunction.WRITE_SINGLE_REGISTER, address, 1, value);
    }

    public boolean readCoil(int slave, int address) throws IOException, ModbusError {
        int[] values = readCoils(slave, address, 1);
        return values[0] > 0;
    }

    public int readHoldingRegister(int slave, int address) throws IOException, ModbusError {
        int[] values = readHoldingRegisters(slave, address, 1);
        return values[0];
    }

    public int readInputRegister(int slave, int address) throws IOException, ModbusError {
        int[] values = readInputRegisters(slave, address, 1);
        return values[0];
    }

    public boolean readInput(int slave, int address) throws IOException, ModbusError {
        int[] values = readInputs(slave, address, 1);
        return values[0] > 0;
    }
}

简单说明一下:

1) 全部功能代码都集中在 ModbusMaster的execute方法中;

2)代码中没有import部分,是因为里面包含有公司代号,不好写出来;

3)ModbusMaster.execute方法里的代码,比modbus_tk里的代码简单许多,原因在于:水平有限,modbus_tk的代码没有完全看懂,我不是故意的;

4)串口操作用的是android_serialport_api,参见让android_serialport_api支持奇偶校验、数据位、停止位等参数;

5) 源代码中用到了一些工具类, 已经跟主程序一起打包上传,可以在下边找到下载链接。

然后我简单说一下代码结构:

1)首先,在实例化这个ModbusMaster类之前,你需要先得有个SerialPort实例 (android_serialport_api);

2) 然后,你就可以调用execute方法读写modbus设备了;

3) execute方法的第一部分,自然是参数验证;

4) 第二部分,是根据功能码类型,构造传入请求的字节组(这部分比起原版的modbus_tk精简很多);

5) 第三部分,写入请求的字节组,并同步读取返回结果,注意,我这里采用了同步方式读取返回结果,如果采用异步方式可能会显著提高性能,但程序代码(包括调用方的程序),将会复杂很多。为了简化程序(也为了偷懒),使用了同步方式,幸而我们的项目没有要求太高的性能。注意,之所以啰嗦这么多,是因为我测试的Modbus串口操作,每次读写差不多要0.1秒才能完成,所以对性能要求很高的场景,必然需要改成异步读写。

6) 第四部分,根据功能码处理返回的字节组,得到结果数值。


以上是本文章的全部内容。


本文的全部源代码在这儿下载

你可能感兴趣的:(Android)