|
@@ -2,8 +2,9 @@
|
|
|
* @file modbus_client.c
|
|
* @file modbus_client.c
|
|
|
* @brief Modbus RTU over TCP client implementation
|
|
* @brief Modbus RTU over TCP client implementation
|
|
|
*
|
|
*
|
|
|
- * Implements CRC-16/Modbus, TCP connection management with timeout,
|
|
|
|
|
- * register read/parse, and auto-reconnect logic.
|
|
|
|
|
|
|
+ * DM Series ModBus 485 Protocol V2.1
|
|
|
|
|
+ * - Input Register (FC=0x04): 30001~30011 (11 registers, real-time data)
|
|
|
|
|
+ * - Output Register (FC=0x03): 40001~40009 (9 registers, parameters)
|
|
|
*/
|
|
*/
|
|
|
|
|
|
|
|
#include "modbus_client.h"
|
|
#include "modbus_client.h"
|
|
@@ -24,11 +25,6 @@
|
|
|
#include <sys/select.h>
|
|
#include <sys/select.h>
|
|
|
|
|
|
|
|
/* ── CRC-16/Modbus ─────────────────────────────────────────────── */
|
|
/* ── CRC-16/Modbus ─────────────────────────────────────────────── */
|
|
|
-/*
|
|
|
|
|
- * Standard Modbus CRC-16 calculation.
|
|
|
|
|
- * Polynomial: 0xA001 (reversed 0x8005)
|
|
|
|
|
- * Initial value: 0xFFFF
|
|
|
|
|
- */
|
|
|
|
|
uint16_t modbus_crc16(const uint8_t *buf, size_t len)
|
|
uint16_t modbus_crc16(const uint8_t *buf, size_t len)
|
|
|
{
|
|
{
|
|
|
uint16_t crc = 0xFFFF;
|
|
uint16_t crc = 0xFFFF;
|
|
@@ -74,12 +70,11 @@ static int tcp_connect_with_timeout(const char *host, uint16_t port, int timeout
|
|
|
char port_str[8];
|
|
char port_str[8];
|
|
|
|
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
memset(&hints, 0, sizeof(hints));
|
|
|
- hints.ai_family = AF_UNSPEC; /* IPv4 or IPv6 */
|
|
|
|
|
- hints.ai_socktype = SOCK_STREAM; /* TCP */
|
|
|
|
|
|
|
+ hints.ai_family = AF_UNSPEC;
|
|
|
|
|
+ hints.ai_socktype = SOCK_STREAM;
|
|
|
|
|
|
|
|
snprintf(port_str, sizeof(port_str), "%u", port);
|
|
snprintf(port_str, sizeof(port_str), "%u", port);
|
|
|
|
|
|
|
|
- /* DNS resolution */
|
|
|
|
|
ret = getaddrinfo(host, port_str, &hints, &res);
|
|
ret = getaddrinfo(host, port_str, &hints, &res);
|
|
|
if (ret != 0) {
|
|
if (ret != 0) {
|
|
|
fprintf(stderr, "[MODBUS] DNS resolution failed for %s:%u - %s\n",
|
|
fprintf(stderr, "[MODBUS] DNS resolution failed for %s:%u - %s\n",
|
|
@@ -87,23 +82,19 @@ static int tcp_connect_with_timeout(const char *host, uint16_t port, int timeout
|
|
|
return -1;
|
|
return -1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /* Try each resolved address */
|
|
|
|
|
for (rp = res; rp != NULL; rp = rp->ai_next) {
|
|
for (rp = res; rp != NULL; rp = rp->ai_next) {
|
|
|
sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
|
sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
|
|
if (sockfd < 0) continue;
|
|
if (sockfd < 0) continue;
|
|
|
|
|
|
|
|
- /* Set non-blocking for connect with timeout */
|
|
|
|
|
int flags = fcntl(sockfd, F_GETFL, 0);
|
|
int flags = fcntl(sockfd, F_GETFL, 0);
|
|
|
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
|
|
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
|
|
|
|
|
|
|
|
ret = connect(sockfd, rp->ai_addr, rp->ai_addrlen);
|
|
ret = connect(sockfd, rp->ai_addr, rp->ai_addrlen);
|
|
|
if (ret == 0) {
|
|
if (ret == 0) {
|
|
|
- /* Connected immediately */
|
|
|
|
|
fcntl(sockfd, F_SETFL, flags);
|
|
fcntl(sockfd, F_SETFL, flags);
|
|
|
break;
|
|
break;
|
|
|
}
|
|
}
|
|
|
if (errno == EINPROGRESS) {
|
|
if (errno == EINPROGRESS) {
|
|
|
- /* Wait for connection with timeout using select */
|
|
|
|
|
fd_set wfds;
|
|
fd_set wfds;
|
|
|
struct timeval tv;
|
|
struct timeval tv;
|
|
|
|
|
|
|
@@ -114,12 +105,10 @@ static int tcp_connect_with_timeout(const char *host, uint16_t port, int timeout
|
|
|
|
|
|
|
|
ret = select(sockfd + 1, NULL, &wfds, NULL, &tv);
|
|
ret = select(sockfd + 1, NULL, &wfds, NULL, &tv);
|
|
|
if (ret > 0 && FD_ISSET(sockfd, &wfds)) {
|
|
if (ret > 0 && FD_ISSET(sockfd, &wfds)) {
|
|
|
- /* Check for connection error */
|
|
|
|
|
int so_error;
|
|
int so_error;
|
|
|
socklen_t len = sizeof(so_error);
|
|
socklen_t len = sizeof(so_error);
|
|
|
getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &so_error, &len);
|
|
getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &so_error, &len);
|
|
|
if (so_error == 0) {
|
|
if (so_error == 0) {
|
|
|
- /* Connected successfully */
|
|
|
|
|
fcntl(sockfd, F_SETFL, flags);
|
|
fcntl(sockfd, F_SETFL, flags);
|
|
|
break;
|
|
break;
|
|
|
} else {
|
|
} else {
|
|
@@ -145,11 +134,9 @@ static int tcp_connect_with_timeout(const char *host, uint16_t port, int timeout
|
|
|
freeaddrinfo(res);
|
|
freeaddrinfo(res);
|
|
|
|
|
|
|
|
if (sockfd >= 0) {
|
|
if (sockfd >= 0) {
|
|
|
- /* Set TCP_NODELAY for low latency */
|
|
|
|
|
int flag = 1;
|
|
int flag = 1;
|
|
|
setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));
|
|
setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));
|
|
|
|
|
|
|
|
- /* Set receive timeout */
|
|
|
|
|
struct timeval recv_tv;
|
|
struct timeval recv_tv;
|
|
|
recv_tv.tv_sec = timeout_sec;
|
|
recv_tv.tv_sec = timeout_sec;
|
|
|
recv_tv.tv_usec = 0;
|
|
recv_tv.tv_usec = 0;
|
|
@@ -163,21 +150,19 @@ static int tcp_connect_with_timeout(const char *host, uint16_t port, int timeout
|
|
|
int modbus_connect(modbus_conn_t *conn)
|
|
int modbus_connect(modbus_conn_t *conn)
|
|
|
{
|
|
{
|
|
|
if (conn->state == MODBUS_STATE_CONNECTED && conn->sockfd >= 0) {
|
|
if (conn->state == MODBUS_STATE_CONNECTED && conn->sockfd >= 0) {
|
|
|
- return 0; /* Already connected */
|
|
|
|
|
|
|
+ return 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /* Rate limit reconnect attempts */
|
|
|
|
|
time_t now = time(NULL);
|
|
time_t now = time(NULL);
|
|
|
if (conn->last_connect_attempt > 0 &&
|
|
if (conn->last_connect_attempt > 0 &&
|
|
|
(now - conn->last_connect_attempt) < 2) {
|
|
(now - conn->last_connect_attempt) < 2) {
|
|
|
- return -1; /* Too soon, wait at least 2 seconds between attempts */
|
|
|
|
|
|
|
+ return -1;
|
|
|
}
|
|
}
|
|
|
conn->last_connect_attempt = now;
|
|
conn->last_connect_attempt = now;
|
|
|
|
|
|
|
|
printf("[MODBUS] Connecting to %s:%u ...\n", conn->host, conn->port);
|
|
printf("[MODBUS] Connecting to %s:%u ...\n", conn->host, conn->port);
|
|
|
conn->state = MODBUS_STATE_CONNECTING;
|
|
conn->state = MODBUS_STATE_CONNECTING;
|
|
|
|
|
|
|
|
- /* Close old socket if any */
|
|
|
|
|
if (conn->sockfd >= 0) {
|
|
if (conn->sockfd >= 0) {
|
|
|
close(conn->sockfd);
|
|
close(conn->sockfd);
|
|
|
conn->sockfd = -1;
|
|
conn->sockfd = -1;
|
|
@@ -211,8 +196,19 @@ void modbus_disconnect(modbus_conn_t *conn)
|
|
|
printf("[MODBUS] Disconnected\n");
|
|
printf("[MODBUS] Disconnected\n");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/* ── Read Registers ────────────────────────────────────────────── */
|
|
|
|
|
-int modbus_read_registers(modbus_conn_t *conn, mppt_data_t *data)
|
|
|
|
|
|
|
+/* ── Generic Register Read ─────────────────────────────────────── */
|
|
|
|
|
+/**
|
|
|
|
|
+ * Generic Modbus RTU register read.
|
|
|
|
|
+ * @param conn Active connection
|
|
|
|
|
+ * @param func_code Function code (0x03 or 0x04)
|
|
|
|
|
+ * @param start_reg Start register address
|
|
|
|
|
+ * @param reg_count Number of registers to read
|
|
|
|
|
+ * @param out_regs Output array for register values (must have reg_count elements)
|
|
|
|
|
+ * @return 0 on success, -1 on failure
|
|
|
|
|
+ */
|
|
|
|
|
+static int modbus_read_generic(modbus_conn_t *conn, uint8_t func_code,
|
|
|
|
|
+ uint16_t start_reg, uint16_t reg_count,
|
|
|
|
|
+ uint16_t *out_regs)
|
|
|
{
|
|
{
|
|
|
uint8_t request[8];
|
|
uint8_t request[8];
|
|
|
uint8_t response[256];
|
|
uint8_t response[256];
|
|
@@ -224,24 +220,19 @@ int modbus_read_registers(modbus_conn_t *conn, mppt_data_t *data)
|
|
|
return -1;
|
|
return -1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /*
|
|
|
|
|
- * Build Modbus RTU request frame:
|
|
|
|
|
- * [Slave Addr] [Func Code] [Start Reg Hi] [Start Reg Lo]
|
|
|
|
|
- * [Reg Count Hi] [Reg Count Lo] [CRC Lo] [CRC Hi]
|
|
|
|
|
- */
|
|
|
|
|
- request[0] = conn->slave_addr; /* Slave address */
|
|
|
|
|
- request[1] = MODBUS_FUNC_READ; /* Function code 04 */
|
|
|
|
|
- request[2] = (MODBUS_REG_START >> 8) & 0xFF; /* Start register high */
|
|
|
|
|
- request[3] = MODBUS_REG_START & 0xFF; /* Start register low */
|
|
|
|
|
- request[4] = (MODBUS_REG_COUNT >> 8) & 0xFF; /* Register count high */
|
|
|
|
|
- request[5] = MODBUS_REG_COUNT & 0xFF; /* Register count low */
|
|
|
|
|
-
|
|
|
|
|
- /* Calculate CRC over first 6 bytes */
|
|
|
|
|
|
|
+ /* Build request: [Addr][FC][StartHi][StartLo][CountHi][CountLo][CRCLo][CRCHi] */
|
|
|
|
|
+ request[0] = conn->slave_addr;
|
|
|
|
|
+ request[1] = func_code;
|
|
|
|
|
+ request[2] = (start_reg >> 8) & 0xFF;
|
|
|
|
|
+ request[3] = start_reg & 0xFF;
|
|
|
|
|
+ request[4] = (reg_count >> 8) & 0xFF;
|
|
|
|
|
+ request[5] = reg_count & 0xFF;
|
|
|
|
|
+
|
|
|
crc_calc = modbus_crc16(request, 6);
|
|
crc_calc = modbus_crc16(request, 6);
|
|
|
- request[6] = crc_calc & 0xFF; /* CRC low byte */
|
|
|
|
|
- request[7] = (crc_calc >> 8) & 0xFF; /* CRC high byte */
|
|
|
|
|
|
|
+ request[6] = crc_calc & 0xFF;
|
|
|
|
|
+ request[7] = (crc_calc >> 8) & 0xFF;
|
|
|
|
|
|
|
|
- /* Send request */
|
|
|
|
|
|
|
+ /* Send */
|
|
|
n = write(conn->sockfd, request, sizeof(request));
|
|
n = write(conn->sockfd, request, sizeof(request));
|
|
|
if (n != sizeof(request)) {
|
|
if (n != sizeof(request)) {
|
|
|
fprintf(stderr, "[MODBUS] Write failed: %s (wrote %zd/%zu)\n",
|
|
fprintf(stderr, "[MODBUS] Write failed: %s (wrote %zd/%zu)\n",
|
|
@@ -250,12 +241,11 @@ int modbus_read_registers(modbus_conn_t *conn, mppt_data_t *data)
|
|
|
return -1;
|
|
return -1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /* Read response with timeout (set via SO_RCVTIMEO) */
|
|
|
|
|
|
|
+ /* Read response */
|
|
|
n = read(conn->sockfd, response, sizeof(response));
|
|
n = read(conn->sockfd, response, sizeof(response));
|
|
|
if (n < 0) {
|
|
if (n < 0) {
|
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
|
- fprintf(stderr, "[MODBUS] Read timeout after %ds\n",
|
|
|
|
|
- MODBUS_TIMEOUT_SEC);
|
|
|
|
|
|
|
+ fprintf(stderr, "[MODBUS] Read timeout after %ds\n", MODBUS_TIMEOUT_SEC);
|
|
|
} else {
|
|
} else {
|
|
|
fprintf(stderr, "[MODBUS] Read error: %s\n", strerror(errno));
|
|
fprintf(stderr, "[MODBUS] Read error: %s\n", strerror(errno));
|
|
|
}
|
|
}
|
|
@@ -268,36 +258,40 @@ int modbus_read_registers(modbus_conn_t *conn, mppt_data_t *data)
|
|
|
return -1;
|
|
return -1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /* Validate minimum response length: addr(1) + func(1) + len(1) + data(20) + crc(2) = 25 */
|
|
|
|
|
- if (n < 25) {
|
|
|
|
|
- fprintf(stderr, "[MODBUS] Response too short (%zd bytes)\n", n);
|
|
|
|
|
|
|
+ /* Minimum response: addr(1) + func(1) + len(1) + data(N*2) + crc(2) */
|
|
|
|
|
+ size_t expected_min = 5 + (size_t)reg_count * 2;
|
|
|
|
|
+ if ((size_t)n < expected_min) {
|
|
|
|
|
+ fprintf(stderr, "[MODBUS] Response too short (%zd bytes, expected >= %zu)\n",
|
|
|
|
|
+ n, expected_min);
|
|
|
return -1;
|
|
return -1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /* Check slave address */
|
|
|
|
|
|
|
+ /* Validate slave address */
|
|
|
if (response[0] != conn->slave_addr) {
|
|
if (response[0] != conn->slave_addr) {
|
|
|
- fprintf(stderr, "[MODBUS] Wrong slave address: 0x%02X\n", response[0]);
|
|
|
|
|
|
|
+ fprintf(stderr, "[MODBUS] Wrong slave address: 0x%02X (expected 0x%02X)\n",
|
|
|
|
|
+ response[0], conn->slave_addr);
|
|
|
return -1;
|
|
return -1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /* Check for exception response */
|
|
|
|
|
|
|
+ /* Check exception */
|
|
|
if (response[1] & 0x80) {
|
|
if (response[1] & 0x80) {
|
|
|
- fprintf(stderr, "[MODBUS] Exception response: func=0x%02X code=0x%02X\n",
|
|
|
|
|
|
|
+ fprintf(stderr, "[MODBUS] Exception: func=0x%02X code=0x%02X\n",
|
|
|
response[1], response[2]);
|
|
response[1], response[2]);
|
|
|
return -1;
|
|
return -1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /* Check function code */
|
|
|
|
|
- if (response[1] != MODBUS_FUNC_READ) {
|
|
|
|
|
- fprintf(stderr, "[MODBUS] Unexpected function code: 0x%02X\n", response[1]);
|
|
|
|
|
|
|
+ /* Validate function code */
|
|
|
|
|
+ if (response[1] != func_code) {
|
|
|
|
|
+ fprintf(stderr, "[MODBUS] Unexpected FC: 0x%02X (expected 0x%02X)\n",
|
|
|
|
|
+ response[1], func_code);
|
|
|
return -1;
|
|
return -1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /* Check data length */
|
|
|
|
|
|
|
+ /* Validate data length */
|
|
|
uint8_t byte_count = response[2];
|
|
uint8_t byte_count = response[2];
|
|
|
- if (byte_count != MODBUS_REG_COUNT * 2) {
|
|
|
|
|
- fprintf(stderr, "[MODBUS] Unexpected data length: %u (expected %u)\n",
|
|
|
|
|
- byte_count, MODBUS_REG_COUNT * 2);
|
|
|
|
|
|
|
+ if (byte_count != reg_count * 2) {
|
|
|
|
|
+ fprintf(stderr, "[MODBUS] Data length mismatch: %u (expected %u)\n",
|
|
|
|
|
+ byte_count, reg_count * 2);
|
|
|
return -1;
|
|
return -1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -311,26 +305,49 @@ int modbus_read_registers(modbus_conn_t *conn, mppt_data_t *data)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* Parse register values (big-endian) */
|
|
/* Parse register values (big-endian) */
|
|
|
- for (int i = 0; i < MODBUS_REG_COUNT; i++) {
|
|
|
|
|
- data->reg[i] = ((uint16_t)response[3 + i * 2] << 8) |
|
|
|
|
|
- (uint16_t)response[3 + i * 2 + 1];
|
|
|
|
|
|
|
+ for (int i = 0; i < reg_count; i++) {
|
|
|
|
|
+ out_regs[i] = ((uint16_t)response[3 + i * 2] << 8) |
|
|
|
|
|
+ (uint16_t)response[3 + i * 2 + 1];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /* Convert to real-world values */
|
|
|
|
|
- data->battery_voltage = data->reg[0] / 10.0; /* V */
|
|
|
|
|
- data->pv_voltage = data->reg[1] / 10.0; /* V */
|
|
|
|
|
- data->pv_charge_current = data->reg[2] / 10.0; /* A */
|
|
|
|
|
- data->cumulative_energy = data->reg[3] / 10.0; /* kWh */
|
|
|
|
|
- data->machine_temp = (double)data->reg[4]; /* deg C */
|
|
|
|
|
- data->fault_code = data->reg[5];
|
|
|
|
|
- data->battery_soc = data->reg[6]; /* % */
|
|
|
|
|
- data->battery_string = data->reg[7];
|
|
|
|
|
- data->charge_state = data->reg[8];
|
|
|
|
|
- data->device_type = data->reg[9];
|
|
|
|
|
-
|
|
|
|
|
- /* Computed values */
|
|
|
|
|
|
|
+ return 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ── Read Input Registers (FC=0x04, 30001~30011) ──────────────── */
|
|
|
|
|
+int modbus_read_input_registers(modbus_conn_t *conn, mppt_data_t *data)
|
|
|
|
|
+{
|
|
|
|
|
+ int ret = modbus_read_generic(conn, MODBUS_FUNC_READ_INPUT,
|
|
|
|
|
+ MODBUS_INPUT_REG_START,
|
|
|
|
|
+ MODBUS_INPUT_REG_COUNT,
|
|
|
|
|
+ data->in_reg);
|
|
|
|
|
+ if (ret != 0) {
|
|
|
|
|
+ data->data_valid = false;
|
|
|
|
|
+ data->consecutive_failures++;
|
|
|
|
|
+ return -1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* Parse Input Registers */
|
|
|
|
|
+ data->battery_voltage = data->in_reg[0] / 10.0; /* 30001: V */
|
|
|
|
|
+ data->pv_voltage = data->in_reg[1] / 10.0; /* 30002: V */
|
|
|
|
|
+ data->pv_charge_current = data->in_reg[2] / 10.0; /* 30003: A */
|
|
|
|
|
+ data->cumulative_energy = data->in_reg[3] / 10.0; /* 30004: kWh */
|
|
|
|
|
+ data->machine_temp = (double)data->in_reg[4]; /* 30005: deg C */
|
|
|
|
|
+ data->fault_code = data->in_reg[5]; /* 30006 */
|
|
|
|
|
+ data->battery_soc = data->in_reg[6]; /* 30007: % */
|
|
|
|
|
+ data->battery_string = data->in_reg[7]; /* 30008 */
|
|
|
|
|
+ data->charge_state = data->in_reg[8]; /* 30009 */
|
|
|
|
|
+ data->device_type = data->in_reg[9]; /* 30010 */
|
|
|
|
|
+ data->software_version = data->in_reg[10]; /* 30011 */
|
|
|
|
|
+
|
|
|
|
|
+ /* Computed */
|
|
|
data->pv_input_power = data->pv_voltage * data->pv_charge_current;
|
|
data->pv_input_power = data->pv_voltage * data->pv_charge_current;
|
|
|
|
|
|
|
|
|
|
+ /* Description strings */
|
|
|
|
|
+ snprintf(data->fault_desc, sizeof(data->fault_desc), "%s",
|
|
|
|
|
+ modbus_fault_string(data->fault_code));
|
|
|
|
|
+ snprintf(data->charge_state_str, sizeof(data->charge_state_str), "%s",
|
|
|
|
|
+ modbus_charge_state_string(data->charge_state));
|
|
|
|
|
+
|
|
|
/* Meta */
|
|
/* Meta */
|
|
|
data->last_update = time(NULL);
|
|
data->last_update = time(NULL);
|
|
|
data->data_valid = true;
|
|
data->data_valid = true;
|
|
@@ -339,33 +356,102 @@ int modbus_read_registers(modbus_conn_t *conn, mppt_data_t *data)
|
|
|
return 0;
|
|
return 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/* ── Fault Code Descriptions ───────────────────────────────────── */
|
|
|
|
|
|
|
+/* ── Read Output Registers (FC=0x03, 40001~40009) ─────────────── */
|
|
|
|
|
+int modbus_read_output_registers(modbus_conn_t *conn, mppt_data_t *data)
|
|
|
|
|
+{
|
|
|
|
|
+ int ret = modbus_read_generic(conn, MODBUS_FUNC_READ_OUTPUT,
|
|
|
|
|
+ MODBUS_OUTPUT_REG_START,
|
|
|
|
|
+ MODBUS_OUTPUT_REG_COUNT,
|
|
|
|
|
+ data->out_reg);
|
|
|
|
|
+ if (ret != 0) {
|
|
|
|
|
+ data->params_valid = false;
|
|
|
|
|
+ return -1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /* Parse Output Registers */
|
|
|
|
|
+ data->battery_type = data->out_reg[0]; /* 40001 */
|
|
|
|
|
+ data->battery_level = data->out_reg[1]; /* 40002 */
|
|
|
|
|
+ data->charge_current_pct = data->out_reg[2]; /* 40003: % */
|
|
|
|
|
+ data->over_discharge_voltage = data->out_reg[3] / 10.0; /* 40004: V */
|
|
|
|
|
+ data->boost_voltage = data->out_reg[4] / 10.0; /* 40005: V */
|
|
|
|
|
+ data->full_charge_voltage = data->out_reg[5] / 10.0; /* 40006: V */
|
|
|
|
|
+ data->over_discharge_recovery = data->out_reg[6] / 10.0; /* 40007: V */
|
|
|
|
|
+ data->rs485_address = data->out_reg[7]; /* 40008 */
|
|
|
|
|
+ data->charge_mode = data->out_reg[8]; /* 40009 */
|
|
|
|
|
+
|
|
|
|
|
+ /* Description strings */
|
|
|
|
|
+ snprintf(data->battery_type_str, sizeof(data->battery_type_str), "%s",
|
|
|
|
|
+ modbus_battery_type_string(data->battery_type));
|
|
|
|
|
+ snprintf(data->battery_level_str, sizeof(data->battery_level_str), "%s",
|
|
|
|
|
+ modbus_battery_level_string(data->battery_level));
|
|
|
|
|
+ snprintf(data->charge_mode_str, sizeof(data->charge_mode_str), "%s",
|
|
|
|
|
+ modbus_charge_mode_string(data->charge_mode));
|
|
|
|
|
+
|
|
|
|
|
+ data->params_valid = true;
|
|
|
|
|
+ return 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ── Fault Code Descriptions (per DM Protocol V2.1) ────────────── */
|
|
|
const char *modbus_fault_string(uint16_t code)
|
|
const char *modbus_fault_string(uint16_t code)
|
|
|
{
|
|
{
|
|
|
switch (code) {
|
|
switch (code) {
|
|
|
- case 0: return "No fault";
|
|
|
|
|
- case 1: return "Battery over-voltage";
|
|
|
|
|
- case 2: return "Battery under-voltage";
|
|
|
|
|
- case 3: return "PV over-voltage";
|
|
|
|
|
- case 4: return "PV under-voltage";
|
|
|
|
|
- case 5: return "Over-temperature";
|
|
|
|
|
- case 6: return "Short circuit";
|
|
|
|
|
- case 7: return "Over-current";
|
|
|
|
|
- case 8: return "Communication error";
|
|
|
|
|
case 20: return "Normal";
|
|
case 20: return "Normal";
|
|
|
|
|
+ case 30: return "PV overvoltage";
|
|
|
|
|
+ case 31: return "PV charge overcurrent";
|
|
|
|
|
+ case 32: return "Battery voltage low";
|
|
|
|
|
+ case 33: return "Battery overvoltage";
|
|
|
|
|
+ case 38: return "Device temperature high";
|
|
|
|
|
+ case 50: return "Temperature sensor fault";
|
|
|
|
|
+ case 88: return "Not decrypted";
|
|
|
default: return "Unknown fault";
|
|
default: return "Unknown fault";
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+/* ── Battery Type Descriptions ─────────────────────────────────── */
|
|
|
|
|
+const char *modbus_battery_type_string(uint16_t type)
|
|
|
|
|
+{
|
|
|
|
|
+ switch (type) {
|
|
|
|
|
+ case 0: return "Gel";
|
|
|
|
|
+ case 1: return "Sealed";
|
|
|
|
|
+ case 2: return "LiFePO4";
|
|
|
|
|
+ case 3: return "Ternary Lithium";
|
|
|
|
|
+ case 4: return "Custom";
|
|
|
|
|
+ case 5: return "Flooded";
|
|
|
|
|
+ default: return "Unknown";
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ── Battery Level Descriptions ────────────────────────────────── */
|
|
|
|
|
+const char *modbus_battery_level_string(uint16_t level)
|
|
|
|
|
+{
|
|
|
|
|
+ switch (level) {
|
|
|
|
|
+ case 0: return "Auto";
|
|
|
|
|
+ case 1: return "12V";
|
|
|
|
|
+ case 2: return "24V";
|
|
|
|
|
+ case 3: return "36V";
|
|
|
|
|
+ case 4: return "48V";
|
|
|
|
|
+ case 5: return "60V";
|
|
|
|
|
+ case 6: return "72V";
|
|
|
|
|
+ default: return "Unknown";
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* ── Charge Mode Descriptions ──────────────────────────────────── */
|
|
|
|
|
+const char *modbus_charge_mode_string(uint16_t mode)
|
|
|
|
|
+{
|
|
|
|
|
+ switch (mode) {
|
|
|
|
|
+ case 0: return "MPPT";
|
|
|
|
|
+ case 1: return "DCDC";
|
|
|
|
|
+ default: return "Unknown";
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
/* ── Charge State Descriptions ─────────────────────────────────── */
|
|
/* ── Charge State Descriptions ─────────────────────────────────── */
|
|
|
const char *modbus_charge_state_string(uint16_t state)
|
|
const char *modbus_charge_state_string(uint16_t state)
|
|
|
{
|
|
{
|
|
|
switch (state) {
|
|
switch (state) {
|
|
|
- case 0: return "Idle / No charging";
|
|
|
|
|
- case 1: return "Charging (Bulk)";
|
|
|
|
|
- case 2: return "Absorption";
|
|
|
|
|
- case 3: return "Float / Trickle";
|
|
|
|
|
- case 4: return "Equalize";
|
|
|
|
|
|
|
+ case 0: return "Not charging";
|
|
|
|
|
+ case 1: return "Charging";
|
|
|
default: return "Unknown";
|
|
default: return "Unknown";
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -378,23 +464,36 @@ void modbus_print_data(const mppt_data_t *data)
|
|
|
strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S", tm_info);
|
|
strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S", tm_info);
|
|
|
|
|
|
|
|
printf("\n");
|
|
printf("\n");
|
|
|
- printf("+==========================================+\n");
|
|
|
|
|
- printf("| MPPT Real-time Data [%s] |\n", timebuf);
|
|
|
|
|
- printf("| Device: %-12s (ID:%d) |\n",
|
|
|
|
|
|
|
+ printf("+======================================================+\n");
|
|
|
|
|
+ printf("| MPPT Full Data [%s] |\n", timebuf);
|
|
|
|
|
+ printf("| Device: %-12s (ID:%d) |\n",
|
|
|
data->device_name, data->device_id);
|
|
data->device_name, data->device_id);
|
|
|
- printf("+==========================================+\n");
|
|
|
|
|
- printf("| Battery Voltage : %8.1f V |\n", data->battery_voltage);
|
|
|
|
|
- printf("| PV Voltage : %8.1f V |\n", data->pv_voltage);
|
|
|
|
|
- printf("| Charge Current : %8.1f A |\n", data->pv_charge_current);
|
|
|
|
|
- printf("| PV Input Power : %8.1f W |\n", data->pv_input_power);
|
|
|
|
|
- printf("| Cumulative Energy: %8.1f kWh |\n", data->cumulative_energy);
|
|
|
|
|
- printf("| Machine Temp : %8.1f C |\n", data->machine_temp);
|
|
|
|
|
- printf("| Battery SOC : %8u %% |\n", data->battery_soc);
|
|
|
|
|
- printf("| Battery String : %8u |\n", data->battery_string);
|
|
|
|
|
- printf("| Charge State : %-20s |\n", modbus_charge_state_string(data->charge_state));
|
|
|
|
|
- printf("| Fault Code : %-20s |\n", modbus_fault_string(data->fault_code));
|
|
|
|
|
- printf("| Device Type : %8u |\n", data->device_type);
|
|
|
|
|
- printf("+==========================================+\n");
|
|
|
|
|
|
|
+ printf("+======================================================+\n");
|
|
|
|
|
+ printf("| --- Input Registers (Real-time) --- |\n");
|
|
|
|
|
+ printf("| Battery Voltage : %8.1f V |\n", data->battery_voltage);
|
|
|
|
|
+ printf("| PV Voltage : %8.1f V |\n", data->pv_voltage);
|
|
|
|
|
+ printf("| Charge Current : %8.1f A |\n", data->pv_charge_current);
|
|
|
|
|
+ printf("| PV Input Power : %8.1f W |\n", data->pv_input_power);
|
|
|
|
|
+ printf("| Cumulative Energy : %8.1f kWh |\n", data->cumulative_energy);
|
|
|
|
|
+ printf("| Machine Temp : %8.1f C |\n", data->machine_temp);
|
|
|
|
|
+ printf("| Battery SOC : %8u %% |\n", data->battery_soc);
|
|
|
|
|
+ printf("| Battery String : %8u |\n", data->battery_string);
|
|
|
|
|
+ printf("| Charge State : %-20s |\n", data->charge_state_str);
|
|
|
|
|
+ printf("| Fault Code : %-20s |\n", data->fault_desc);
|
|
|
|
|
+ printf("| Device Type : %8u |\n", data->device_type);
|
|
|
|
|
+ printf("| Software Version : %8u |\n", data->software_version);
|
|
|
|
|
+ printf("+------------------------------------------------------+\n");
|
|
|
|
|
+ printf("| --- Output Registers (Parameters) --- |\n");
|
|
|
|
|
+ printf("| Battery Type : %-20s |\n", data->battery_type_str);
|
|
|
|
|
+ printf("| Battery Level : %-20s |\n", data->battery_level_str);
|
|
|
|
|
+ printf("| Charge Current Pct : %8u %% |\n", data->charge_current_pct);
|
|
|
|
|
+ printf("| Over-discharge V : %8.1f V |\n", data->over_discharge_voltage);
|
|
|
|
|
+ printf("| Boost Voltage : %8.1f V |\n", data->boost_voltage);
|
|
|
|
|
+ printf("| Full Charge V : %8.1f V |\n", data->full_charge_voltage);
|
|
|
|
|
+ printf("| Recovery V : %8.1f V |\n", data->over_discharge_recovery);
|
|
|
|
|
+ printf("| RS485 Address : %8u |\n", data->rs485_address);
|
|
|
|
|
+ printf("| Charge Mode : %-20s |\n", data->charge_mode_str);
|
|
|
|
|
+ printf("+======================================================+\n");
|
|
|
printf("\n");
|
|
printf("\n");
|
|
|
fflush(stdout);
|
|
fflush(stdout);
|
|
|
}
|
|
}
|