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

news2025/7/7 14:20:49

本系列教程包括:
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

本章首先简单的介绍了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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/74683.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IFR202型红外雨量传感器数据说明

信号输出 寄存器 OUT_CFG 默认参数时&#xff0c;当降雨量达到预设的分辨率时&#xff0c;传感器从脉冲线输出脉冲信号、从数字 接口 RS232 或者 RS485 输出总降雨量、LED 指示灯闪烁一次。 数字接口 RS232 或者 RS485 输出数据帧说明如下&#xff1a;&#xff08;16 进制数…

VUE3 数据的侦听

侦听数据变化也是组件里的一项重要工作&#xff0c;比如侦听路由变化、侦听参数变化等等。 Vue 3 在保留原来的 watch 功能之外&#xff0c;还新增了一个 watchEffect 帮助更简单的进行侦听。 watch 在 Vue 3 &#xff0c;新版的 watch 和 Vue 2 的旧版写法对比&#xff0c;在…

Spring Security基础使用

Spring Security基础使用 定义 官方文档&#xff1a;https://docs.spring.io/spring-security/reference/index.html security结合servelt:https://docs.spring.io/spring-security/reference/5.6/servlet/getting-started.html Security 官方示例项目&#xff1a;https://…

深度对比 Footprint 和 Dune 数据模型

在产品建设方面&#xff0c;数据所有权转移已迅速成为需要解决的关键问题之一。社交媒体平台滥用数据的现象频发&#xff0c;我们亟需建立方案来解决这一问题。鉴于此&#xff0c;区块链提供了一种功能&#xff0c;可以永恒地存储、转移数据&#xff0c;并创建市场&#xff0c;…

ChatGPT帮你写代码?人工智能ChatGPT之于Web3的几点思考

人工智能AI有人热情膜拜&#xff0c;有人畏惧如虎&#xff0c;AI的发展已经进入高层面的快车道&#xff0c;技术更新迭代日新夜异&#xff0c;技术无罪也无善恶&#xff0c;曾经科幻未来的人机交流&#xff0c;共生共存如今在我们的脑海中已经有了轮廓的意识感想&#xff0c;科…

Spring底层事务原理

Spring事务底层原理一、EnableTransactionManagement工作原理二、Spring事务基本执行原理三、Spring事务的过程四、Spring事务传播机制五、Spring事务传播机制分类&#xff08;1&#xff09;案例分析、情况1&#xff08;2&#xff09;案例分析、情况2&#xff08;3&#xff09;…

SEVNLDAEFR, 186142-28-9

This peptide substrate corresponds to the Swedish Lys-Met/Asn-Leu (K670N/M671L) mutation of the amyloid precursor protein (APP) β-secretase cleavage site. It has been used for assaying β-secretase activity.该肽底物对应于淀粉样前体蛋白(APP) β-分泌酶切割位…

政企办公「分水岭」以至,融云百幄数智化破局

中国政企数智办公平台行业研究报告 246 年前&#xff0c;亚当斯密便在《国富论》中提出&#xff0c;劳动分工是提高社会生产效率的主要原因。从工厂定岗&#xff0c;到国际分工&#xff0c;分工的价值已经深入社会生活和工作的方方面面。关注【融云RongCloud】&#xff0c;了解…

六、使用注解开发

文章目录一、CRUD 注解(舍弃 mapper.xml): Select、 Insert、 Update、 Delete、 Param1、Select2、Insert3、Delete4、Update5、Param二、Lombok 插件&#xff0c;减少重复代码1、Data一、CRUD 注解(舍弃 mapper.xml): Select、 Insert、 Update、 Delete、 Param 通过注解去处…

【Netty】Netty高性能原理剖析

