├── LICENSE
├── README.md
├── doc
├── picture
│ ├── 1项目启动.png
│ ├── 2NetAssist参数配置.png
│ ├── 3连接成功.png
│ ├── 4客户端心跳指令上传.png
│ ├── 5服务端接收到心跳指令.png
│ ├── 6postman请求下发控制单锁指令.png
│ ├── 7服务端下发控制单锁指令.png
│ ├── 8服务端接收到控制单锁指令.png
│ ├── 9物联网通讯协议(iot-modbus)交流群群二维码.png
│ ├── TF(腾飞)开源公众号二维码.jpg
│ └── TF(腾飞)开源公众号二维码.png
├── postman
│ └── 通讯指令下发.postman_collection.json
├── serialport
│ ├── librxtxParallel.so
│ ├── librxtxSerial.so
│ ├── rxtxParallel.dll
│ └── rxtxSerial.dll
└── 物联网通讯协议(iot-modbus)开发指南.pdf
├── iot-modbus-client
├── pom.xml
└── src
│ └── main
│ └── java
│ └── com
│ └── takeoff
│ └── iot
│ └── modbus
│ └── client
│ ├── MiiClient.java
│ ├── connect
│ └── MiiClientConnect.java
│ ├── entity
│ └── OpenLock.java
│ └── message
│ └── sender
│ ├── ClientMessageSender.java
│ └── MiiClientMessageSender.java
├── iot-modbus-common
├── pom.xml
└── src
│ └── main
│ └── java
│ └── com
│ └── takeoff
│ └── iot
│ └── modbus
│ └── common
│ ├── bytes
│ └── factory
│ │ ├── MiiAlarmLampDataBytesFactory.java
│ │ ├── MiiBarcodeBytesFactory.java
│ │ ├── MiiBytesCombinedFactory.java
│ │ ├── MiiBytesFactory.java
│ │ ├── MiiBytesFactorySubWrapper.java
│ │ ├── MiiDataFactory.java
│ │ ├── MiiEntityBytesFactory.java
│ │ ├── MiiFingerBytesCombinedFactory.java
│ │ ├── MiiFingerBytesFactory.java
│ │ ├── MiiFingerFeatureBytesCombinedFactory.java
│ │ ├── MiiFingerFeatureBytesFactory.java
│ │ ├── MiiInteger2BytesFactory.java
│ │ ├── MiiLampColorDataBytesFactory.java
│ │ ├── MiiLcdBatchBytesFactory.java
│ │ ├── MiiLcdDataBytesFactory.java
│ │ ├── MiiMultiLockBytesFactory.java
│ │ ├── MiiMultiLockDataBytesFactory.java
│ │ ├── MiiSlotBytesFactory.java
│ │ └── MiiStrings2BytesFactory.java
│ ├── data
│ ├── Finger.java
│ ├── MiiBackLightData.java
│ ├── MiiBarcodeData.java
│ ├── MiiByteArrayData.java
│ ├── MiiCardData.java
│ ├── MiiData.java
│ ├── MiiFingerData.java
│ ├── MiiHeartBeatData.java
│ ├── MiiHexData.java
│ ├── MiiHumitureData.java
│ ├── MiiInData.java
│ ├── MiiIntegerData.java
│ ├── MiiLockData.java
│ ├── MiiSlotData.java
│ └── MiiStringData.java
│ ├── entity
│ ├── AlarmLampData.java
│ ├── ChannelConnectData.java
│ ├── DeviceData.java
│ ├── LampColorData.java
│ ├── LcdData.java
│ └── LockStatus.java
│ ├── enums
│ ├── DeviceConnectEnum.java
│ └── MitModbusCacheKeyEnum.java
│ ├── message
│ ├── MiiByteArrayMessage.java
│ ├── MiiMessage.java
│ └── factory
│ │ ├── MiiMessageFactory.java
│ │ └── MiiOutMessageFactory.java
│ └── utils
│ ├── BytesToHexUtil.java
│ ├── CacheUtils.java
│ ├── IntegerToByteUtil.java
│ ├── JudgeEmptyUtils.java
│ ├── ModbusCrc16Utils.java
│ └── SpringContextUtil.java
├── iot-modbus-netty
├── pom.xml
└── src
│ └── main
│ └── java
│ └── com
│ └── takeoff
│ └── iot
│ └── modbus
│ └── netty
│ ├── channel
│ ├── MiiChannel.java
│ ├── MiiChannelGroup.java
│ └── MiiContextChannel.java
│ ├── data
│ └── factory
│ │ ├── MiiClientDataFactory.java
│ │ └── MiiServerDataFactory.java
│ ├── device
│ ├── MiiControlCentre.java
│ ├── MiiDevice.java
│ ├── MiiDeviceChannel.java
│ └── MiiDeviceGroup.java
│ ├── handle
│ ├── MiiBasedFrameDecoder.java
│ ├── MiiExceptionHandler.java
│ ├── MiiListenerHandler.java
│ ├── MiiMessageDecoder.java
│ └── MiiMessageEncoder.java
│ ├── listener
│ ├── MiiListener.java
│ └── MiiReadWriteLock.java
│ ├── message
│ └── MiiInMessage.java
│ └── service
│ ├── Mappings.java
│ ├── MappingsImpl.java
│ ├── NameValue.java
│ └── NameValueImpl.java
├── iot-modbus-serialport
├── pom.xml
└── src
│ └── main
│ └── java
│ └── com
│ └── takeoff
│ └── iot
│ └── modbus
│ └── serialport
│ ├── data
│ ├── BackLightData.java
│ ├── BarCodeData.java
│ ├── CardData.java
│ ├── FingerData.java
│ ├── HeartBeatData.java
│ ├── HumitureData.java
│ ├── LockData.java
│ ├── ReceiveDataEvent.java
│ └── factory
│ │ ├── SerialportDataFactory.java
│ │ └── SerialportDataReceiveFactory.java
│ ├── entity
│ └── ReceiveData.java
│ ├── enums
│ ├── DatebitsEnum.java
│ ├── ParityEnum.java
│ └── StopbitsEnum.java
│ ├── handler
│ ├── NettyRxtxDecoderHandler.java
│ └── NettyRxtxFrameDecoder.java
│ ├── service
│ ├── SerialportSendService.java
│ ├── SerialportService.java
│ └── impl
│ │ ├── SerialportSendServiceImpl.java
│ │ └── SerialportServiceImpl.java
│ └── utils
│ ├── NettyRxtxClientUtil.java
│ ├── SerialPortUtil.java
│ └── SpringContextUtils.java
├── iot-modbus-server
├── pom.xml
└── src
│ └── main
│ └── java
│ └── com
│ └── takeoff
│ └── iot
│ └── modbus
│ └── server
│ ├── MiiServer.java
│ ├── connect
│ └── MiiServerConnect.java
│ └── message
│ └── sender
│ ├── MiiServerMessageSender.java
│ └── ServerMessageSender.java
├── iot-modbus-test
├── pom.xml
└── src
│ ├── main
│ ├── java
│ │ └── com
│ │ │ └── takeoff
│ │ │ └── iot
│ │ │ └── modbus
│ │ │ ├── App.java
│ │ │ └── test
│ │ │ ├── config
│ │ │ ├── IotModbusClientConfig.java
│ │ │ ├── IotModbusSerialportConfig.java
│ │ │ └── IotModbusServerConfig.java
│ │ │ ├── controller
│ │ │ └── TestController.java
│ │ │ ├── listener
│ │ │ ├── BackLightListener.java
│ │ │ ├── BarCodeListener.java
│ │ │ ├── CardListener.java
│ │ │ ├── ChannelConnectListener.java
│ │ │ ├── FingerListener.java
│ │ │ ├── HeartBeatListener.java
│ │ │ ├── HumitureListener.java
│ │ │ └── LockListener.java
│ │ │ ├── properties
│ │ │ ├── IotModbusClientProperties.java
│ │ │ ├── IotModbusSerialportProperties.java
│ │ │ └── IotModbusServerProperties.java
│ │ │ └── utils
│ │ │ └── R.java
│ └── resources
│ │ ├── application.yml
│ │ ├── banner.txt
│ │ └── logback-spring.xml
│ └── test
│ └── java
│ └── com
│ └── takeoff
│ └── iot
│ └── modbus
│ └── test
│ └── AppTest.java
├── pom.xml
└── tools
├── NetAssist.exe
├── UartAssist.exe
└── vspd7.2.308(串口模拟工具).zip
/README.md:
--------------------------------------------------------------------------------
1 | # iot-modbus
2 |
3 | #### 介绍
4 | 物联网通讯协议,基于netty框架,支持COM(串口)和TCP协议,支持服务端和客户端两种模式,实现Java控制智能设备,同时支持设备组多台设备高并发通讯。采用工厂设计模式,代码采用继承和重写的方式实现高度封装,可作为SDK提供封装的接口,让具体的业务开发人员无需关心通讯协议的底层实现,直接调用接口即可使用。实现了心跳、背光灯、扫码、刷卡、指静脉、温湿度和门锁(支持多锁)、LCD显示屏等指令控制、三色报警灯控制。代码注释丰富,包括上传和下发指令调用例子,非常容易上手。
5 |
6 | #### 视频教程
7 |
8 | [物联网通信协议(iot-modbus)视频教程,创作不易,别忘了点亮Star,你们的支持,是我源源不断的动力。(持续更新中。。。)](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzAxMjUzMDUxNw==&action=getalbum&album_id=2714994150789922817&scene=173&from_msgid=2247484129&from_itemidx=1&count=3&nolastread=1#wechat_redirect)
9 |
10 | #### 版本说明
11 | 1. V1.0.0版本仅支持TCP服务端通讯模式;
12 | 2. V2.0.0版本支持TCP服务端和客户端两种模式,客户端模式还增加了心跳重连机制。
13 | 3. V3.0.0版本支持COM(串口)和TCP协议,增加logback日志按文件大小和时间切割输出。
14 | 4. V3.1.0版本代码优化,抽取公共模块子工程。
15 | 5. V3.2.0版本TCP通讯增加支持LCD显示屏控制指令,支持批量控制LCD显示屏。
16 | 6. V3.2.1版本串口通讯增加支持LCD显示屏控制指令,支持批量控制LCD显示屏。
17 | 7. V3.2.2版本串口通讯接收指令数据拆包处理代码优化,网口通讯增加支持三色报警灯控制指令。
18 | 8. V3.2.3版本串口通讯增加支持三色报警灯控制指令,串口通讯接收指令数据拆包处理代码优化。
19 | 9. V3.2.4版本使用netty集成Rxtx对串口数据进行数据拆包处理,并且对指静脉指令进行优化。
20 | 10. V3.2.5版本客户端模式支持同时连接多个服务端下发和接收指令数据。
21 | 11. V3.2.6版本支持设备上线、掉线、处理业务异常监听处理。
22 | 12. V3.2.7版本优化客户端模式掉线/断网向服务端发起重连的机制。
23 | 13. V3.2.8版本优化服务端上线、掉线监听处理,以及对客户端心跳检测。
24 | 14. V3.2.9版本主要是集成了spring-boot-devtools工具来支持热部署,提高开发效率,无需手动重启应用。
25 |
26 | #### 软件架构
27 | 软件架构说明
28 | 基础架构采用Spring Boot2.x + Netty4.X + Maven3.6.x,日志采用logback。
29 |
30 | #### 安装环境
31 |
32 | 1. 系统Windows7以上;
33 | 2. 安装Jdk1.8以上;
34 | 2. 安装Maven3.6以上;
35 | 3. 代码以Maven工程导入Eclipse或Idea。
36 |
37 | #### 使用说明
38 |
39 | 1. 工程结构说明:
40 | - iot-modbus //物联网通讯父工程
41 | - ├── doc //文档管理
42 | - ├── iot-modbus-client //netty通讯客户端
43 | - ├── iot-modbus-common //公共模块子工程
44 | - ├── iot-modbus-netty //netty通讯子工程
45 | - ├── iot-modbus-serialport //串口通讯子工程
46 | - ├── iot-modbus-server //netty通讯服务端
47 | - ├── iot-modbus-test //使用样例子工程
48 | - └── tools //通讯指令调试工具
49 |
50 | 2. 配置文件查看iot-modbus-test子工程resources目录下的application.yml文件;
51 | 3. 启动文件查看iot-modbus-test子工程App.java文件;
52 | 4. 服务启动后,服务端端口默认为:8080,网口通讯端口默认为:4000,串口通讯默认串口为:COM1;
53 | 5. 通讯指令调试工具,TCP通讯模式使用tools目录下的NetAssist.exe,串口通讯模式使用tools目录下的UartAssist.exe(注意先安装串口模拟工具:解压tools目录下的vspd7.2.308(串口模拟工具).zip,双击vspd.exe安装,拷贝vspdctl.dll破解文件到vspd.exe安装目录替换);
54 | 6. 通讯指令采用Hex编码(十六进制);
55 | 7. 串口通讯依赖文件查看doc/serialport目录,Windows环境下rxtxParallel.dll和rxtxSerial.dll文件拷贝到JDK安装的bin目录下,Linux环境将librxtxParallel.so和librxtxSerial.so文件拷贝到JDK安装的bin目录下;
56 | 8. postman指令下发样例请查看doc/postman/通讯指令下发.postman_collection.json文件,包括门锁(单锁/多锁)、扫码、背光灯、LCD显示屏、三色报警灯指令。
57 |
58 | #### 指令格式
59 |
60 | 1. 以心跳指令(7E 04 00 BE 01 00 00 74 77 7F)作为样例说明,下标从0开始;
61 | 2. 第0位为起始符,长度固定占1个字节,固定格式:7E;
62 | 3. 第1、2位为数据长度,计算方法是从命令符到数据位(即:从3位到指令长度-3位),长度固定占2个字节,例如:04 00,表示长度为4;
63 | 4. 第3位为指令符,长度固定占1个字节,例如:BE,表示心跳指令;
64 | 5. 第4位为设备号,长度固定占1个字节,例如:01,表示设备号为1;
65 | 6. 第5位为层地址,长度固定占1个字节,例如:00,表示设备所有的层不执行;
66 | 7. 第6位为槽地址,长度固定占1个字节,例如:00,表示设备所有的槽不执行;
67 | 8. 指令长度-3位到-2位为校验位,采用CRC16_MODBUS(长度,命令,地址,数据)校验,例如:74 77,详细查看:ModbusCrc16Utils.java工具类;
68 | 9. 末位为结束符,长度固定占1个字节,固定格式:7F。
69 |
70 | #### 通讯指令
71 |
72 | 1. 心跳上传指令
73 | - iot-modbus作为服务端,通过心跳来维持通讯,启动服务端后,打开NetAssist.exe指令调试工具,先往服务端发送心跳指令;
74 | - 硬件往服务端发送:7E 04 00 BE 01 00 00 74 77 7F ,为必要指令。
75 | 2. 背光灯上传指令
76 | - 硬件往服务端发送:7E 05 00 88 01 00 00 00 AF E3 7F
77 | 3. 扫码指令下发
78 | - 服务端往硬件下发:7E 05 00 08 01 00 00 01 6F FD 7F
79 | - 第7位为数据位,长度固定占1个字节,例如:01,表示开开启扫码头。
80 | 4. 扫码指令上传
81 | - 硬件往服务端发送:7E 24 00 8F 01 00 00 03 45 30 30 34 30 31 30 38 32 38 30 32 41 36 39 33 0D 02 00 00 01 02 13 73 02 00 00 01 02 13 73 9B 79 7F
82 | - 数据位:03 45 30 30 34 30 31 30 38 32 38 30 32 41 36 39 33 0D 02 00 00 01 02 13 73 02 00 00 01 02 13 73为条码信息。
83 | 5. 刷卡指令上传
84 | - 硬件往服务端发送:7E 08 00 84 01 00 00 86 14 AE 02 7C 53 7F
85 | - 数据位:86 14 AE 02为卡号信息。
86 | 6. 单开锁下发指令
87 | - 服务端往硬件下发:7E 05 00 03 01 00 00 01 CA 3C 7F
88 | - 第7位为数据位,长度固定占1个字节,例如:01,表示开1号锁。
89 | 7. 多开锁下发指令
90 | - 服务端往硬件下发:7E 08 00 03 FF FF FF 01 00 02 01 7F B0 7F
91 | - FF FF FF为指令做兼容填补位,后面 01 00 02 01是数据位,其中:01表示1号锁,00表示上锁;02表示2号锁,01表示开锁。
92 | 8. 锁状态上传指令
93 | - 硬件往服务端发送:7E 0D 00 83 01 00 00 FF FF FF 01 00 05 02 00 01 EE 99 7F
94 | - FF FF FF为指令做兼容填补位,后面 01 00 05 02 00 01是数据位,其中:01表示1号锁,00表示上锁,05表示传感器状态码;02表示2号锁,00表示上锁,01表示传感器状态码。
95 | 9. 指静脉和温湿度指令(不作说明,详细查看代码);
96 | 10. LCD显示屏批量控制下发指令(不作说明,详细查看代码);
97 | 11. 三色报警灯控制下发指令(不作说明,详细查看代码)。
98 |
99 | #### 调试说明
100 |
101 | 1. 找到iot-modbus-test子工程App.java文件启动服务端,如下图所示:
102 | - 
103 | - 说明:项目启动成功后,控制台日志输出服务端的端口为:8080;项目服务名为:iot-modbus-test;服务端开启socket通讯端口为:4000。
104 | 2. 将工程tools目录下通讯指令调试工具NetAssist拷贝到Windows桌面,双击打开,并配置参数,如下图所示:
105 | - 
106 | - 说明:协议类型选择TCP Client(调试工具作为模拟硬件通讯的客服端);远程主机地址输入本地电脑的IP地址;远程主机端口输入服务端端口4000;接收和发送的编码选择HEX;最后点击连接按钮进行连接,连接成功后服务端控制台日志输出如下图所示:
107 | - 
108 | 3. 客户端往服务端上传心跳指令,如下图所示:
109 | - 
110 | - 说明:拷贝心跳指令到通讯指令调试工具NetAssist的数据发送窗口粘贴进来,然后点击发送按钮;此时服务端将接收到心跳指令,如下图所示:
111 | - 
112 | - 说明:客服端与服务端的通讯连接需要通过客户端定时往服务端发送心跳指令来维持,在生产环境中发送频率一般可设置为每5秒一次,如果通讯连接断开则客服端与服务端无法通讯。注意:在调试的过程中,如果通讯指令调试工具NetAssist与服务端通讯连接断开,则要手动点击NetAssist的连接按钮,重新往服务端发送一条心跳的指令。
113 | 4. 使用Postman请求服务端往客户端下发控制单锁指令,如下图所示:
114 | - 
115 | - 说明:在Postman输入服务端发送控制单锁指令接口,填入请求地址和参数:http://127.0.0.1:8080/iot-modbus-test/test/openlock/1/1,服务端控制台日志输出如下图所示:
116 | - 
117 | - 客服端指令调试工具NetAssist接收到控制单锁指令,如下图所示:
118 | - 
119 | 5. 其他上传和下发的指令不作详细介绍,感兴趣的同学可以参考通讯指令和工程目录doc/postman目录下的请求样例。
120 |
121 | #### 创作不易,别忘了点亮Star,你们的支持,是我源源不断的动力。
122 |
123 | #### 欢迎加入交流群
124 |
125 | - 微信公众号
126 | - 
127 | - QQ技术交流群
128 | - 
129 |
--------------------------------------------------------------------------------
/doc/picture/1项目启动.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luorongxi/iot-modbus/ebd6d00d29eb744a046e0593d9613a53dd8eaa07/doc/picture/1项目启动.png
--------------------------------------------------------------------------------
/doc/picture/2NetAssist参数配置.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luorongxi/iot-modbus/ebd6d00d29eb744a046e0593d9613a53dd8eaa07/doc/picture/2NetAssist参数配置.png
--------------------------------------------------------------------------------
/doc/picture/3连接成功.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luorongxi/iot-modbus/ebd6d00d29eb744a046e0593d9613a53dd8eaa07/doc/picture/3连接成功.png
--------------------------------------------------------------------------------
/doc/picture/4客户端心跳指令上传.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luorongxi/iot-modbus/ebd6d00d29eb744a046e0593d9613a53dd8eaa07/doc/picture/4客户端心跳指令上传.png
--------------------------------------------------------------------------------
/doc/picture/5服务端接收到心跳指令.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luorongxi/iot-modbus/ebd6d00d29eb744a046e0593d9613a53dd8eaa07/doc/picture/5服务端接收到心跳指令.png
--------------------------------------------------------------------------------
/doc/picture/6postman请求下发控制单锁指令.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luorongxi/iot-modbus/ebd6d00d29eb744a046e0593d9613a53dd8eaa07/doc/picture/6postman请求下发控制单锁指令.png
--------------------------------------------------------------------------------
/doc/picture/7服务端下发控制单锁指令.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luorongxi/iot-modbus/ebd6d00d29eb744a046e0593d9613a53dd8eaa07/doc/picture/7服务端下发控制单锁指令.png
--------------------------------------------------------------------------------
/doc/picture/8服务端接收到控制单锁指令.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luorongxi/iot-modbus/ebd6d00d29eb744a046e0593d9613a53dd8eaa07/doc/picture/8服务端接收到控制单锁指令.png
--------------------------------------------------------------------------------
/doc/picture/9物联网通讯协议(iot-modbus)交流群群二维码.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luorongxi/iot-modbus/ebd6d00d29eb744a046e0593d9613a53dd8eaa07/doc/picture/9物联网通讯协议(iot-modbus)交流群群二维码.png
--------------------------------------------------------------------------------
/doc/picture/TF(腾飞)开源公众号二维码.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luorongxi/iot-modbus/ebd6d00d29eb744a046e0593d9613a53dd8eaa07/doc/picture/TF(腾飞)开源公众号二维码.jpg
--------------------------------------------------------------------------------
/doc/picture/TF(腾飞)开源公众号二维码.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luorongxi/iot-modbus/ebd6d00d29eb744a046e0593d9613a53dd8eaa07/doc/picture/TF(腾飞)开源公众号二维码.png
--------------------------------------------------------------------------------
/doc/serialport/librxtxParallel.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luorongxi/iot-modbus/ebd6d00d29eb744a046e0593d9613a53dd8eaa07/doc/serialport/librxtxParallel.so
--------------------------------------------------------------------------------
/doc/serialport/librxtxSerial.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luorongxi/iot-modbus/ebd6d00d29eb744a046e0593d9613a53dd8eaa07/doc/serialport/librxtxSerial.so
--------------------------------------------------------------------------------
/doc/serialport/rxtxParallel.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luorongxi/iot-modbus/ebd6d00d29eb744a046e0593d9613a53dd8eaa07/doc/serialport/rxtxParallel.dll
--------------------------------------------------------------------------------
/doc/serialport/rxtxSerial.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luorongxi/iot-modbus/ebd6d00d29eb744a046e0593d9613a53dd8eaa07/doc/serialport/rxtxSerial.dll
--------------------------------------------------------------------------------
/doc/物联网通讯协议(iot-modbus)开发指南.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luorongxi/iot-modbus/ebd6d00d29eb744a046e0593d9613a53dd8eaa07/doc/物联网通讯协议(iot-modbus)开发指南.pdf
--------------------------------------------------------------------------------
/iot-modbus-client/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | iot-modbus
8 | com.takeoff.iot
9 | 3.2.9-SNAPSHOT
10 |
11 | iot-modbus-client
12 | jar
13 | iot-modbus-client
14 | 物联网网口通讯协议客户端模块
15 |
16 |
17 | 3.2.9-SNAPSHOT
18 |
19 |
20 |
21 |
22 | com.takeoff.iot
23 | iot-modbus-netty
24 | ${iot-modbus-netty.version}
25 |
26 |
27 |
28 |
29 | ${project.artifactId}
30 |
31 |
32 |
33 | org.apache.maven.plugins
34 | maven-surefire-plugin
35 |
36 | true
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/iot-modbus-client/src/main/java/com/takeoff/iot/modbus/client/MiiClient.java:
--------------------------------------------------------------------------------
1 | package com.takeoff.iot.modbus.client;
2 |
3 | import java.net.InetSocketAddress;
4 | import java.util.HashMap;
5 | import java.util.HashSet;
6 | import java.util.Map;
7 | import java.util.Set;
8 | import java.util.concurrent.TimeUnit;
9 |
10 | import com.takeoff.iot.modbus.client.connect.MiiClientConnect;
11 | import com.takeoff.iot.modbus.client.message.sender.ClientMessageSender;
12 | import com.takeoff.iot.modbus.client.message.sender.MiiClientMessageSender;
13 | import com.takeoff.iot.modbus.common.bytes.factory.MiiDataFactory;
14 | import com.takeoff.iot.modbus.common.message.MiiMessage;
15 | import com.takeoff.iot.modbus.common.utils.CacheUtils;
16 | import com.takeoff.iot.modbus.common.utils.JudgeEmptyUtils;
17 | import com.takeoff.iot.modbus.netty.channel.MiiChannel;
18 | import com.takeoff.iot.modbus.netty.data.factory.MiiClientDataFactory;
19 | import com.takeoff.iot.modbus.netty.device.MiiDeviceChannel;
20 | import com.takeoff.iot.modbus.netty.handle.*;
21 | import com.takeoff.iot.modbus.netty.listener.MiiListener;
22 |
23 | import io.netty.bootstrap.Bootstrap;
24 | import io.netty.channel.ChannelFuture;
25 | import io.netty.channel.ChannelHandlerContext;
26 | import io.netty.channel.ChannelInboundHandlerAdapter;
27 | import io.netty.channel.ChannelInitializer;
28 | import io.netty.channel.ChannelPipeline;
29 | import io.netty.channel.EventLoopGroup;
30 | import io.netty.channel.nio.NioEventLoopGroup;
31 | import io.netty.channel.socket.SocketChannel;
32 | import io.netty.channel.socket.nio.NioSocketChannel;
33 | import io.netty.handler.timeout.IdleStateEvent;
34 | import io.netty.handler.timeout.IdleStateHandler;
35 | import lombok.extern.slf4j.Slf4j;
36 |
37 | /**
38 | * 类功能说明:设备通讯客户端
39 | * 公司名称:TF(腾飞)开源
40 | * 作者:luorongxi
41 | */
42 | @Slf4j
43 | public class MiiClient extends ChannelInitializer {
44 |
45 | private static int IDLE_TIMEOUT = 60000;
46 |
47 | private Map workerGroupMap = new HashMap();
48 |
49 | private String deviceGroup;
50 | private int nThread;
51 | private MiiListenerHandler handler;
52 | private MiiDataFactory dataFactory;
53 |
54 | private Set addressSet = new HashSet<>();
55 |
56 | private Map cmMap = new HashMap();
57 |
58 | private Map futureMap = new HashMap();
59 |
60 | public MiiClient(String deviceGroup){
61 | this(deviceGroup, 0, IDLE_TIMEOUT);
62 | }
63 |
64 | public MiiClient(String deviceGroup,int nThread, int heartBeatTime){
65 | this.deviceGroup = deviceGroup;
66 | this.nThread = nThread;
67 | this.IDLE_TIMEOUT = heartBeatTime;
68 | this.handler = new MiiListenerHandler();
69 | this.dataFactory = new MiiClientDataFactory();
70 | }
71 |
72 | /**
73 | * 连接服务端
74 | */
75 | public ChannelFuture connect(String serverHost, int serverPort, int reconnectTime) throws Exception {
76 | EventLoopGroup workerGroup = new NioEventLoopGroup(nThread);
77 | workerGroupMap.put(serverHost, workerGroup);
78 | Bootstrap boot = new Bootstrap()
79 | .group(workerGroup)
80 | .channel(NioSocketChannel.class)
81 | .handler(this);
82 | InetSocketAddress address = InetSocketAddress.createUnresolved(serverHost, serverPort);
83 | addressSet.add(address);
84 | MiiClientConnect cm = new MiiClientConnect(boot, address, reconnectTime){
85 | @Override
86 | public void afterSuccess() {
87 | sender().registerGroup(serverHost, deviceGroup);
88 | }
89 | };
90 | cmMap.put(serverHost, cm);
91 | ChannelFuture future = cm.connect();
92 | futureMap.put(serverHost, future);
93 | return future;
94 | }
95 |
96 | /**
97 | * 停止服务
98 | */
99 | public ChannelFuture disconnect(String serverHost){
100 | MiiClientConnect cm = (MiiClientConnect) cmMap.get(serverHost);
101 | ChannelFuture future = (ChannelFuture) futureMap.get(serverHost);
102 | ChannelFuture f = future.channel().closeFuture();
103 | EventLoopGroup workerGroup = (EventLoopGroup) workerGroupMap.get(serverHost);
104 | workerGroup.shutdownGracefully();
105 | future = null;
106 | return f;
107 | }
108 |
109 | @Override
110 | protected void initChannel(SocketChannel ch) throws Exception {
111 | ChannelPipeline p = ch.pipeline();
112 | for(InetSocketAddress address : addressSet){
113 | MiiChannel channel = new MiiDeviceChannel(address, ch);
114 | MiiChannel isExist = (MiiChannel) CacheUtils.get(channel.name());
115 | if(JudgeEmptyUtils.isEmpty(isExist)){
116 | MiiClientConnect cm = (MiiClientConnect) cmMap.get(channel.name());
117 | p.addLast(cm);
118 | p.addLast(new IdleStateHandler(0, 0, IDLE_TIMEOUT, TimeUnit.MILLISECONDS));
119 | p.addLast(new ChannelInboundHandlerAdapter(){
120 |
121 | @Override
122 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
123 | if(evt instanceof IdleStateEvent){
124 | sender().registerGroup(channel.name(), deviceGroup);
125 | } else {
126 | super.userEventTriggered(ctx, evt);
127 | }
128 | }
129 |
130 | });
131 | p.addLast(new MiiMessageEncoder());
132 | p.addLast(new MiiBasedFrameDecoder());
133 | p.addLast(new MiiMessageDecoder(dataFactory));
134 | p.addLast(handler);
135 | p.addLast(new MiiExceptionHandler());
136 | }
137 | }
138 | }
139 |
140 | public ClientMessageSender sender(){
141 | return new MiiClientMessageSender();
142 | }
143 |
144 | /**
145 | * 添加接收指定指令的消息监听器
146 | * @param command 指令类型 {@link MiiMessage}
147 | * @param listener 消息监听器
148 | * @return 上一个消息监听器,如果没有返回null
149 | */
150 | public MiiListener addListener(int command, MiiListener listener){
151 | return handler.addListener(command, listener);
152 | }
153 |
154 | /**
155 | * 移除接收指定指令的消息监听器
156 | * @param command 指令类型 {@link MiiMessage}
157 | * @return 移除消息监听器,如果没有返回null
158 | */
159 | public MiiListener removeListener(int command){
160 | return handler.removeListener(command);
161 | }
162 |
163 | }
164 |
--------------------------------------------------------------------------------
/iot-modbus-client/src/main/java/com/takeoff/iot/modbus/client/connect/MiiClientConnect.java:
--------------------------------------------------------------------------------
1 | package com.takeoff.iot.modbus.client.connect;
2 |
3 | import java.net.InetSocketAddress;
4 | import java.net.SocketAddress;
5 | import java.util.HashMap;
6 | import java.util.Map;
7 | import java.util.concurrent.TimeUnit;
8 |
9 | import com.takeoff.iot.modbus.client.message.sender.MiiClientMessageSender;
10 | import com.takeoff.iot.modbus.common.entity.ChannelConnectData;
11 | import com.takeoff.iot.modbus.common.enums.DeviceConnectEnum;
12 | import com.takeoff.iot.modbus.common.utils.CacheUtils;
13 | import com.takeoff.iot.modbus.common.utils.JudgeEmptyUtils;
14 | import com.takeoff.iot.modbus.common.utils.SpringContextUtil;
15 | import com.takeoff.iot.modbus.netty.channel.MiiChannel;
16 | import com.takeoff.iot.modbus.netty.device.MiiDeviceChannel;
17 | import io.netty.bootstrap.Bootstrap;
18 | import io.netty.channel.*;
19 | import io.netty.channel.ChannelHandler.Sharable;
20 | import io.netty.util.HashedWheelTimer;
21 | import io.netty.util.Timeout;
22 | import io.netty.util.Timer;
23 | import io.netty.util.TimerTask;
24 | import lombok.Getter;
25 | import lombok.extern.slf4j.Slf4j;
26 | import org.springframework.context.ApplicationContext;
27 | import org.springframework.util.ObjectUtils;
28 |
29 | /**
30 | * 类功能说明:客户端链接管理器
31 | * 公司名称:TF(腾飞)开源
32 | * 作者:luorongxi
33 | */
34 | @Slf4j
35 | @Sharable
36 | public abstract class MiiClientConnect extends ChannelInboundHandlerAdapter implements TimerTask {
37 |
38 | private ApplicationContext getApplicationContext = SpringContextUtil.applicationContext;
39 |
40 | private static int TIMEOUT = 5000;
41 |
42 | private final Bootstrap boot;
43 | private SocketAddress address;
44 | private Timer timer;
45 |
46 | /**
47 | * 连接成功次数
48 | */
49 | private Map onLineMap = new HashMap<>();
50 |
51 | /**
52 | * 连接断开次数
53 | */
54 | private Map breakOffMap = new HashMap<>();
55 |
56 | /**
57 | * 重连失败次数
58 | */
59 | private Map retriesMap = new HashMap<>();
60 |
61 | public MiiClientConnect(Bootstrap boot, SocketAddress address, int reconnectTime){
62 | this.boot = boot;
63 | this.address = address;
64 | this.TIMEOUT = reconnectTime;
65 | this.timer = new HashedWheelTimer();
66 | }
67 |
68 | public ChannelFuture connect() throws Exception {
69 | ChannelFuture future = boot.connect(address);
70 | future.addListener(new ChannelFutureListener() {
71 | @Override
72 | public void operationComplete(ChannelFuture future) throws Exception {
73 | if (!future.isSuccess()) {
74 | //连接失败,重连服务端,重连交给后端线程执行
75 | future.channel().eventLoop().schedule(() -> {
76 | Integer retries = (ObjectUtils.isEmpty(retriesMap.get(address.toString())) ? 0 : retriesMap.get(address.toString())) + 1;
77 | retriesMap.put(address.toString(), retries);
78 | if(!JudgeEmptyUtils.isEmpty(address)){
79 | ChannelConnectData connectServerData = new ChannelConnectData(this, DeviceConnectEnum.BREAK_RECONNECT.getKey(), address.toString(), retries);
80 | if(!JudgeEmptyUtils.isEmpty(connectServerData) && !JudgeEmptyUtils.isEmpty(getApplicationContext)){
81 | getApplicationContext.publishEvent(connectServerData);
82 | }
83 | }
84 | try {
85 | connect();
86 | } catch (Exception e) {
87 | e.printStackTrace();
88 | }
89 | }, TIMEOUT, TimeUnit.MILLISECONDS);
90 | } else {
91 | //服务端连接成功
92 | afterSuccess();
93 | Integer onLine = (ObjectUtils.isEmpty(onLineMap.get(address.toString())) ? 0 : onLineMap.get(address.toString())) + 1;
94 | onLineMap.put(address.toString(), onLine);
95 | if(!JudgeEmptyUtils.isEmpty(address)){
96 | ChannelConnectData connectServerData = new ChannelConnectData(this, DeviceConnectEnum.ON_LINE.getKey(), address.toString(), onLine);
97 | if(!JudgeEmptyUtils.isEmpty(connectServerData) && !JudgeEmptyUtils.isEmpty(getApplicationContext)){
98 | getApplicationContext.publishEvent(connectServerData);
99 | }
100 | }
101 | }
102 | }
103 | });
104 | //对通道关闭进行监听
105 | future.channel().closeFuture().sync();
106 | return future;
107 | }
108 |
109 | @Override
110 | public void run(Timeout timeout) throws Exception {
111 | connect();
112 | }
113 |
114 | @Override
115 | public void channelActive(ChannelHandlerContext ctx) throws Exception {
116 | //成功后,重连失败次数清零
117 | Channel channel = ctx.channel();
118 | ctx.fireChannelActive();
119 | if(!JudgeEmptyUtils.isEmpty(channel.remoteAddress())){
120 | String address = channel.remoteAddress().toString().substring(1,channel.remoteAddress().toString().length());
121 | retriesMap.put(address.toString(), 0);
122 | //将柜地址与通讯管道的绑定关系写入缓存
123 | MiiChannel miiChannel = new MiiDeviceChannel(channel);
124 | CacheUtils.put(miiChannel.name(), miiChannel);
125 | }
126 | }
127 |
128 | @Override
129 | public void channelInactive(ChannelHandlerContext ctx) throws Exception {
130 | ctx.fireChannelInactive();
131 | Channel channel = ctx.channel();
132 | if(!JudgeEmptyUtils.isEmpty(channel) && !JudgeEmptyUtils.isEmpty(channel.remoteAddress())){
133 | String address = channel.remoteAddress().toString().substring(1,channel.remoteAddress().toString().length());
134 | Integer breakOff = (ObjectUtils.isEmpty(breakOffMap.get(address)) ? 0 : breakOffMap.get(address)) + 1;
135 | breakOffMap.put(address, breakOff);
136 | ChannelConnectData connectServerData = new ChannelConnectData(this, DeviceConnectEnum.BREAK_OFF.getKey(), address, breakOff);
137 | if(!JudgeEmptyUtils.isEmpty(connectServerData) && !JudgeEmptyUtils.isEmpty(getApplicationContext)){
138 | getApplicationContext.publishEvent(connectServerData);
139 | }
140 | //将通讯管道的绑定关系从缓存中删除
141 | MiiChannel miiChannel = new MiiDeviceChannel(channel);
142 | MiiChannel isExist = (MiiChannel) CacheUtils.get(miiChannel.name());
143 | if(!JudgeEmptyUtils.isEmpty(isExist)){
144 | CacheUtils.remove(miiChannel.name());
145 | timer.newTimeout(this, TIMEOUT, TimeUnit.MILLISECONDS);
146 | }
147 | //连接断开后的最后处理
148 | ctx.pipeline().remove(ctx.handler());
149 | ctx.deregister();
150 | ctx.close();
151 | }
152 | }
153 |
154 | public abstract void afterSuccess();
155 | }
156 |
--------------------------------------------------------------------------------
/iot-modbus-client/src/main/java/com/takeoff/iot/modbus/client/entity/OpenLock.java:
--------------------------------------------------------------------------------
1 | package com.takeoff.iot.modbus.client.entity;
2 |
3 | import lombok.Data;
4 |
5 | @Data
6 | public class OpenLock {
7 |
8 | /**
9 | * 服务IP
10 | */
11 | private String ip;
12 |
13 | /**
14 | * 设备号
15 | */
16 | private int device;
17 |
18 | /**
19 | * 锁状态(0:上锁;1:开锁)
20 | */
21 | private int status;
22 | }
23 |
--------------------------------------------------------------------------------
/iot-modbus-client/src/main/java/com/takeoff/iot/modbus/client/message/sender/ClientMessageSender.java:
--------------------------------------------------------------------------------
1 | package com.takeoff.iot.modbus.client.message.sender;
2 |
3 | import java.util.List;
4 |
5 | import com.takeoff.iot.modbus.common.data.Finger;
6 |
7 | /**
8 | * 类功能说明:上传指令接口
9 | * 公司名称:TF(腾飞)开源
10 | * 作者:luorongxi
11 | */
12 | public interface ClientMessageSender {
13 |
14 | /**
15 | * 上传设备组指令.
16 | * @param ip 设备IP
17 | * @param deviceGroup 设备组号
18 | */
19 | void registerGroup(String ip, String deviceGroup);
20 |
21 | /**
22 | * 上传控制单锁指令.
23 | * @param ip 设备IP
24 | * @param device 设备号
25 | * @param status 锁状态(0:上锁;1:开锁)
26 | */
27 | void unlock(String ip, int device, int status);
28 |
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/iot-modbus-client/src/main/java/com/takeoff/iot/modbus/client/message/sender/MiiClientMessageSender.java:
--------------------------------------------------------------------------------
1 | package com.takeoff.iot.modbus.client.message.sender;
2 |
3 | import com.takeoff.iot.modbus.common.utils.CacheUtils;
4 | import com.takeoff.iot.modbus.common.utils.JudgeEmptyUtils;
5 | import org.bouncycastle.util.encoders.Hex;
6 |
7 | import com.takeoff.iot.modbus.common.bytes.factory.MiiBytesCombinedFactory;
8 | import com.takeoff.iot.modbus.common.bytes.factory.MiiBytesFactory;
9 | import com.takeoff.iot.modbus.common.bytes.factory.MiiBytesFactorySubWrapper;
10 | import com.takeoff.iot.modbus.common.bytes.factory.MiiSlotBytesFactory;
11 | import com.takeoff.iot.modbus.common.bytes.factory.MiiStrings2BytesFactory;
12 | import com.takeoff.iot.modbus.common.data.MiiData;
13 | import com.takeoff.iot.modbus.common.message.MiiMessage;
14 | import com.takeoff.iot.modbus.common.message.factory.MiiMessageFactory;
15 | import com.takeoff.iot.modbus.common.message.factory.MiiOutMessageFactory;
16 | import com.takeoff.iot.modbus.netty.channel.MiiChannel;
17 |
18 | import lombok.extern.slf4j.Slf4j;
19 |
20 | import java.util.HashMap;
21 | import java.util.Map;
22 |
23 | /**
24 | * 类功能说明:指令下发接口实现
25 | * 公司名称:TF(腾飞)开源
26 | * 作者:luorongxi
27 | */
28 | @Slf4j
29 | public class MiiClientMessageSender implements ClientMessageSender {
30 |
31 | private static final MiiBytesFactory BYTESFACTORY_SLOT = new MiiSlotBytesFactory();
32 |
33 | private static final MiiMessageFactory SINGLE_LOCK = new MiiOutMessageFactory<>(BYTESFACTORY_SLOT);
34 |
35 | private static final MiiMessageFactory