IOT云平台 simple(5)springboot netty实现modbus TCP Master

本系列教程包括:
IOT云平台 simple(0)IOT云平台简介
IOT云平台 simple(1)netty入门
IOT云平台 simple(2)springboot入门
IOT云平台 simple(3)springboot netty实现TCP Server
IOT云平台 simple(4)springboot netty实现简单的mqtt broker
IOT云平台 simple(5)springboot netty实现modbus TCP Master
IOT云平台 simple(6)springboot netty实现IOT云平台基本的架构(mqtt、Rabbitmq)

本章首先简单的介绍了modbus,然后利用springboot netty实现了简单的modbus TCP Master。

由于modbus是应答式的交互,这里通过HTTP请求触发springboot netty发送modbus TCP请求,网络调试工具收到请求后发送响应message。
在这里插入图片描述

这里:
modbus TCP Master:springboot netty
modbus TCP Slave:网络调试工具
postman:http触发springboot netty主动发送请求;

1 Modbus入门

Modbus在串行链路上分为Slave和Master、
Modbus协议使用的是主从通讯技术,即由主设备主动查询和操作设备。
Modbus Master:主控设备方;
Modbus Slave:从设备方。

Modbus协议有3种模式:
1)RTU
2)ASCII
3)TCP

1.1 Modbus-RTU

基于串口的Modbus-RTU 数据按照标准串口协议进行编码,是使用最广泛的一种Modbus协议,采用CRC-16_Modbus校验算法。

具体协议:
在这里插入图片描述

1.2 Modbus-ASCII

基于串口的Modbus-ASCII 所有数据都是ASCII格式,一个字节的原始数据需要两个字符来表示,效率低,采用LRC校验算法。

具体协议:
在这里插入图片描述

1.3 Modbus-TCP

基于网口的Modbus-TCP Modbus-TCP基于TCP/IP协议,占用502端口,数据帧主要包括两部分:MBAP(报文头)+PDU(帧结构),数据块与串行链路是一致的。

具体协议:

在这里插入图片描述

2 Modbus TCP master集成开发

Modbus TCP是运行在TCP/IP之上的应用层,所以master基本就是个TCP Server。
创建主要的类:
1) TCPServer
server类。
2 )TCPServerChannelInitializer
server channel初始化的类
3)TCPServerStartListener:监听到springboot启动后,启动TCPServer。

TCPServerChannelInitializer中增加了编码解码器。

        socketChannel.pipeline().addLast("decoder", new TCPModbusResDecoder());
        socketChannel.pipeline().addLast("encoder", new TCPModbusReqEncoder());
        socketChannel.pipeline().addLast("tcpModbus", new TCPModbusResHandler());

2.1 定义message

定义message:

public class TCPModbusMessage {
    public MBAPHeader mbapHeader;
    public PduPayload pduPayload;
}

其中header:

public class MBAPHeader {
    //事务处理标识符 递增
    private short transactionId;
    //协议标识符 0x00 标识modbus协议
    private short protocolId;
    //长度,unitId + pdu长度
    private short length;
    //单元标识符,从机地址
    private byte unitId;

    public MBAPHeader(short transactionId, short protocolId, short length, byte unitId) {
        this.transactionId = transactionId;
    }

    public MBAPHeader() {

    }

    public int getTransactionId() {
        return transactionId;
    }

    public void setTransactionId(short transactionId) {
        this.transactionId = transactionId;
    }

    public int getProtocolId() {
        return protocolId;
    }

    public void setProtocolId(short protocolId) {
        this.protocolId = protocolId;
    }

    public int getLength() {
        return length;
    }

    public void setLength(short length) {
        this.length = length;
    }

    public short getUnitId() {
        return unitId;
    }

    public void setUnitId(byte unitId) {
        this.unitId = unitId;
    }

    public String toString() {
        return "transactionId:" + transactionId
                + ",protocolId:" + protocolId
                + ",length:" + length
                + ",unitId:" + unitId;

    }
}

其中pdu消息主体:

public class PduPayload {
    private short functionCode;
    private short dataLength;//数据字节的长度
    private byte[] data;

    public void setFunctionCode(short  code){
        functionCode = code;
    }
    public short getFunctionCode(){
        return functionCode;
    }