Netty高性能原理剖析1、前言2、Netty高性能2.1 多路复用通讯方式2.2 异步通讯 NIO2.3 零拷贝(DIRECT BUFFERS 使用堆外直接内存&#xff09;2.4 内存池(基于内存池的缓冲区重用机制&#xff09;2.5 高效的 Reactor 线程模型2.5.1 Reactor 单线程模型2.5.2 Reactor 多线程模型2.…

低代码有多爽?1个月的活,只需3天干完

仅看到“低代码”三个字&#xff0c;不少程序员就会吐槽“只适合简单业务&#xff0c;普通查改还行&#xff0c;复杂业务简直是灾难”&#xff0c;甚至认为是“儿童玩具”。 不少人自以为是程序员的二把刀&#xff0c;以为自己懂完了&#xff0c;在没理解低代码的应用场景&…

怎么压缩动态图片?手机怎么压缩gif动图?

在平时的聊天当中为了增加聊天的趣味性我们经常会保存一些有趣的gif动图表情包&#xff0c;但是由于gif图一般是由多帧组成&#xff0c;因此有的gif动图就会非常大&#xff0c;无法添加到表情当中&#xff0c;这时候就需要将gif压缩变小&#xff0c;那么我们用手机怎么压缩gif动…

华硕编程竞赛11月JAVA专场 C题太空遨游 题解

作者主页&#xff1a;Designer 小郑 作者简介&#xff1a;Java全栈软件工程师一枚&#xff0c;来自浙江宁波&#xff0c;负责开发管理公司OA项目&#xff0c;专注软件前后端开发&#xff08;Vue、SpringBoot和微信小程序&#xff09;、系统定制、远程技术指导。CSDN学院、蓝桥云…

chatGPT辣么火,你却不会注册

chatGPT 是什么&#xff1f; 一款目前超级火的 AI 对话聊天工具&#xff0c;只是不同于其他的智能聊天机器人那样&#xff0c;他非常的智能。 可以回答你的技术问题、帮你写代码、还能帮你写小说等等&#xff0c;发挥你的想象力&#xff0c;让他干点啥都行。 比如让他帮你用…

[附源码]Python计算机毕业设计SSM基于旅游服务平台(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

VMware使用和Linux安装Docker

一、VMware安装和配置 二、Linux安装Docker PS:记得每次配置安装新东西前先拍摄拍照&#xff0c;这样即使安装坏了&#xff0c;不需要重装虚拟机。&#xff08;吃过这个亏&#xff09; 1.Docker支持64位版本的CentOS 7和CentOS 8及更高版本&#xff0c;它要求Linux内核版本不…

Web前端大作业—— 饮食餐饮网站 咖啡网站pc端带轮播(5个页面)HTML+CSS+JavaScript 学生美食网页设计作品 学生餐饮文化网页模板

&#x1f468;‍&#x1f393;静态网站的编写主要是用HTML DIVCSS JS等来完成页面的排版设计&#x1f469;‍&#x1f393;,常用的网页设计软件有Dreamweaver、EditPlus、HBuilderX、VScode 、Webstorm、Animate等等&#xff0c;用的最多的还是DW&#xff0c;当然不同软件写出的…

数据技术篇之日志采集

第2章 日志采集 1.日志采集有哪些 页面浏览日志 页面浏览日志是指当一个页面被浏览器加载呈现时采集的日志。此类日志 也是最基础的互联网日志&#xff0c;也是目前所有互联网产品的两大基本指标&#xff1a;页面浏览量&#xff08;Page View&#xff0c;PV&#xff09;和访客…

谈一谈 IPA 上传到 App Store Connect 的几种方法

谈一谈​ 1、前言​ 关于上传​ 2、Xcode​ 利用​ 3、Application Loader​ 当然&#xff0c;Xcode 这种方式&#xff0c;是需要有源代码情况下&#xff0c;才能上传。所以&#xff0c;就会有没有源代码的情况&#xff0c;怎么上传的情况啦&#xff01;​ Application L…

ET框架解读其一

ECS&#xff1f; 真正的ECS属于是entity-component-system组件里面只有数据没有方法&#xff0c;system里面是针对组件的方法&#xff0c;system通过查找只需要关注自己想关注的组件集合就可以。但是ET框架的代码在组件里面写满了方法&#xff0c;有数据又有方法的组件&#x…