Explorar el Código

fix: 修复Modbus数据读取失败 - TCP缓冲区残留+指令间隔不足+不完整帧读取

Coze-Commit-Type: user
Coze-User-ID: 256649704250362
Coze-Conversation-ID: 11640025
steven_roc hace 2 meses
padre
commit
81774e3eaf

+ 26 - 0
mppt_monitor/CHANGELOG.md

@@ -4,6 +4,32 @@
 
 ---
 
+## [2.0.1] - 2026-08-06
+
+### Bug 修复:Modbus 数据读取失败
+
+#### 问题描述
+升级到 V2.0.0 后,设备无法读取数据,日志显示 `Address mismatch: expected 1, got 105`。
+
+#### 根因分析
+1. **TCP 缓冲区残留数据**:发送 Output Registers 请求前,Input Registers 的响应数据可能未完全读取,残留字节被误解析为新响应的地址字节
+2. **指令间隔不足**:协议规范要求两条指令间隔 1 秒,原代码仅等待 200ms
+3. **不完整帧读取**:`recv()` 单次调用可能只读到部分响应,导致后续解析错位
+
+#### 修复内容
+- 新增 `drain_socket()` 函数:每次发送前清空 TCP 缓冲区残留数据
+- 实现完整 Modbus RTU 帧读取:根据 byte_count 字段判断帧完整性,循环读取直到完整
+- 连接建立后增加 200ms 稳定等待 + 缓冲区清空
+- 两条指令间隔从 200ms 增加到 **1000ms**(符合协议规范)
+
+#### 验证结果
+```
+Device #1 [ONLINE] Batt:28.0V(100%) PV:42.0V/1.7A/72W Temp:48C Fault:Normal
+Type:Custom (自定义) Mode:DC-DC SW:v115
+```
+
+---
+
 ## [2.0.0] - 2026-08-05
 
 ### 重大更新:DM Series ModBus Protocol V2.1 完整支持

BIN
mppt_monitor/build/mppt_monitor


BIN
mppt_monitor/mppt_data.db


+ 76 - 18
mppt_monitor/src/modbus_client.c

@@ -184,35 +184,89 @@ static int tcp_connect(const char *host, uint16_t port, int timeout_sec)
     return sockfd;
 }
 
-/* ---- Send Modbus RTU frame and receive response ---- */
+/* ---- Drain any leftover data from socket buffer ---- */
+static void drain_socket(int sockfd)
+{
+    uint8_t tmp[256];
+    struct timeval tv;
+    fd_set rfds;
+    
+    tv.tv_sec = 0;
+    tv.tv_usec = 100000;  /* 100ms timeout */
+    
+    while (1) {
+        FD_ZERO(&rfds);
+        FD_SET(sockfd, &rfds);
+        int ret = select(sockfd + 1, &rfds, NULL, NULL, &tv);
+        if (ret <= 0) break;
+        ssize_t n = recv(sockfd, tmp, sizeof(tmp), MSG_DONTWAIT);
+        if (n <= 0) break;
+    }
+}
+
+/* ---- Send Modbus RTU frame and receive complete response ---- */
 static int modbus_send_recv(int sockfd, const uint8_t *tx_frame, int tx_len,
                             uint8_t *rx_buf, int rx_buf_size, int timeout_sec)
 {
+    /* Drain any leftover data before sending */
+    drain_socket(sockfd);
+    
     /* Send */
     ssize_t sent = send(sockfd, tx_frame, tx_len, 0);
     if (sent != tx_len) {
         return -1;
     }
 
-    /* Receive with timeout */
-    fd_set rfds;
-    struct timeval tv;
-    FD_ZERO(&rfds);
-    FD_SET(sockfd, &rfds);
-    tv.tv_sec = timeout_sec;
-    tv.tv_usec = 0;
+    /* Receive with timeout - read complete response */
+    int total_received = 0;
+    int empty_reads = 0;
+    
+    while (total_received < rx_buf_size) {
+        fd_set rfds;
+        struct timeval tv;
+        FD_ZERO(&rfds);
+        FD_SET(sockfd, &rfds);
+        tv.tv_sec = (total_received == 0) ? timeout_sec : 1;
+        tv.tv_usec = 0;
 
-    int ret = select(sockfd + 1, &rfds, NULL, NULL, &tv);
-    if (ret <= 0) {
-        return -1;  /* Timeout or error */
-    }
+        int ret = select(sockfd + 1, &rfds, NULL, NULL, &tv);
+        if (ret < 0) return -1;  /* Error */
+        if (ret == 0) {
+            /* Timeout */
+            if (total_received > 0) break;  /* Got some data, use it */
+            return -1;  /* No data at all */
+        }
 
-    ssize_t received = recv(sockfd, rx_buf, rx_buf_size, 0);
-    if (received <= 0) {
-        return -1;
+        ssize_t received = recv(sockfd, rx_buf + total_received, 
+                                rx_buf_size - total_received, MSG_DONTWAIT);
+        if (received < 0) {
+            if (errno == EAGAIN || errno == EWOULDBLOCK) {
+                empty_reads++;
+                if (empty_reads > 5) break;
+                usleep(10000);  /* 10ms */
+                continue;
+            }
+            return -1;
+        }
+        if (received == 0) break;  /* Connection closed */
+        
+        total_received += (int)received;
+        empty_reads = 0;
+        
+        /* Check if we have a complete Modbus RTU frame */
+        if (total_received >= 5) {
+            /* For normal response: addr(1) + func(1) + byte_count(1) + data(N) + crc(2) */
+            if (!(rx_buf[1] & 0x80)) {
+                int expected = 3 + rx_buf[2] + 2;  /* header + data + crc */
+                if (total_received >= expected) break;  /* Complete frame */
+            } else {
+                /* Exception response: addr(1) + func|0x80(1) + exception(1) + crc(2) = 5 */
+                if (total_received >= 5) break;
+            }
+        }
     }
 
-    return (int)received;
+    return total_received;
 }
 
 /* ---- Build and send read input registers command (0x04) ---- */
@@ -369,8 +423,8 @@ static int poll_device(modbus_client_t *client, mppt_device_data_t *dev)
     dev->software_version_raw  = regs[10];  /* 30011 */
 
     /* Read Output Registers (0x03): 9 registers starting at 0x0000 */
-    /* Wait 1 second between commands per protocol spec */
-    usleep(200000);  /* 200ms - reduced for faster polling */
+    /* Protocol spec: two commands must have 1 second interval */
+    usleep(1000000);  /* 1 second delay per protocol specification */
 
     ret = read_output_registers(client->sockfd, dev->device_addr,
                                 0x0000, MODBUS_OUTPUT_REG_COUNT,
@@ -429,6 +483,10 @@ static void *poll_thread_func(void *arg)
             client->connected = true;
             printf("[MODBUS] Connected to %s:%d\n",
                    client->config.host, client->config.port);
+            
+            /* Drain any stale data after connecting */
+            usleep(200000);  /* 200ms for connection to stabilize */
+            drain_socket(client->sockfd);
         }
 
         /* Poll each device */