    public short getDataLength() {
        return dataLength;
    }

    public void setDataLength(short dataLength) {
        this.dataLength = dataLength;
    }

    public byte[] getData() {
        return data;
    }

    public void setData(byte[] data) {
        this.data = data;
    }

}

2.2 定义编码解码

2.2.1 解码

解码定义ByteToMessageDecoder:

public class TCPModbusResDecoder extends ByteToMessageDecoder {

    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) {
        try {
            byteBuf.resetReaderIndex();
            int count = byteBuf.readableBytes();
            log.info("TCPModbusResDecoder decode:" + count);

            ByteBuf byteBuf1 = Unpooled.copiedBuffer(byteBuf);
            TCPModbusByteBufHolder tcpModbusByteBufHolder = new TCPModbusByteBufHolder(byteBuf1);
            list.add(tcpModbusByteBufHolder);

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

解码定义SimpleChannelInboundHandler:

public class TCPModbusResHandler extends SimpleChannelInboundHandler<TCPModbusByteBufHolder> {
    public static final int HEADER_LENGTH = 8;// // transactionId(2) + protocolId(2) + length(2) + unitId(1)+ functionCode(1)

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("channelActive");
        Channel channel = ctx.channel();
        log.info("channelActive channelId:" + channel.id().asLongText());
        log.info("channelActive 终端:" + channel.remoteAddress());
        ChannelManager.addChannel(channel.remoteAddress().toString(), channel);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.info("channelInactive");
        Channel channel = ctx.channel();
        ChannelManager.removeChannel(channel.remoteAddress().toString());
    }

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, TCPModbusByteBufHolder modbusByteBufHolder) {
        log.info("channelRead0");
        int totalLen = modbusByteBufHolder.content().readableBytes();
        log.info("channelRead0:" + totalLen);
        if (totalLen < HEADER_LENGTH) {
            log.info("not modbus TCP protocol:");
            return;
        }
        //header
        MBAPHeader mbapHeader = MBAPHeaderDecoder.decode(modbusByteBufHolder.content());
        log.info("mbapHeader:" + mbapHeader.toString());
        //pdu
        PduPayload pduPayload = new PduPayload();
        short functionCode = modbusByteBufHolder.content().readUnsignedByte();
        pduPayload.setFunctionCode(functionCode);
        log.info("functionCode:" + functionCode);
        int dataLength = 0;
        if (totalLen > HEADER_LENGTH) {
            dataLength = totalLen - HEADER_LENGTH;
        }
        pduPayload.setDataLength((short) dataLength);
        log.info("dataLength:" + dataLength);
        byte[] data = new byte[dataLength];
        modbusByteBufHolder.content().readBytes(data);
        pduPayload.setData(data);

        RecMessageStrategy messageStrategy = RecMessageStrategyManager.getMessageStrategy(functionCode);
        if (messageStrategy != null) {
            messageStrategy.recMessage(channelHandlerContext.channel(), mbapHeader, pduPayload);
        } else {
            log.info("not support function code...");
        }

    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        log.info("channelReadComplete");
        ctx.flush();
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        log.info("handlerRemoved");
        ctx.close();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.info("exceptionCaught");
        Channel channel = ctx.channel();
        ChannelManager.removeChannel(channel.remoteAddress().toString());
        cause.printStackTrace();
    }
}

这里针对Message接收处理定义了一个策略:

public interface RecMessageStrategy {
    void recMessage(Channel channel, MBAPHeader mbapHeader, PduPayload pduPayload);
}

增加了不同类型消息策略的管理:

public class RecMessageStrategyManager {

    //根据消息类型获取对应的策略类
    public static RecMessageStrategy getMessageStrategy(int functionCode){
        switch (functionCode){
            case FunctionCodeConstants
                    .ReadCoils:
                return new ReadCoilsResMessageStrategy();
            case FunctionCodeConstants
                    .ReadDiscreteInputs:
                return new ReadDiscreteInputsResMessageStrategy();
            case FunctionCodeConstants
                    .ReadHoldingRegisters:
                return new ReadHoldingRegistersResMessageStrategy();
            case FunctionCodeConstants
                    .ReadInputRegisters:
                return new ReadInputRegistersResMessageStrategy();
            case FunctionCodeConstants
                    .WriteSingleCoil:
                return new WriteSingleCoilResMessageStrategy();
            case FunctionCodeConstants
                    .WriteSingleRegister:
                return new WriteSingleRegisterResMessageStrategy();
            case FunctionCodeConstants
                    .WriteMultipleCoils:
                return new WriteMultipleCoilsResMessageStrategy();
            case FunctionCodeConstants
                    .WriteMultipleRegisters:
                return new WriteMultipleRegistersResMessageStrategy();
            default:
                return null;
        }
    }

}

