Forráskód Böngészése

add rtu_p2p_trans function

malongwei 4 éve
szülő
commit
fd00a6853f

+ 9 - 0
ChangeLog.md

@@ -14,6 +14,10 @@
 * 增加 RTU 和 TCP 从机例子
 * 增加示例文档
 
+2022-01-08:马龙伟
+
+* 增加 RTU 点对点传输文件例子
+
 ### 修改
 
 2022-01-06:马龙伟
@@ -21,3 +25,8 @@
 * 修改从机例子,RTU 和 TCP 使用同一个从机回调
 * TCP 从机支持最大 5 个客户端接入
 * TCP 从机 10s 内未收到正确报文主动断开
+
+2022-01-08:马龙伟
+
+* 去除接收数据判断中长度限制
+* 去除 `agile_modbus_serialize_raw_request` 对于原始数据的长度限制

+ 14 - 9
README.md

@@ -4,6 +4,8 @@
 
 Agile Modbus 即:轻量型 modbus 协议栈,满足用户任何场景下的使用需求。
 
+![ModbusProtocol](./figures/ModbusProtocol.jpg)
+
 ### 1.1、特性
 
 1. 支持 rtu 及 tcp 协议,使用纯 C 开发,不涉及任何硬件接口,可在任何形式的硬件上直接使用。
@@ -16,10 +18,11 @@ Agile Modbus 即:轻量型 modbus 协议栈,满足用户任何场景下的
 
 | 名称 | 说明 |
 | ---- | ---- |
-| doc | 文档目录 |
-| examples | 例子目录 |
-| inc  | 头文件目录 |
-| src  | 源代码目录 |
+| doc | 文档 |
+| examples | 例子 |
+| figures | 素材 |
+| inc  | 头文件 |
+| src  | 源代码 |
 
 ### 1.3、许可证
 
@@ -84,7 +87,7 @@ Agile Modbus 遵循 LGPLv2.1 许可,详见 `LICENSE` 文件。
 
 - `agile_modbus_rtu_init` / `agile_modbus_tcp_init`
 
-  初始化 `RTU/TCP` 环境时需要用户传入 `发送缓冲区` 和 `接收缓冲区`,建议这两个缓冲区大小都为 `AGILE_MODBUS_MAX_ADU_LENGTH` (260) 字节。
+  初始化 `RTU/TCP` 环境时需要用户传入 `发送缓冲区` 和 `接收缓冲区`,建议这两个缓冲区大小都为 `AGILE_MODBUS_MAX_ADU_LENGTH` (260) 字节。`特殊功能码` 情况用户根据协议自行决定。
 
   但对于小内存 MCU,这两个缓冲区也可以设置小,所有 API 都会对缓冲区大小进行判断:
 
@@ -172,13 +175,15 @@ Agile Modbus 遵循 LGPLv2.1 许可,详见 `LICENSE` 文件。
 
   - 自定义功能码
 
-  需要使用到 `send_index`、`nb`、`buf` 属性,用户在回调中处理数据。
+    需要使用到 `send_index`、`nb`、`buf` 属性,用户在回调中处理数据。
+
+    send_index: 发送缓冲区当前索引
 
-  send_index: 发送缓冲区当前索引
+    nb: PUD - 1,也就是 modbus 数据域长度
 
-  nb: PUD - 1,也就是 modbus 数据域长度
+    buf: modbus 数据域起始位置
 
-  buf: modbus 数据域起始位置
+    **注意**: 用户在回调中往发送缓冲区填入数据后,需要更新 `agile_modbus_slave_info` 的 `rsp_length` 值。
 
 ### 2.1、示例
 

BIN
doc/doxygen/Agile_Modbus.chm


+ 3 - 1
doc/doxygen/Doxyfile

@@ -965,7 +965,9 @@ EXCLUDE                = ../../examples/build \
                          ../../examples/rtu_master \
                          ../../examples/slave \
                          ../../examples/tcp_master \
-                         ../../examples/Makefile
+                         ../../examples/Makefile \
+                         ../../examples/rtu_broadcast \
+                         ../../examples/rtu_p2p
 
 # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
 # directories that are symbolic links (a Unix file system feature) are excluded

BIN
doc/doxygen/figures/ModbusProtocol.jpg


BIN
doc/doxygen/figures/VirtualComGroup.jpg


BIN
doc/doxygen/figures/rtu_p2p.gif


BIN
doc/doxygen/output/figures/ModbusProtocol.jpg


BIN
doc/doxygen/output/figures/VirtualComGroup.jpg


BIN
doc/doxygen/output/figures/rtu_p2p.gif


+ 42 - 18
doc/doxygen/output/index.html