2.2.2 编码

定义编码器:

public class TCPModbusReqEncoder extends MessageToByteEncoder<TCPModbusMessage> {

    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, TCPModbusMessage tcpModbusMessage, ByteBuf byteBuf) throws Exception {
        log.info("-----------TCPModbusReqEncoder encode begin------------");
        //header
        MBAPHeaderEncoder.encode(byteBuf, tcpModbusMessage.mbapHeader);
        log.info("header:"+tcpModbusMessage.mbapHeader.toString());

        //functionCode
        int functionCode = tcpModbusMessage.pduPayload.getFunctionCode();
        log.info("functionCode:"+functionCode);

        SendMessageStrategy sendMessageStrategy = new SendMessageStrategy();
        sendMessageStrategy.sendMessage(byteBuf,tcpModbusMessage.pduPayload);
        log.info("data:"+ ByteUtil.bytesToHexString(tcpModbusMessage.pduPayload.getData()));
        log.info("-----------TCPModbusReqEncoder encode end------------");
    }
}

这里定义了一个message发送的策略:

public class SendMessageStrategy {

    public ByteBuf sendMessage(ByteBuf byteBuf, PduPayload pduPayload) {
        log.info("SendMessageStrategy ");
        byteBuf.writeByte(pduPayload.getFunctionCode());
        byteBuf.writeBytes(pduPayload.getData());
        return byteBuf;
    }
}

3 测试运行

这里通过测试以下功能:
1)读线圈状态(readCoils)
2)读保持寄存器(readHoldingRegisters)
3)写单个线圈(writeSingleCoil)
4)写单个寄存器(writeSingleRegister)

注:这里网络调试工具发送modbus TCP响应message是手动输入模拟的。

3.1 读线圈状态(readCoils)

事务处理标识符(transactionId):1
功能码(functionCode):1
从机地址(unitId):1

第1步:http触发请求:
在这里插入图片描述
第2步:springboot netty发送modbus TCP请求:
在这里插入图片描述
第3步:网络调试工具收到请求:
在这里插入图片描述
第4步:网络调试工具发送modbus TCP响应:
在这里插入图片描述
第5步:springboot netty收到响应:
在这里插入图片描述

3.2 读保持寄存器(readHoldingRegisters)

事务处理标识符(transactionId):2
功能码(functionCode):3
从机地址(unitId):1

第1步:http触发请求:
在这里插入图片描述
第2步:springboot netty发送modbus TCP请求:
在这里插入图片描述
第3步:网络调试工具收到请求:
在这里插入图片描述
第4步:网络调试工具发送modbus TCP响应:
在这里插入图片描述
第5步:springboot netty收到响应:
在这里插入图片描述

3.3 写单个线圈(writeSingleCoil)

事务处理标识符(transactionId):3
功能码(functionCode):5
从机地址(unitId):1

第1步:http触发请求:
在这里插入图片描述
第2步:springboot netty发送modbus TCP请求:
在这里插入图片描述
第3步:网络调试工具收到请求:
在这里插入图片描述
第4步:网络调试工具发送modbus TCP响应:
在这里插入图片描述
第5步:springboot netty收到响应:
在这里插入图片描述

3.4 写单个寄存器(writeSingleRegister)

事务处理标识符(transactionId):4
功能码(functionCode):6
从机地址(unitId):1

第1步:http触发请求:
在这里插入图片描述
第2步:springboot netty发送modbus TCP请求:
在这里插入图片描述
第3步:网络调试工具收到请求:
在这里插入图片描述
第4步:网络调试工具发送modbus TCP响应:
在这里插入图片描述
第5步:springboot netty收到响应:
在这里插入图片描述

代码详见:
https://gitee.com/linghufeixia/iot-simple
code4

  • 7
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 15
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值