@@ -44,10 +44,11 @@ $(function() {
 </div><!--header-->
 <div class="contents">
 <div class="textblock"><p ><a class="anchor" id="md_C__Users_25440_Desktop_test_packages_agile_modbus_latest_README"></a> </p>
-<h1><a class="anchor" id="autotoc_md10"></a>
+<h1><a class="anchor" id="autotoc_md13"></a>
 1、介绍</h1>
 <p >Agile Modbus 即:轻量型 modbus 协议栈,满足用户任何场景下的使用需求。</p>
-<h2><a class="anchor" id="autotoc_md11"></a>
+<p ><img src="./figures/ModbusProtocol.jpg" alt="ModbusProtocol" class="inline"/></p>
+<h2><a class="anchor" id="autotoc_md14"></a>
 1.1、特性</h2>
 <ol type="1">
 <li>支持 rtu 及 tcp 协议,使用纯 C 开发,不涉及任何硬件接口,可在任何形式的硬件上直接使用。</li>
@@ -56,24 +57,26 @@ $(function() {
 <li>同时支持多主机和多从机。</li>
 <li>使用简单,只需要将 rtu 或 tcp 句柄初始化好后,调用相应 API 进行组包和解包即可。</li>
 </ol>
-<h2><a class="anchor" id="autotoc_md12"></a>
+<h2><a class="anchor" id="autotoc_md15"></a>
 1.2、目录结构</h2>
 <table class="markdownTable">
 <tr class="markdownTableHead">
 <th class="markdownTableHeadNone">名称   </th><th class="markdownTableHeadNone">说明    </th></tr>
 <tr class="markdownTableRowOdd">
-<td class="markdownTableBodyNone">doc   </td><td class="markdownTableBodyNone">文档目录    </td></tr>
+<td class="markdownTableBodyNone">doc   </td><td class="markdownTableBodyNone">文档    </td></tr>
 <tr class="markdownTableRowEven">
-<td class="markdownTableBodyNone">examples   </td><td class="markdownTableBodyNone">例子目录    </td></tr>
+<td class="markdownTableBodyNone">examples   </td><td class="markdownTableBodyNone">例子    </td></tr>
 <tr class="markdownTableRowOdd">
-<td class="markdownTableBodyNone">inc   </td><td class="markdownTableBodyNone">头文件目录    </td></tr>
+<td class="markdownTableBodyNone">figures   </td><td class="markdownTableBodyNone">素材    </td></tr>
 <tr class="markdownTableRowEven">
-<td class="markdownTableBodyNone">src   </td><td class="markdownTableBodyNone">源代码目录   </td></tr>
+<td class="markdownTableBodyNone">inc   </td><td class="markdownTableBodyNone">头文件    </td></tr>
+<tr class="markdownTableRowOdd">
+<td class="markdownTableBodyNone">src   </td><td class="markdownTableBodyNone">源代码   </td></tr>
 </table>
-<h2><a class="anchor" id="autotoc_md13"></a>
+<h2><a class="anchor" id="autotoc_md16"></a>
 1.3、许可证</h2>
 <p >Agile Modbus 遵循 LGPLv2.1 许可,详见 <code>LICENSE</code> 文件。</p>
-<h1><a class="anchor" id="autotoc_md14"></a>
+<h1><a class="anchor" id="autotoc_md17"></a>
 2、使用 Agile Modbus</h1>
 <ul>
 <li>帮助文档请查看 <a href="./doc/doxygen/Agile_Modbus.chm">doc/doxygen/Agile_Modbus.chm</a></li>
@@ -110,8 +113,20 @@ $(function() {
 <li><code>发送数据</code></li>
 </ol>
 </li>
+<li><p class="startli">特殊功能码</p>
+<p class="startli">需要调用 <code>agile_modbus_set_compute_meta_length_after_function_cb</code> 和 <code>agile_modbus_set_compute_data_length_after_meta_cb</code> API 设置特殊功能码在主从模式下处理的回调。</p><ul>
+<li><p class="startli"><code>agile_modbus_set_compute_meta_length_after_function_cb</code></p>
+<p class="startli"><code>msg_type == AGILE_MODBUS_MSG_INDICATION</code>: 返回主机请求报文的数据元长度(uint8_t 类型),不是特殊功能码必须返回 0。</p>
+<p class="startli"><code>msg_type == MSG_CONFIRMATION</code>: 返回从机响应报文的数据元长度(uint8_t 类型),不是特殊功能码必须返回 1。</p>
+</li>
+<li><p class="startli"><code>agile_modbus_set_compute_data_length_after_meta_cb</code></p>
+<p class="startli"><code>msg_type == AGILE_MODBUS_MSG_INDICATION</code>: 返回主机请求报文数据元之后的数据长度,不是特殊功能码必须返回 0。</p>
+<p class="startli"><code>msg_type == MSG_CONFIRMATION</code>: 返回从机响应报文数据元之后的数据长度,不是特殊功能码必须返回 0。</p>
+</li>
+</ul>
+</li>
 <li><p class="startli"><code>agile_modbus_rtu_init</code> / <code>agile_modbus_tcp_init</code></p>
-<p class="startli">初始化 <code>RTU/TCP</code> 环境时需要用户传入 <code>发送缓冲区</code> 和 <code>接收缓冲区</code>,建议这两个缓冲区大小都为 <code>AGILE_MODBUS_MAX_ADU_LENGTH</code> (260) 字节。</p>
+<p class="startli">初始化 <code>RTU/TCP</code> 环境时需要用户传入 <code>发送缓冲区</code> 和 <code>接收缓冲区</code>,建议这两个缓冲区大小都为 <code>AGILE_MODBUS_MAX_ADU_LENGTH</code> (260) 字节。<code>特殊功能码</code> 情况用户根据协议自行决定。</p>
 <p class="startli">但对于小内存 MCU,这两个缓冲区也可以设置小,所有 API 都会对缓冲区大小进行判断:</p>
 <p class="startli">发送缓冲区设置:如果 <code>预期请求的数据长度</code> 或 <code>预期响应的数据长度</code> 大于 <code>设置的发送缓冲区大小</code>,返回异常。</p>
 <p class="startli">接收缓冲区设置:如果 <code>主机请求的报文长度</code> 大于 <code>设置的接收缓冲区大小</code>,返回异常。这个是合理的,小内存 MCU 做从机肯定是需要对某些功能码做限制的。</p>
@@ -120,7 +135,7 @@ $(function() {
 <div class="fragment"><div class="line"><span class="keywordtype">int</span> <a class="code hl_function" href="group___slave___operation___functions.html#ga35776da46b116038a0395e56f2750403">agile_modbus_slave_handle</a>(<a class="code hl_struct" href="structagile__modbus.html">agile_modbus_t</a> *ctx, <span class="keywordtype">int</span> msg_length, uint8_t slave_strict,</div>
 <div class="line">                            <a class="code hl_typedef" href="group___slave___exported___types.html#ga5df67b94293bd33bdd5ff6b5ccb0901e">agile_modbus_slave_callback_t</a> slave_cb, <span class="keywordtype">int</span> *frame_length)</div>
 <div class="ttc" id="agroup___slave___exported___types_html_ga5df67b94293bd33bdd5ff6b5ccb0901e"><div class="ttname"><a href="group___slave___exported___types.html#ga5df67b94293bd33bdd5ff6b5ccb0901e">agile_modbus_slave_callback_t</a></div><div class="ttdeci">int(* agile_modbus_slave_callback_t)(agile_modbus_t *ctx, struct agile_modbus_slave_info *slave_info)</div><div class="ttdoc">从机回调函数</div><div class="ttdef"><b>Definition:</b> <a href="agile__modbus_8h_source.html#l00251">agile_modbus.h:251</a></div></div>
-<div class="ttc" id="agroup___slave___operation___functions_html_ga35776da46b116038a0395e56f2750403"><div class="ttname"><a href="group___slave___operation___functions.html#ga35776da46b116038a0395e56f2750403">agile_modbus_slave_handle</a></div><div class="ttdeci">int agile_modbus_slave_handle(agile_modbus_t *ctx, int msg_length, uint8_t slave_strict, agile_modbus_slave_callback_t slave_cb, int *frame_length)</div><div class="ttdoc">从机数据处理</div><div class="ttdef"><b>Definition:</b> <a href="agile__modbus_8c_source.html#l01205">agile_modbus.c:1205</a></div></div>
+<div class="ttc" id="agroup___slave___operation___functions_html_ga35776da46b116038a0395e56f2750403"><div class="ttname"><a href="group___slave___operation___functions.html#ga35776da46b116038a0395e56f2750403">agile_modbus_slave_handle</a></div><div class="ttdeci">int agile_modbus_slave_handle(agile_modbus_t *ctx, int msg_length, uint8_t slave_strict, agile_modbus_slave_callback_t slave_cb, int *frame_length)</div><div class="ttdoc">从机数据处理</div><div class="ttdef"><b>Definition:</b> <a href="agile__modbus_8c_source.html#l01203">agile_modbus.c:1203</a></div></div>
 <div class="ttc" id="astructagile__modbus_html"><div class="ttname"><a href="structagile__modbus.html">agile_modbus</a></div><div class="ttdoc">Agile Modbus 结构体</div><div class="ttdef"><b>Definition:</b> <a href="agile__modbus_8h_source.html#l00204">agile_modbus.h:204</a></div></div>
 </div><!-- fragment --><p class="startli">msg_length: <code>等待数据接收结束</code> 后接收到的数据长度</p>
 <p class="startli">slave_strict: 从机地址严格性检查 (0: 不判断地址是否一致,由用户回调处理; 1: 地址必须一致,否则不会调用回调,也不打包响应数据)。</p>
@@ -143,7 +158,7 @@ $(function() {
 <p class="startli">buf: 不同功能码需要使用的数据域 (不是所有功能码都用到)</p>
 <p class="startli">send_index: 发送缓冲区当前索引 (不是所有功能码都用到)</p>
 </li>
-<li><p class="startli"><code><a class="el" href="structagile__modbus__slave__info.html" title="Agile Modbus 从机信息结构体">agile_modbus_slave_info</a></code> 不同功能码使用</p><ul>
+<li><code><a class="el" href="structagile__modbus__slave__info.html" title="Agile Modbus 从机信息结构体">agile_modbus_slave_info</a></code> 不同功能码使用<ul>
 <li><p class="startli">AGILE_MODBUS_FC_READ_COILS、AGILE_MODBUS_FC_READ_DISCRETE_INPUTS</p>
 <p class="startli">需要使用到 <code>address</code>、<code>nb</code>、<code>send_index</code> 属性,需要调用 <code>agile_modbus_slave_io_set</code> API 将 IO 数据存放到 <code>ctx-&gt;send_buf + send_index</code> 开始的数据区域。</p>
 </li>
@@ -165,25 +180,34 @@ $(function() {
 <li><p class="startli">AGILE_MODBUS_FC_WRITE_AND_READ_REGISTERS</p>
 <p class="startli">需要使用到 <code>address</code>、<code>buf</code>、<code>send_index</code> 属性,通过 <code>(buf[0] &lt;&lt; 8) + buf[1]</code> 获取要读取的寄存器数目,通过 <code>(buf[2] &lt;&lt; 8) + buf[3]</code> 获取要写入的寄存器地址,通过 <code>(buf[4] &lt;&lt; 8) + buf[5]</code> 获取要写入的寄存器数目。需要调用 <code>agile_modbus_slave_register_get</code> API 获取要写入的寄存器数据,调用 <code>agile_modbus_slave_register_set</code> API 将寄存器数据存放到 <code>ctx-&gt;send_buf + send_index</code> 开始的数据区域。</p>
 </li>
-<li>自定义功能码</li>
-</ul>
+<li><p class="startli">自定义功能码</p>
 <p class="startli">需要使用到 <code>send_index</code>、<code>nb</code>、<code>buf</code> 属性,用户在回调中处理数据。</p>
 <p class="startli">send_index: 发送缓冲区当前索引</p>
 <p class="startli">nb: PUD - 1,也就是 modbus 数据域长度</p>
 <p class="startli">buf: modbus 数据域起始位置</p>
+<p class="startli"><b>注意</b>: 用户在回调中往发送缓冲区填入数据后,需要更新 <code><a class="el" href="structagile__modbus__slave__info.html" title="Agile Modbus 从机信息结构体">agile_modbus_slave_info</a></code> 的 <code>rsp_length</code> 值。</p>
 </li>
 </ul>
-<h2><a class="anchor" id="autotoc_md15"></a>
+</li>
+</ul>
+<h2><a class="anchor" id="autotoc_md18"></a>
 2.1、示例</h2>
-<p >使用示例在 <a href="./examples">examples</a> 下。</p>
-<h2><a class="anchor" id="autotoc_md16"></a>
+<p ><a href="./examples">examples</a> 文件夹中提供 PC 上的示例,可以在 <code>WSL</code> 或 <code>Linux</code> 下编译运行。</p>
+<ul>
+<li>RTU / TCP 主机、从机的示例</li>
+<li><p class="startli">特殊功能码的示例</p>
+<p class="startli">RTU 点对点传输文件: 演示特殊功能码的使用方式</p>
+<p class="startli">RTU 广播传输文件: 演示 <code>agile_modbus_slave_handle</code> 中 <code>frame_length</code> 的用处</p>
+</li>
+</ul>
+<h2><a class="anchor" id="autotoc_md19"></a>
 2.2、Doxygen 文档生成</h2>
 <ul>
 <li>使用 <code>Doxywizard</code> 打开 <a href="./doc/doxygen/Doxyfile">Doxyfile</a> 运行,生成的文件在 <a href="./doc/doxygen/output">doxygen/output</a> 下。</li>
 <li>需要更改 <code>Graphviz</code> 路径。</li>
 <li><code>HTML</code> 生成未使用 <code>chm</code> 格式的,如果使能需要更改 <code>hhc.exe</code> 路径。</li>
 </ul>
-<h1><a class="anchor" id="autotoc_md17"></a>
+<h1><a class="anchor" id="autotoc_md20"></a>
 3、联系方式 &amp; 感谢</h1>
 <ul>
 <li>维护:马龙伟</li>

+ 14 - 1
examples/Makefile

@@ -5,7 +5,8 @@ OBJSDIR = ./build
 
 .PHONY: all clean
 
-TARGETS = ./rtu_master/RtuMaster ./tcp_master/TcpMaster ./slave/ModbusSlave
+TARGETS = ./rtu_master/RtuMaster ./tcp_master/TcpMaster ./slave/ModbusSlave \
+		  ./rtu_p2p/p2p_master ./rtu_p2p/p2p_slave
 
 COMMON_SRCS = $(wildcard ../src/*.c) $(wildcard ./common/*.c)
 COMMON_OBJS = $(patsubst %.c,./$(OBJSDIR)/%.o,$(notdir $(COMMON_SRCS)))
@@ -24,6 +25,12 @@ all: $(TARGETS)
 ./slave/ModbusSlave : $(COMMON_OBJS) $(SLAVE_OBJS)
 	${CC} $^ -g -o $@ ${LDFLAGS}
 
+./rtu_p2p/p2p_master : $(COMMON_OBJS) ./$(OBJSDIR)/p2p_master.o
+	${CC} $^ -g -o $@ ${LDFLAGS}
+
+./rtu_p2p/p2p_slave : $(COMMON_OBJS) ./$(OBJSDIR)/p2p_slave.o
+	${CC} $^ -g -o $@ ${LDFLAGS}
+
 ./$(OBJSDIR)/%.o : ../src/%.c
 	@if [ ! -d $(OBJSDIR) ]; then \
 		mkdir -p $(OBJSDIR); \
@@ -42,6 +49,12 @@ all: $(TARGETS)
 ./$(OBJSDIR)/%.o : ./slave/%.c
 	${CC} -g -c $< -o $@ ${CFLAGS}
 
+./$(OBJSDIR)/%.o : ./rtu_p2p/%.c
+	${CC} -g -c $< -o $@ ${CFLAGS}
+
+./$(OBJSDIR)/%.o : ./rtu_broadcast/%.c
+	${CC} -g -c $< -o $@ ${CFLAGS}
+
 clean:
 	$(RM) $(TARGETS)
 	$(RM) ./$(OBJSDIR)/*.o

+ 63 - 0
examples/README.md

@@ -105,3 +105,66 @@
   将 `Modbus Poll` 的 poll 界面关闭,看控制台打印可以看到 close 报文。
 
   ![ModbusSlaveTimeoutShow](./figures/ModbusSlaveTimeoutShow.jpg)
+
+### 2.3、RTU 传输文件
+
+![ModbusProtocol](./figures/ModbusProtocol.jpg)
+
+使用 `0x50` 作为传输文件的特殊功能码。
+
+分包传输文件数据,每包数据最大 1024 字节。
+
+`Data` 字段协议定义:
+
+- 主机请求
+
+  | 命令 | 字节数 | 数据 |
+  | ---- | ---- | ---- |
+  | 2 Bytes | 2 Bytes | N Bytes |
+
+  命令:
+
+  | 命令 | 说明 | 数据 |
+  | ---- | ---- | ---- |
+  | 0x0001 | 开始发送 | 文件大小(4 Bytes) + 文件名称(字符串) |
+  | 0x0002 | 传输数据 | 标志(1 Byte) + 文件数据 |
+
+  标志:
+
+  | 状态 | 说明 |
+  | ---- | ---- |
+  | 0x00 | 最后一包数据 |
+  | 0x01 | 不是最后一包数据 |
+
+- 从机响应
+
+  | 命令 | 状态 |
+  | ---- | ---- |
+  | 2 Bytes | 1 Byte |
+
+  状态:
+
+  | 状态 | 说明 |
+  | ---- | ---- |
+  | 0x00 | 失败 |
+  | 0x01 | 成功 |
+
+#### 2.3.1、点对点传输
+
+- 使用虚拟串口软件虚拟出 3 个串口,组成串口群组
+
+  这里我使用的时 MX 虚拟串口
+
+  ![VirtualComGroup](./figures/VirtualComGroup.jpg)
+
+- 进入 `rtu_p2p` 目录,打开 `Linux Shell`,演示效果如下
+
+  **注意**:
+
+  - 传输的文件必须是一般文件,像可执行文件、目录等不支持
+
+  - 从机接收到数据后修改文件名称 (从机地址_原文件名) 写入在当前目录。
+
+  ![rtu_p2p](./figures/rtu_p2p.gif)
+
+#### 2.3.2、广播传输

BIN
examples/figures/ModbusProtocol.jpg


BIN
examples/figures/VirtualComGroup.jpg


BIN
examples/figures/rtu_p2p.gif


+ 338 - 0
examples/rtu_p2p/p2p_master.c

@@ -0,0 +1,338 @@
+#include "agile_modbus.h"
+#include "serial.h"
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#define DBG_ENABLE
+#define DBG_COLOR
+#define DBG_SECTION_NAME "p2p_master"
+#define DBG_LEVEL        DBG_LOG
+#include "dbg_log.h"
+
+static int _fd = -1;
+static struct termios _old_tios = {0};
+
+#define AGILE_MODBUS_FC_TRANS_FILE 0x50
+#define TRANS_FILE_CMD_START       0x0001
+#define TRANS_FILE_CMD_DATA        0x0002
+#define TRANS_FILE_FLAG_END        0x00
+#define TRANS_FILE_FLAG_NOT_END    0x01
+
+static uint8_t compute_meta_length_after_function_callback(agile_modbus_t *ctx, int function,
+                                                           agile_modbus_msg_type_t msg_type)
+{
+    int length;
+
+    if (msg_type == AGILE_MODBUS_MSG_INDICATION) {
+        length = 0;
+        if (function == AGILE_MODBUS_FC_TRANS_FILE)
+            length = 4;
+    } else {
+        /* MSG_CONFIRMATION */
+        length = 1;
+        if (function == AGILE_MODBUS_FC_TRANS_FILE)
+            length = 3;
+    }
+
+    return length;
+}
+
+static int compute_data_length_after_meta_callback(agile_modbus_t *ctx, uint8_t *msg,
+                                                   int msg_length, agile_modbus_msg_type_t msg_type)
+{
+    int function = msg[ctx->backend->header_length];
+    int length;
+
+    if (msg_type == AGILE_MODBUS_MSG_INDICATION) {
+        length = 0;
+        if (function == AGILE_MODBUS_FC_TRANS_FILE)
+            length = (msg[ctx->backend->header_length + 3] << 8) + msg[ctx->backend->header_length + 4];
+    } else {
+        /* MSG_CONFIRMATION */
+        length = 0;
+    }
+
+    return length;
+}
+
+static void print_progress(size_t cur_size, size_t total_size)
+{
+    static uint8_t progress_sign[100 + 1];
+    uint8_t i, per = cur_size * 100 / total_size;
+
+    if (per > 100) {
+        per = 100;
+    }
+
+    for (i = 0; i < 100; i++) {
+        if (i < per) {
+            progress_sign[i] = '=';
+        } else if (per == i) {
+            progress_sign[i] = '>';
+        } else {
+            progress_sign[i] = ' ';
+        }
+    }
+
+    progress_sign[sizeof(progress_sign) - 1] = '\0';
+
+    LOG_I("\033[2A");
+    LOG_I("Trans: [%s] %d%%", progress_sign, per);
+}
+
+static char *normalize_path(char *fullpath)
+{
+    char *dst0, *dst, *src;
+
+    src = fullpath;
+    dst = fullpath;
+
+    dst0 = dst;
+    while (1) {
+        char c = *src;
+
+        if (c == '.') {
+            if (!src[1])
+                src++; /* '.' and ends */
+            else if (src[1] == '/') {
+                /* './' case */
+                src += 2;
+
+                while ((*src == '/') && (*src != '\0'))
+                    src++;
+                continue;
+            } else if (src[1] == '.') {
+                if (!src[2]) {
+                    /* '..' and ends case */
+                    src += 2;
+                    goto up_one;
+                } else if (src[2] == '/') {
+                    /* '../' case */
+                    src += 3;
+
+                    while ((*src == '/') && (*src != '\0'))
+                        src++;
+                    goto up_one;
+                }
+            }
+        }
+
+        /* copy up the next '/' and erase all '/' */
+        while ((c = *src++) != '\0' && c != '/')
+            *dst++ = c;
+
+        if (c == '/') {
+            *dst++ = '/';
+            while (c == '/')
+                c = *src++;
+
+            src--;
+        } else if (!c)
+            break;
+
+        continue;
+
+    up_one:
+        dst--;
+        if (dst < dst0)
+            return NULL;
+        while (dst0 < dst && dst[-1] != '/')
+            dst--;
+    }
+
+    *dst = '\0';
+
+    /* remove '/' in the end of path if exist */
+    dst--;
+    if ((dst != fullpath) && (*dst == '/'))
+        *dst = '\0';
+
+    return fullpath;
+}
+
+static int trans_file(int slave, char *file_path)
+{
+    uint8_t ctx_send_buf[2048];
+    uint8_t ctx_read_buf[50];
+    uint8_t raw_req[2048];
+    int raw_req_len = 0;
+
+    agile_modbus_rtu_t ctx_rtu;
+    agile_modbus_t *ctx = &ctx_rtu._ctx;
+    agile_modbus_rtu_init(&ctx_rtu, ctx_send_buf, sizeof(ctx_send_buf), ctx_read_buf, sizeof(ctx_read_buf));
+    agile_modbus_set_slave(ctx, slave);
+    agile_modbus_set_compute_meta_length_after_function_cb(ctx, compute_meta_length_after_function_callback);
+    agile_modbus_set_compute_data_length_after_meta_cb(ctx, compute_data_length_after_meta_callback);
+
+    if (normalize_path(file_path) == NULL)
+        return -1;
+
+    const char *file_name = file_path;
+    while (1) {
+        const char *ptr = strchr(file_name, '/');
+        if (ptr == NULL)
+            break;
+
+        file_name = ptr + 1;
+    }
+
+    struct stat s;
+    if (stat(file_path, &s) != 0)
+        return -1;
+
+    if (!S_ISREG(s.st_mode))
+        return -1;
+
+    int file_size = s.st_size;
+
+    LOG_I("file name:%s, file size:%d", file_name, file_size);
+    printf("\r\n\r\n");
+
+    FILE *fp = fopen(file_path, "rb");
+    if (fp == NULL)
+        return -1;
+
+    int ret = 0;
+    int write_file_size = 0;
+    int step = 0;
+
+    raw_req[0] = slave;
+    raw_req[1] = AGILE_MODBUS_FC_TRANS_FILE;
+
+    while (1) {
+        usleep(10000);
+
+        raw_req_len = 2;
+
+        switch (step) {
+        case 0: {
+            step = 1;
+            raw_req[raw_req_len++] = ((uint16_t)TRANS_FILE_CMD_START >> 8);
+            raw_req[raw_req_len++] = ((uint16_t)TRANS_FILE_CMD_START & 0xFF);
+            int nb = 5 + strlen(file_name);
+            raw_req[raw_req_len++] = nb >> 8;
+            raw_req[raw_req_len++] = nb & 0xFF;
+            raw_req[raw_req_len++] = (file_size >> 24) & 0xFF;
+            raw_req[raw_req_len++] = (file_size >> 16) & 0xFF;
+            raw_req[raw_req_len++] = (file_size >> 8) & 0xFF;
+            raw_req[raw_req_len++] = file_size & 0xFF;
+            memcpy(raw_req + raw_req_len, file_name, strlen(file_name));
+            raw_req_len += strlen(file_name);
+            raw_req[raw_req_len++] = '\0';
+        } break;
+
+        case 1: {
+            raw_req[raw_req_len++] = ((uint16_t)TRANS_FILE_CMD_DATA >> 8);
+            raw_req[raw_req_len++] = ((uint16_t)TRANS_FILE_CMD_DATA & 0xFF);
+            int nb_pos = raw_req_len;
+            raw_req_len += 3;
+            int recv_bytes = fread(raw_req + raw_req_len, 1, 1024, fp);
+            raw_req_len += recv_bytes;
+            write_file_size += recv_bytes;
+            int nb = recv_bytes + 1;
+            raw_req[nb_pos] = nb >> 8;
+            raw_req[nb_pos + 1] = nb & 0xFF;
+            if (recv_bytes < 1024) {
+                raw_req[nb_pos + 2] = TRANS_FILE_FLAG_END;
+                step = 2;
+            } else
+                raw_req[nb_pos + 2] = TRANS_FILE_FLAG_NOT_END;
+        } break;
+
+        default:
+            break;
+        }
+
+        if (ret < 0)
+            break;
+
+        serial_flush(_fd);
+        int send_len = agile_modbus_serialize_raw_request(ctx, raw_req, raw_req_len);
+        serial_send(_fd, ctx->send_buf, send_len);
+        int read_len = serial_receive(_fd, ctx->read_buf, ctx->read_bufsz, 1000);
+        if (read_len < 0) {
+            LOG_E("Receive error.");
+            ret = -1;
+            break;
+        }
+
+        if (read_len == 0) {
+            LOG_W("Receive timeout.");
+            ret = -1;
+            break;
+        }
+
+        int rc = agile_modbus_deserialize_raw_response(ctx, read_len);
+        if (rc < 0) {
+            LOG_W("Receive failed.");
+            ret = -1;
+            break;
+        }
+
+        int flag = ctx->read_buf[ctx->backend->header_length + 3];
+        if (flag != 0x01) {
+            LOG_W("ack flag is failed.");
+            ret = -1;
+            break;
+        }
+
+        print_progress(write_file_size, file_size);
+
+        if (step == 2)
+            break;
+    }
+
+    fclose(fp);
+    fp = NULL;
+
+    return ret;
+}
+
+static void *cycle_entry(void *param)
+{
+    int slave;
+    char tmp[100];
+
+    while (1) {
+        printf("please enter slave:\r\n");
+        fgets(tmp, sizeof(tmp), stdin);
+        slave = atoi(tmp);
+        if (slave <= 0) {
+            LOG_W("slave must be greater than 0");
+            continue;
+        }
+
+        printf("please enter file_path:\r\n");
+        fgets(tmp, sizeof(tmp), stdin);
+        for (int i = 0; i < strlen(tmp); i++) {
+            if (tmp[i] == '\r' || tmp[i] == '\n') {
+                tmp[i] = '\0';
+                break;
+            }
+        }
+
+        trans_file(slave, tmp);
+    }
+}
+
+int main(int argc, char *argv[])
+{
+    if (argc < 2) {
+        LOG_E("Please enter p2p_master [dev]!");
+        return -1;
+    }
+
+    _fd = serial_init(argv[1], 9600, 'N', 8, 1, &_old_tios);
+    if (_fd < 0) {
+        LOG_E("Open %s failed!", argv[1]);
+        return -1;
+    }
+
+    pthread_t tid;
+    pthread_create(&tid, NULL, cycle_entry, NULL);
+    pthread_join(tid, NULL);
+}

+ 267 - 0
examples/rtu_p2p/p2p_slave.c

@@ -0,0 +1,267 @@
+#include "serial.h"
+#include "agile_modbus.h"
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#define DBG_ENABLE
+#define DBG_COLOR
+#define DBG_SECTION_NAME "p2p_slave"
+#define DBG_LEVEL        DBG_LOG
+#include "dbg_log.h"
+
+static int _fd = -1;
+static struct termios _old_tios = {0};
+static FILE *_fp = NULL;
+static int _slave = 0;
+static int _file_size = 0;
+static int _write_file_size = 0;
+
+#define AGILE_MODBUS_FC_TRANS_FILE 0x50
+#define TRANS_FILE_CMD_START       0x0001
+#define TRANS_FILE_CMD_DATA        0x0002
+#define TRANS_FILE_FLAG_END        0x00
+#define TRANS_FILE_FLAG_NOT_END    0x01
+
+static uint8_t compute_meta_length_after_function_callback(agile_modbus_t *ctx, int function,
+                                                           agile_modbus_msg_type_t msg_type)
+{
+    int length;
+
+    if (msg_type == AGILE_MODBUS_MSG_INDICATION) {
+        length = 0;
+        if (function == AGILE_MODBUS_FC_TRANS_FILE)
+            length = 4;
+    } else {
+        /* MSG_CONFIRMATION */
+        length = 1;
+        if (function == AGILE_MODBUS_FC_TRANS_FILE)
+            length = 3;
+    }
+
+    return length;
+}
+
+static int compute_data_length_after_meta_callback(agile_modbus_t *ctx, uint8_t *msg,
+                                                   int msg_length, agile_modbus_msg_type_t msg_type)
+{
+    int function = msg[ctx->backend->header_length];
+    int length;
+
+    if (msg_type == AGILE_MODBUS_MSG_INDICATION) {
+        length = 0;
+        if (function == AGILE_MODBUS_FC_TRANS_FILE)
+            length = (msg[ctx->backend->header_length + 3] << 8) + msg[ctx->backend->header_length + 4];
+    } else {
+        /* MSG_CONFIRMATION */
+        length = 0;
+    }
+
+    return length;
+}
+
+static void print_progress(size_t cur_size, size_t total_size)
+{
+    static uint8_t progress_sign[100 + 1];
+    uint8_t i, per = cur_size * 100 / total_size;
+
+    if (per > 100) {
+        per = 100;
+    }
+
+    for (i = 0; i < 100; i++) {
+        if (i < per) {
+            progress_sign[i] = '=';
+        } else if (per == i) {
+            progress_sign[i] = '>';
+        } else {
+            progress_sign[i] = ' ';
+        }
+    }
+
+    progress_sign[sizeof(progress_sign) - 1] = '\0';
+
+    LOG_I("\033[2A");
+    LOG_I("Trans: [%s] %d%%", progress_sign, per);
+}
+
+/**
+ * @brief   从机回调函数
+ * @param   ctx modbus 句柄
+ * @param   slave_info 从机信息体
+ * @return  =0:正常;
+ *          <0:异常
+ *             (-AGILE_MODBUS_EXCEPTION_UNKNOW(-255): 未知异常,从机不会打包响应数据)
+ *             (其他负数异常码: 从机会打包异常响应数据)
+ */
+static int slave_callback(agile_modbus_t *ctx, struct agile_modbus_slave_info *slave_info)
+{
+    int function = slave_info->sft->function;
+
+    if (function != AGILE_MODBUS_FC_TRANS_FILE)
+        return 0;
+
+    int ret = 0;
+
+    int send_index = slave_info->send_index;
+    int data_len = slave_info->nb;
+    uint8_t *data_ptr = slave_info->buf;
+    int cmd = (data_ptr[0] << 8) + data_ptr[1];
+    int cmd_data_len = (data_ptr[2] << 8) + data_ptr[3];
+    uint8_t *cmd_data_ptr = data_ptr + 4;
+
+    switch (cmd) {
+    case TRANS_FILE_CMD_START: {
+        if (_fp != NULL) {
+            LOG_W("_fp is not NULL, now close _fp.");
+            fclose(_fp);
+            _fp = NULL;
+            ret = -1;
+            break;
+        }
+
+        if (cmd_data_len <= 4) {
+            LOG_W("cmd start date_len must be greater than 4.");
+            ret = -1;
+            break;
+        }
+
+        _file_size = (((int)cmd_data_ptr[0] << 24) +
+                      ((int)cmd_data_ptr[1] << 16) +
+                      ((int)cmd_data_ptr[2] << 8) +
+                      (int)cmd_data_ptr[3]);
+
+        _write_file_size = 0;
+
+        char *file_name = (char *)(data_ptr + 8);
+        if (strlen(file_name) >= 256) {
+            LOG_W("file name must be less than 256.");
+            ret = -1;
+            break;
+        }
+
+        char own_file_name[300];
+        snprintf(own_file_name, sizeof(own_file_name), "%d_%s", slave_info->sft->slave, file_name);
+
+        LOG_I("write to %s, file size is %d", own_file_name, _file_size);
+        printf("\r\n\r\n");
+
+        _fp = fopen(own_file_name, "wb");
+        if (_fp == NULL) {
+            LOG_W("open file %s error.", own_file_name);
+            ret = -1;
+            break;
+        }
+    } break;
+
+    case TRANS_FILE_CMD_DATA: {
+        if (_fp == NULL) {
+            LOG_W("_fp is NULL.");
+            ret = -1;
+            break;
+        }
+
+        if (cmd_data_len <= 0) {
+            LOG_W("cmd data data_len must be greater than 0");
+            ret = -1;
+            break;
+        }
+
+        int flag = cmd_data_ptr[0];
+        int file_len = cmd_data_len - 1;
+        if (file_len > 0) {
+            if (fwrite(cmd_data_ptr + 1, file_len, 1, _fp) != 1) {
+                LOG_W("write to file error.");
+                ret = -1;
+                break;
+            }
+        }
+        _write_file_size += file_len;
+
+        print_progress(_write_file_size, _file_size);
+
+        if (flag == TRANS_FILE_FLAG_END) {
+            fclose(_fp);
+            _fp = NULL;
+            if (_write_file_size != _file_size) {
+                LOG_W("_write_file_size (%d) != _file_size (%d)", _write_file_size, _file_size);
+                ret = -1;
+                break;
+            }
+        }
+
+    } break;
+
+    default:
+        ret = -1;
+        break;
+    }
+
+    ctx->send_buf[send_index++] = data_ptr[0];
+    ctx->send_buf[send_index++] = data_ptr[1];
+    ctx->send_buf[send_index++] = (ret == 0) ? 0x01 : 0x00;
+    *(slave_info->rsp_length) = send_index;
+
+    return 0;
+}
+
+static void *cycle_entry(void *param)
+{
+    uint8_t ctx_send_buf[50];
+    uint8_t ctx_read_buf[2048];
+
+    agile_modbus_rtu_t ctx_rtu;
+    agile_modbus_t *ctx = &ctx_rtu._ctx;
+    agile_modbus_rtu_init(&ctx_rtu, ctx_send_buf, sizeof(ctx_send_buf), ctx_read_buf, sizeof(ctx_read_buf));
+    agile_modbus_set_slave(ctx, _slave);
+    agile_modbus_set_compute_meta_length_after_function_cb(ctx, compute_meta_length_after_function_callback);
+    agile_modbus_set_compute_data_length_after_meta_cb(ctx, compute_data_length_after_meta_callback);
+
+    LOG_I("slave %d running.", _slave);
+
+    while (1) {
+        int read_len = serial_receive(_fd, ctx->read_buf, ctx->read_bufsz, 1000);
+        if (read_len < 0) {
+            LOG_E("Receive error, now exit.");
+            break;
+        }
+
+        if (read_len == 0)
+            continue;
+
+        int send_len = agile_modbus_slave_handle(ctx, read_len, 1, slave_callback, NULL);
+        serial_flush(_fd);
+        if (send_len > 0)
+            serial_send(_fd, ctx->send_buf, send_len);
+    }
+
+    serial_close(_fd, &_old_tios);
+}
+
+int main(int argc, char *argv[])
+{
+    if (argc < 3) {
+        LOG_E("Please enter p2p_slave [dev] [slave]!");
+        return -1;
+    }
+
+    _slave = atoi(argv[2]);
+    if (_slave <= 0) {
+        LOG_E("slave must be greater than 0!");
+        return -1;
+    }
+
+    _fd = serial_init(argv[1], 9600, 'N', 8, 1, &_old_tios);
+    if (_fd < 0) {
+        LOG_E("Open %s failed!", argv[1]);
+        return -1;
+    }
+
+    pthread_t tid;
+    pthread_create(&tid, NULL, cycle_entry, NULL);
+    pthread_join(tid, NULL);
+
+    return 0;
+}

+ 0 - 1
examples/slave/rtu_slave.c

@@ -18,7 +18,6 @@ static void *rtu_entry(void *param)
 {
     uint8_t ctx_send_buf[AGILE_MODBUS_MAX_ADU_LENGTH];
     uint8_t ctx_read_buf[AGILE_MODBUS_MAX_ADU_LENGTH];
-    uint16_t hold_register[10];
 
     agile_modbus_rtu_t ctx_rtu;
     agile_modbus_t *ctx = &ctx_rtu._ctx;

BIN
figures/ModbusProtocol.jpg


+ 2 - 4
src/agile_modbus.c

@@ -226,8 +226,6 @@ static int agile_modbus_receive_msg_judge(agile_modbus_t *ctx, uint8_t *msg, int
 {
     int remain_len = msg_length;
 
-    if (remain_len > (int)ctx->backend->max_adu_length)
-        return -1;
     remain_len -= (ctx->backend->header_length + 1);
     if (remain_len < 0)
         return -1;
@@ -1033,7 +1031,7 @@ int agile_modbus_deserialize_report_slave_id(agile_modbus_t *ctx, int msg_length
  */
 int agile_modbus_serialize_raw_request(agile_modbus_t *ctx, const uint8_t *raw_req, int raw_req_length)
 {
-    if ((raw_req_length < 2) || (raw_req_length > (AGILE_MODBUS_MAX_PDU_LENGTH + 1))) {
+    if (raw_req_length < 2) {
         /* The raw request must contain function and slave at least and
            must not be longer than the maximum pdu length plus the slave
            address. */
@@ -1241,7 +1239,7 @@ int agile_modbus_slave_handle(agile_modbus_t *ctx, int msg_length, uint8_t slave
     slave_info.address = address;
 
     if (slave_strict) {
-        if (slave != ctx->slave)
+        if ((slave != ctx->slave) && (slave != AGILE_MODBUS_BROADCAST_ADDRESS))
             return 0;
     }