Migliora discovery e pubblicazione MQTT per inverter in cascata
Build Docker Image for Raspberry Pi / build-and-push (push) Successful in 9m22s

This commit is contained in:
Pi Developer
2026-02-10 21:59:47 +01:00
parent 61567e3326
commit 9e72d4f5a7
6 changed files with 696 additions and 128 deletions
+261 -32
View File
@@ -7,6 +7,8 @@
#include "tools.h" #include "tools.h"
#include "main.h" #include "main.h"
#include <vector>
#include <fcntl.h> #include <fcntl.h>
#include <termios.h> #include <termios.h>
@@ -20,6 +22,7 @@ cInverter::cInverter(std::string devicename, int qpiri, int qpiws, int qmod, int
buf_qpiws = qpiws; buf_qpiws = qpiws;
buf_qmod = qmod; buf_qmod = qmod;
buf_qpigs = qpigs; buf_qpigs = qpigs;
last_reply_size = 0;
lprintf("INVERTER: Initialized with buffer sizes - QPIRI:%d QPIWS:%d QMOD:%d QPIGS:%d", lprintf("INVERTER: Initialized with buffer sizes - QPIRI:%d QPIWS:%d QMOD:%d QPIGS:%d",
buf_qpiri, buf_qpiws, buf_qmod, buf_qpigs); buf_qpiri, buf_qpiws, buf_qmod, buf_qpigs);
} }
@@ -96,9 +99,12 @@ bool cInverter::query(const char *cmd, int replysize) {
settings.c_cflag &= ~PARENB; // no parity settings.c_cflag &= ~PARENB; // no parity
settings.c_cflag &= ~CSTOPB; // 1 stop bit settings.c_cflag &= ~CSTOPB; // 1 stop bit
settings.c_cflag &= ~CSIZE; settings.c_cflag &= ~CSIZE;
settings.c_cflag |= CS8 | CLOCAL; // 8 bits settings.c_cflag |= CS8 | CLOCAL | CREAD; // 8 bits, local, enable receiver
// settings.c_lflag = ICANON; // canonical mode settings.c_iflag = 0; // raw input
settings.c_oflag &= ~OPOST; // raw output settings.c_oflag = 0; // raw output
settings.c_lflag = 0; // no canonical/echo
settings.c_cc[VMIN] = 0;
settings.c_cc[VTIME] = 1; // 0.1s read timeout
tcsetattr(fd, TCSANOW, &settings); // apply the settings tcsetattr(fd, TCSANOW, &settings); // apply the settings
@@ -141,8 +147,12 @@ bool cInverter::query(const char *cmd, int replysize) {
memset(buf, 0, sizeof(buf)); memset(buf, 0, sizeof(buf));
time(&started); time(&started);
bool started_frame = false;
unsigned char ch = 0;
int max_read = (int)sizeof(buf) - 1;
do { do {
n = read(fd, (void*)buf+i, 1); // Read one byte at a time for reliable terminator detection n = read(fd, (void*)&ch, 1); // Read one byte at a time for reliable terminator detection
if (n < 0) { if (n < 0) {
if (time(NULL) - started > 2) { if (time(NULL) - started > 2) {
lprintf("INVERTER: %s read timeout", cmd); lprintf("INVERTER: %s read timeout", cmd);
@@ -154,18 +164,35 @@ bool cInverter::query(const char *cmd, int replysize) {
} }
if (n > 0) { if (n > 0) {
i += n; if (!started_frame) {
if (ch != '(') {
continue;
}
started_frame = true;
buf[0] = ch;
i = 1;
continue;
}
buf[i++] = ch;
// Check if we've received the terminator (CR or LF) // Check if we've received the terminator (CR or LF)
if (i > 0 && (buf[i-1] == 0x0d || buf[i-1] == 0x0a)) { if (ch == 0x0d || ch == 0x0a) {
lprintf("INVERTER: %s received terminator (0x%02X) at byte %d", cmd, buf[i-1], i); lprintf("INVERTER: %s received terminator (0x%02X) at byte %d", cmd, ch, i);
break;
}
if (i >= (int)sizeof(buf) - 1) {
lprintf("INVERTER: %s buffer full before terminator", cmd);
break; break;
} }
} }
} while (i<replysize && (time(NULL) - started < 3)); } while ((i < max_read || !started_frame) && (time(NULL) - started < 5));
close(fd); close(fd);
if (i > 0) { if (i > 0 && started_frame) {
last_reply_size = i;
lprintf("INVERTER: %s reply size (%d bytes, expected %d)", cmd, i, replysize); lprintf("INVERTER: %s reply size (%d bytes, expected %d)", cmd, i, replysize);
@@ -197,6 +224,12 @@ bool cInverter::query(const char *cmd, int replysize) {
buf[i-3] = '\0'; //nullterminating on first CRC byte buf[i-3] = '\0'; //nullterminating on first CRC byte
lprintf("INVERTER: %s: %d bytes read: %s", cmd, i, buf); lprintf("INVERTER: %s: %d bytes read: %s", cmd, i, buf);
// Treat NAK as a failed query
if (strncmp((const char*)buf + 1, "NAK", 3) == 0) {
lprintf("INVERTER: %s: NAK reply", cmd);
return false;
}
lprintf("INVERTER: %s query finished", cmd); lprintf("INVERTER: %s query finished", cmd);
// If expected size doesn't match actual size, log it // If expected size doesn't match actual size, log it
@@ -207,6 +240,7 @@ bool cInverter::query(const char *cmd, int replysize) {
return true; return true;
} else { } else {
last_reply_size = 0;
lprintf("INVERTER: %s reply too short (%d bytes)", cmd, i); lprintf("INVERTER: %s reply too short (%d bytes)", cmd, i);
return false; return false;
} }
@@ -227,6 +261,7 @@ void cInverter::poll() {
ups_qmod_changed = true; ups_qmod_changed = true;
fprintf(stderr, "[POLL] QMOD completed\n"); fprintf(stderr, "[POLL] QMOD completed\n");
} }
usleep(200000); // allow inverter to settle between commands
} }
// reading status (QPIGS) // reading status (QPIGS)
@@ -239,6 +274,7 @@ void cInverter::poll() {
ups_qpigs_changed = true; ups_qpigs_changed = true;
fprintf(stderr, "[POLL] QPIGS completed\n"); fprintf(stderr, "[POLL] QPIGS completed\n");
} }
usleep(200000); // allow inverter to settle between commands
} }
// Reading QPIRI status // Reading QPIRI status
@@ -251,6 +287,7 @@ void cInverter::poll() {
ups_qpiri_changed = true; ups_qpiri_changed = true;
fprintf(stderr, "[POLL] QPIRI completed\n"); fprintf(stderr, "[POLL] QPIRI completed\n");
} }
usleep(200000); // allow inverter to settle between commands
} }
// Get any device warnings... // Get any device warnings...
@@ -263,6 +300,7 @@ void cInverter::poll() {
ups_qpiws_changed = true; ups_qpiws_changed = true;
fprintf(stderr, "[POLL] QPIWS completed\n"); fprintf(stderr, "[POLL] QPIWS completed\n");
} }
usleep(200000); // allow inverter to settle between commands
} }
// If runOnce mode and all data collected, exit the thread // If runOnce mode and all data collected, exit the thread
@@ -332,6 +370,135 @@ bool cInverter::CheckCRC(unsigned char *data, int len) {
return data[len-3]==(crc>>8) && data[len-2]==(crc&0xff); return data[len-3]==(crc>>8) && data[len-2]==(crc&0xff);
} }
int cInverter::openSerial() {
int fd = open(this->device.data(), O_RDWR | O_NOCTTY);
if (fd == -1) {
lprintf("INVERTER: Unable to open device file (errno=%d %s)", errno, strerror(errno));
return -1;
}
speed_t baud = B2400;
struct termios settings;
tcgetattr(fd, &settings);
cfsetospeed(&settings, baud);
cfsetispeed(&settings, baud);
settings.c_cflag &= ~PARENB;
settings.c_cflag &= ~CSTOPB;
settings.c_cflag &= ~CSIZE;
settings.c_cflag |= CS8 | CLOCAL | CREAD;
settings.c_iflag = 0;
settings.c_oflag = 0;
settings.c_lflag = 0;
settings.c_cc[VMIN] = 0;
settings.c_cc[VTIME] = 1;
tcsetattr(fd, TCSANOW, &settings);
tcflush(fd, TCIOFLUSH);
usleep(300000);
return fd;
}
void cInverter::closeSerial(int fd) {
if (fd >= 0) {
close(fd);
}
}
bool cInverter::sendCommand(int fd, const std::string &cmd) {
unsigned char outbuf[128];
size_t cmd_len = cmd.size();
if (cmd_len + 3 > sizeof(outbuf)) {
return false;
}
tcflush(fd, TCIFLUSH);
uint16_t crc = cal_crc_half((uint8_t*)cmd.data(), cmd_len);
memcpy(outbuf, cmd.data(), cmd_len);
outbuf[cmd_len++] = crc >> 8;
outbuf[cmd_len++] = crc & 0xff;
outbuf[cmd_len++] = 0x0d;
int written = write(fd, outbuf, cmd_len);
if (written != (int)cmd_len) {
lprintf("INVERTER: %s write failed (wrote %d of %d bytes)", cmd.c_str(), written, (int)cmd_len);
return false;
}
tcdrain(fd);
usleep(500000);
return true;
}
bool cInverter::readReply(int fd, std::string &payload) {
unsigned char localbuf[1024];
memset(localbuf, 0, sizeof(localbuf));
time_t started;
time(&started);
bool started_frame = false;
unsigned char ch = 0;
int i = 0;
while (i < (int)sizeof(localbuf) - 1 && (time(NULL) - started < 7)) {
int n = read(fd, &ch, 1);
if (n > 0) {
if (!started_frame) {
if (ch != '(') {
continue;
}
started_frame = true;
localbuf[0] = ch;
i = 1;
continue;
}
if (ch == '(') {
localbuf[0] = ch;
i = 1;
continue;
}
localbuf[i++] = ch;
if (ch == 0x0d || ch == 0x0a) {
break;
}
} else {
usleep(10000);
}
}
if (!started_frame || i <= 0) {
return false;
}
if (localbuf[i-1] != 0x0d && localbuf[i-1] != 0x0a) {
if (debugFlag) {
lprintf("INVERTER: readReply missing terminator (size=%d)", i);
}
return false;
}
if (!CheckCRC(localbuf, i)) {
if (debugFlag) {
lprintf("INVERTER: readReply CRC failed (size=%d)", i);
}
return false;
}
localbuf[i-3] = '\0';
if (strncmp((const char*)localbuf + 1, "NAK", 3) == 0) {
return false;
}
payload = std::string((char*)localbuf + 1);
last_reply_size = i;
return true;
}
// Auto-discover the correct buffer size for a command by reading until CR // Auto-discover the correct buffer size for a command by reading until CR
int cInverter::query_auto(const char *cmd, int max_size) { int cInverter::query_auto(const char *cmd, int max_size) {
time_t started; time_t started;
@@ -341,7 +508,7 @@ int cInverter::query_auto(const char *cmd, int max_size) {
memset(temp_buf, 0, sizeof(temp_buf)); memset(temp_buf, 0, sizeof(temp_buf));
fd = open(this->device.data(), O_RDWR | O_NONBLOCK); fd = open(this->device.data(), O_RDWR | O_NOCTTY);
if (fd == -1) { if (fd == -1) {
lprintf("INVERTER: Unable to open device file for auto-discovery"); lprintf("INVERTER: Unable to open device file for auto-discovery");
return -1; return -1;
@@ -356,13 +523,17 @@ int cInverter::query_auto(const char *cmd, int max_size) {
settings.c_cflag &= ~PARENB; settings.c_cflag &= ~PARENB;
settings.c_cflag &= ~CSTOPB; settings.c_cflag &= ~CSTOPB;
settings.c_cflag &= ~CSIZE; settings.c_cflag &= ~CSIZE;
settings.c_cflag |= CS8 | CLOCAL; settings.c_cflag |= CS8 | CLOCAL | CREAD;
settings.c_oflag &= ~OPOST; settings.c_iflag = 0;
settings.c_oflag = 0;
settings.c_lflag = 0;
settings.c_cc[VMIN] = 0;
settings.c_cc[VTIME] = 5; // 0.5s read timeout
tcsetattr(fd, TCSANOW, &settings); tcsetattr(fd, TCSANOW, &settings);
// Flush all buffers // Flush all buffers
tcflush(fd, TCIOFLUSH); tcflush(fd, TCIOFLUSH);
usleep(200000); // 200ms delay to ensure clean state usleep(100000); // 100ms delay to ensure clean state
// Generate and send command with CRC // Generate and send command with CRC
uint16_t crc = cal_crc_half((uint8_t*)cmd, strlen(cmd)); uint16_t crc = cal_crc_half((uint8_t*)cmd, strlen(cmd));
@@ -374,18 +545,32 @@ int cInverter::query_auto(const char *cmd, int max_size) {
write(fd, &temp_buf, n); write(fd, &temp_buf, n);
tcdrain(fd); tcdrain(fd);
usleep(500000); // allow inverter to respond
// Clear buffer for reading // Clear buffer for reading
memset(temp_buf, 0, sizeof(temp_buf)); memset(temp_buf, 0, sizeof(temp_buf));
time(&started); time(&started);
// Read until we find CR (0x0d) or timeout // Read until we find CR/LF or timeout
bool started_frame = false;
unsigned char ch = 0;
while (i < max_size && (time(NULL) - started < 5)) { while (i < max_size && (time(NULL) - started < 5)) {
n = read(fd, temp_buf+i, 1); // Read one byte at a time n = read(fd, &ch, 1); // Read one byte at a time
if (n > 0) { if (n > 0) {
i += n; if (!started_frame) {
if (ch != '(') {
continue;
}
started_frame = true;
temp_buf[0] = ch;
i = 1;
continue;
}
temp_buf[i++] = ch;
// Found the terminator // Found the terminator
if (temp_buf[i-1] == 0x0d) { if (ch == 0x0d || ch == 0x0a) {
lprintf("INVERTER: Auto-discovery for %s: found CR at byte %d", cmd, i); lprintf("INVERTER: Auto-discovery for %s: found CR at byte %d", cmd, i);
break; break;
} }
@@ -397,7 +582,11 @@ int cInverter::query_auto(const char *cmd, int max_size) {
close(fd); close(fd);
// Validate the response // Validate the response
if (i > 0 && temp_buf[0] == '(' && temp_buf[i-1] == 0x0d) { if (i > 0 && temp_buf[0] == '(' && (temp_buf[i-1] == 0x0d || temp_buf[i-1] == 0x0a)) {
if (i >= 5 && temp_buf[1] == 'N' && temp_buf[2] == 'A' && temp_buf[3] == 'K') {
lprintf("INVERTER: Auto-discovery for %s got NAK", cmd);
return -1;
}
lprintf("INVERTER: Auto-discovery for %s successful: %d bytes", cmd, i); lprintf("INVERTER: Auto-discovery for %s successful: %d bytes", cmd, i);
return i; return i;
} else { } else {
@@ -425,32 +614,32 @@ int cInverter::query_auto(const char *cmd, int max_size) {
void cInverter::AutoDiscoverBufferSizes() { void cInverter::AutoDiscoverBufferSizes() {
printf("\n=== AUTO-DISCOVERY MODE ===\n"); printf("\n=== AUTO-DISCOVERY MODE ===\n");
printf("Testing inverter to find correct buffer sizes...\n\n"); printf("Testing inverter to find correct buffer sizes...\n\n");
int qmod_size = query_auto("QMOD", 20); int qmod_size = query("QMOD", buf_qmod) ? last_reply_size : query_auto("QMOD", 20);
if (qmod_size > 0) { if (qmod_size > 0) {
printf("✓ QMOD buffer size: %d\n", qmod_size); printf("✓ QMOD buffer size: %d\n", qmod_size);
} else { } else {
printf("✗ QMOD auto-discovery failed\n"); printf("✗ QMOD auto-discovery failed\n");
} }
sleep(1); sleep(1);
int qpigs_size = query_auto("QPIGS", 150); int qpigs_size = query("QPIGS", buf_qpigs) ? last_reply_size : query_auto("QPIGS", 150);
if (qpigs_size > 0) { if (qpigs_size > 0) {
printf("✓ QPIGS buffer size: %d\n", qpigs_size); printf("✓ QPIGS buffer size: %d\n", qpigs_size);
} else { } else {
printf("✗ QPIGS auto-discovery failed\n"); printf("✗ QPIGS auto-discovery failed\n");
} }
sleep(1); sleep(1);
int qpiri_size = query_auto("QPIRI", 150); int qpiri_size = query("QPIRI", buf_qpiri) ? last_reply_size : query_auto("QPIRI", 150);
if (qpiri_size > 0) { if (qpiri_size > 0) {
printf("✓ QPIRI buffer size: %d\n", qpiri_size); printf("✓ QPIRI buffer size: %d\n", qpiri_size);
} else { } else {
printf("✗ QPIRI auto-discovery failed\n"); printf("✗ QPIRI auto-discovery failed\n");
} }
sleep(1); sleep(1);
int qpiws_size = query_auto("QPIWS", 100); int qpiws_size = query("QPIWS", buf_qpiws) ? last_reply_size : query_auto("QPIWS", 100);
if (qpiws_size > 0) { if (qpiws_size > 0) {
printf("✓ QPIWS buffer size: %d\n", qpiws_size); printf("✓ QPIWS buffer size: %d\n", qpiws_size);
} else { } else {
@@ -468,8 +657,8 @@ void cInverter::AutoDiscoverBufferSizes() {
// Output in parsable format for scripts // Output in parsable format for scripts
printf("DISCOVERY_QMOD=%d\n", qmod_size > 0 ? qmod_size : 5); printf("DISCOVERY_QMOD=%d\n", qmod_size > 0 ? qmod_size : 5);
printf("DISCOVERY_QPIGS=%d\n", qpigs_size > 0 ? qpigs_size : 110); printf("DISCOVERY_QPIGS=%d\n", qpigs_size > 0 ? qpigs_size : 110);
printf("DISCOVERY_QPIRI=%d\n", qpiri_size > 0 ? qpiri_size : 98); printf("DISCOVERY_QPIRI=%d\n", qpiri_size > 0 ? qpiri_size : 103);
printf("DISCOVERY_QPIWS=%d\n", qpiws_size > 0 ? qpiws_size : 36); printf("DISCOVERY_QPIWS=%d\n", qpiws_size > 0 ? qpiws_size : 40);
printf("DISCOVERY_SUCCESS=%s\n", (qmod_size > 0 && qpigs_size > 0 && qpiri_size > 0 && qpiws_size > 0) ? "true" : "false"); printf("DISCOVERY_SUCCESS=%s\n", (qmod_size > 0 && qpigs_size > 0 && qpiri_size > 0 && qpiws_size > 0) ? "true" : "false");
} }
@@ -496,12 +685,19 @@ int cInverter::DiscoverParallelInverters() {
serial[j++] = buf[k]; serial[j++] = buf[k];
} }
// Check if serial is valid (not all zeros and not empty) // Check if serial is valid (not empty, not all zeros, not "0.0")
bool valid_serial = false; bool valid_serial = false;
for (int k = 0; k < j; k++) { if (j > 0) {
if (serial[k] != '0') { bool all_zero = true;
for (int k = 0; k < j; k++) {
if (serial[k] != '0') {
all_zero = false;
break;
}
}
if (!all_zero && strcmp(serial, "0.0") != 0) {
valid_serial = true; valid_serial = true;
break;
} }
} }
@@ -552,3 +748,36 @@ string cInverter::GetParallelStatus(int inverter_num) {
return ""; return "";
} }
int cInverter::QueryParallelQpgs(int count, std::vector<std::string> &replies) {
replies.clear();
if (count <= 0) {
return 0;
}
replies.resize(count);
int fd = openSerial();
if (fd < 0) {
return 0;
}
int ok = 0;
for (int i = 0; i < count; i++) {
std::string cmd = "QPGS" + std::to_string(i);
for (int attempt = 0; attempt < 3; attempt++) {
if (sendCommand(fd, cmd)) {
std::string payload;
if (readReply(fd, payload)) {
replies[i] = payload;
ok++;
break;
}
}
usleep(600000);
}
usleep(500000);
}
closeSerial(fd);
return ok;
}
+8
View File
@@ -4,6 +4,7 @@
#include <string> #include <string>
#include <thread> #include <thread>
#include <mutex> #include <mutex>
#include <vector>
using namespace std; using namespace std;
@@ -23,6 +24,12 @@ class cInverter {
int buf_qpiws; int buf_qpiws;
int buf_qmod; int buf_qmod;
int buf_qpigs; int buf_qpigs;
int last_reply_size;
int openSerial();
void closeSerial(int fd);
bool sendCommand(int fd, const std::string &cmd);
bool readReply(int fd, std::string &payload);
void SetMode(char newmode); void SetMode(char newmode);
bool CheckCRC(unsigned char *buff, int len); bool CheckCRC(unsigned char *buff, int len);
@@ -47,6 +54,7 @@ class cInverter {
void AutoDiscoverBufferSizes(); void AutoDiscoverBufferSizes();
int DiscoverParallelInverters(); // Returns number of parallel inverters int DiscoverParallelInverters(); // Returns number of parallel inverters
string GetParallelStatus(int inverter_num); // Get QPGS data for specific inverter string GetParallelStatus(int inverter_num); // Get QPGS data for specific inverter
int QueryParallelQpgs(int count, std::vector<std::string> &replies);
}; };
#endif // ___INVERTER_H #endif // ___INVERTER_H
+22
View File
@@ -175,6 +175,7 @@ int main(int argc, char* argv[]) {
// Get command flag settings from the arguments (if any) // Get command flag settings from the arguments (if any)
InputParser cmdArgs(argc, argv); InputParser cmdArgs(argc, argv);
const string &rawcmd = cmdArgs.getCmdOption("-r"); const string &rawcmd = cmdArgs.getCmdOption("-r");
const string &parallelDataCount = cmdArgs.getCmdOption("-P");
if(cmdArgs.cmdOptionExists("-h") || cmdArgs.cmdOptionExists("--help")) { if(cmdArgs.cmdOptionExists("-h") || cmdArgs.cmdOptionExists("--help")) {
return print_help(); return print_help();
@@ -209,6 +210,27 @@ int main(int argc, char* argv[]) {
exit(0); exit(0);
} }
// Parallel inverter QPGS read (single session)
if(cmdArgs.cmdOptionExists("-P") || cmdArgs.cmdOptionExists("--parallel-qpgs")) {
int count = 2;
if (!parallelDataCount.empty()) {
try {
count = stoi(parallelDataCount);
} catch (...) {
count = 2;
}
}
vector<string> replies;
int ok = ups->QueryParallelQpgs(count, replies);
printf("PARALLEL_COUNT=%d\n", count);
for (int i = 0; i < count; i++) {
printf("QPGS%d_REPLY=%s\n", i, replies[i].c_str());
}
printf("PARALLEL_OK=%d\n", ok);
exit(0);
}
// Logic to send 'raw commands' to the inverter.. // Logic to send 'raw commands' to the inverter..
if (!rawcmd.empty()) { if (!rawcmd.empty()) {
ups->ExecuteCmd(rawcmd); ups->ExecuteCmd(rawcmd);
+15 -3
View File
@@ -236,7 +236,11 @@ fi
echo "" echo ""
echo "=== Starting MQTT Bridge Services ===" echo "=== Starting MQTT Bridge Services ==="
echo "Using parallel inverter mode (2 inverters)" if [ -n "$INVERTER_DEVICES" ]; then
echo "Using multi-device mode (INVERTER_DEVICES=${INVERTER_DEVICES})"
else
echo "Using parallel inverter mode (2 inverters)"
fi
echo "" echo ""
# Wait a bit for the device to be ready # Wait a bit for the device to be ready
@@ -267,7 +271,12 @@ echo "✓ MQTT discovery topics initialized"
# Init the mqtt server every 5 minutes (300 seconds) # Init the mqtt server every 5 minutes (300 seconds)
# This will re-create the auto-created topics in the MQTT server if HA is restarted... # This will re-create the auto-created topics in the MQTT server if HA is restarted...
echo "Starting MQTT initialization service (every 5 minutes)..." echo "Starting MQTT initialization service (every 5 minutes)..."
watch -n 300 "$MQTT_INIT_SCRIPT" > /dev/null 2>&1 & (
while true; do
"$MQTT_INIT_SCRIPT" > /dev/null 2>&1
sleep 300
done
) &
# Run the MQTT Subscriber process in the background (so that way we can change the configuration on the inverter from home assistant) # Run the MQTT Subscriber process in the background (so that way we can change the configuration on the inverter from home assistant)
echo "Starting MQTT subscriber for commands..." echo "Starting MQTT subscriber for commands..."
@@ -280,4 +289,7 @@ echo "✓ All services started successfully!"
echo " Logs will appear below..." echo " Logs will appear below..."
echo "" echo ""
watch -n 30 "$MQTT_PUSH_SCRIPT" > /dev/null 2>&1 while true; do
"$MQTT_PUSH_SCRIPT" > /dev/null 2>&1
sleep 30
done
+319 -71
View File
@@ -8,12 +8,14 @@ if [ -f "/etc/inverter/mqtt.json" ] && [ -x "/opt/inverter-cli/bin/inverter_poll
MQTT_CONFIG="/etc/inverter/mqtt.json" MQTT_CONFIG="/etc/inverter/mqtt.json"
INVERTER_BIN="/opt/inverter-cli/bin/inverter_poller" INVERTER_BIN="/opt/inverter-cli/bin/inverter_poller"
MQTT_FALLBACK="/opt/inverter-mqtt/mqtt-push.sh" MQTT_FALLBACK="/opt/inverter-mqtt/mqtt-push.sh"
INVERTER_CONF="/etc/inverter/inverter.conf"
CONTAINER_MODE=true CONTAINER_MODE=true
else else
# Development mode # Development mode
MQTT_CONFIG="/home/pi/Progetti/config/mqtt.json" MQTT_CONFIG="/home/pi/Progetti/config/mqtt.json"
INVERTER_BIN="/home/pi/Progetti/sources/inverter-cli/bin/inverter_poller" INVERTER_BIN="/home/pi/Progetti/sources/inverter-cli/bin/inverter_poller"
MQTT_FALLBACK="/home/pi/Progetti/sources/inverter-mqtt/mqtt-push.sh" MQTT_FALLBACK="/home/pi/Progetti/sources/inverter-mqtt/mqtt-push.sh"
INVERTER_CONF="/home/pi/Progetti/config/inverter.conf"
CONTAINER_MODE=false CONTAINER_MODE=false
fi fi
@@ -180,8 +182,36 @@ if [ "$EUID" -ne 0 ] && [ -c "/dev/ttyUSB0" ]; then
SUDO_CMD="sudo" SUDO_CMD="sudo"
fi fi
# Multi-device mode: if INVERTER_DEVICES is set, poll each device separately
if [ -n "$INVERTER_DEVICES" ]; then
IFS=':' read -ra DEVICE_ARRAY <<< "$INVERTER_DEVICES"
if [ ${#DEVICE_ARRAY[@]} -gt 0 ]; then
echo "Multi-device mode enabled (${#DEVICE_ARRAY[@]} devices)"
for idx in "${!DEVICE_ARRAY[@]}"; do
inv_id=$((idx + 1))
device_path="${DEVICE_ARRAY[$idx]}"
temp_dir=$(mktemp -d)
cp "$INVERTER_CONF" "$temp_dir/inverter.conf" 2>/dev/null
sed -i "s|^device=.*|device=$device_path|g" "$temp_dir/inverter.conf"
INVERTER_DATA=$(cd "$temp_dir" && $SUDO_CMD "$INVERTER_BIN" -1 2>/dev/null | tr -d '\r')
rm -rf "$temp_dir"
if [ -z "$INVERTER_DATA" ] || ! echo "$INVERTER_DATA" | jq -e . >/dev/null 2>&1; then
echo "⚠ No valid JSON from $device_path (inv$inv_id)"
continue
fi
extractAndPublishAllData "$inv_id" "$INVERTER_DATA"
done
echo "Multi-device MQTT push completed"
exit 0
fi
fi
# Try parallel discovery with retry # Try parallel discovery with retry
MAX_RETRIES=1 # Reduced to 1 since we'll fallback to direct QPGS anyway MAX_RETRIES=3
RETRY_DELAY=2 RETRY_DELAY=2
PARALLEL_COUNT=0 PARALLEL_COUNT=0
@@ -205,44 +235,80 @@ done
# If still no count or discovery failed, try direct QPGS commands # If still no count or discovery failed, try direct QPGS commands
if [ -z "$PARALLEL_COUNT" ] || [ "$PARALLEL_COUNT" -eq 0 ]; then if [ -z "$PARALLEL_COUNT" ] || [ "$PARALLEL_COUNT" -eq 0 ]; then
echo "⚠ Discovery reports 0 inverters, trying direct QPGS0/QPGS1 commands..." echo "⚠ Discovery reports 0 inverters, trying direct QPGS0/QPGS1 commands..."
# Test QPGS0 to see if parallel mode is active for attempt in 1 2 3; do
TEST_QPGS0=`$SUDO_CMD "$INVERTER_BIN" -r "QPGS0" 2>&1 | grep "Reply:"` # Test QPGS0 to see if parallel mode is active
TEST_QPGS1=`$SUDO_CMD "$INVERTER_BIN" -r "QPGS1" 2>&1 | grep "Reply:"` TEST_QPGS0=`$SUDO_CMD "$INVERTER_BIN" -r "QPGS0" 2>&1 | grep "Reply:"`
TEST_QPGS1=`$SUDO_CMD "$INVERTER_BIN" -r "QPGS1" 2>&1 | grep "Reply:"`
if [ ! -z "$TEST_QPGS0" ] && [[ "$TEST_QPGS0" != *"NAK"* ]]; then
echo "✓ QPGS0 responds, assuming 2 inverters in cascade" if [ ! -z "$TEST_QPGS0" ] && [[ "$TEST_QPGS0" != *"NAK"* ]]; then
PARALLEL_COUNT=2 echo "✓ QPGS0 responds, assuming 2 inverters in cascade"
PARALLEL_COUNT=2
# Build fake discovery output
PARALLEL_DISCOVERY="INVERTER_1_SERIAL=unknown # Build fake discovery output
PARALLEL_DISCOVERY="INVERTER_1_SERIAL=unknown
INVERTER_1_QPGS=0 INVERTER_1_QPGS=0
INVERTER_2_SERIAL=unknown INVERTER_2_SERIAL=unknown
INVERTER_2_QPGS=1" INVERTER_2_QPGS=1"
elif [ ! -z "$TEST_QPGS1" ] && [[ "$TEST_QPGS1" != *"NAK"* ]]; then break
echo "✓ QPGS1 responds, assuming 2 inverters in cascade" elif [ ! -z "$TEST_QPGS1" ] && [[ "$TEST_QPGS1" != *"NAK"* ]]; then
PARALLEL_COUNT=2 echo "✓ QPGS1 responds, assuming 2 inverters in cascade"
PARALLEL_COUNT=2
# Build fake discovery output
PARALLEL_DISCOVERY="INVERTER_1_SERIAL=unknown # Build fake discovery output
PARALLEL_DISCOVERY="INVERTER_1_SERIAL=unknown
INVERTER_1_QPGS=0 INVERTER_1_QPGS=0
INVERTER_2_SERIAL=unknown INVERTER_2_SERIAL=unknown
INVERTER_2_QPGS=1" INVERTER_2_QPGS=1"
else break
else
echo "⚠ QPGS commands failed (attempt $attempt/3)"
sleep 1
fi
done
if [ "$PARALLEL_COUNT" -eq 0 ]; then
echo "✗ QPGS commands also failed" echo "✗ QPGS commands also failed"
PARALLEL_COUNT=0
fi fi
fi fi
echo "Processing $PARALLEL_COUNT parallel inverters" DISCOVERY_SERIALS=()
DISCOVERY_QPGS=()
MAX_QPGS_IDX=-1
# Publish discovery info for i in $(seq 1 $PARALLEL_COUNT); do
pushMQTTData "system" "parallel_count" "$PARALLEL_COUNT" serial=$(echo "$PARALLEL_DISCOVERY" | grep "INVERTER_${i}_SERIAL=" | cut -d= -f2)
qpgs_idx=$(echo "$PARALLEL_DISCOVERY" | grep "INVERTER_${i}_QPGS=" | cut -d= -f2)
if [ -z "$serial" ] || [ "$serial" = "0.0" ]; then
continue
fi
if echo "$serial" | grep -qE '^0+$'; then
continue
fi
if [ -z "$qpgs_idx" ]; then
continue
fi
DISCOVERY_SERIALS+=("$serial")
DISCOVERY_QPGS+=("$qpgs_idx")
if [ "$qpgs_idx" -gt "$MAX_QPGS_IDX" ]; then
MAX_QPGS_IDX=$qpgs_idx
fi
done
# Get QPIRI data once (shared configuration for all inverters in cascade) # Get QPIRI data once (shared configuration for all inverters in cascade)
echo "" echo ""
echo "Getting shared configuration (QPIRI)..." echo "Getting shared configuration (QPIRI)..."
QPIRI_RAW=`$SUDO_CMD "$INVERTER_BIN" -r "QPIRI" 2>&1 | grep "Reply:" | cut -d: -f2- | xargs` QPIRI_RAW=""
for attempt in 1 2 3 4 5; do
QPIRI_RAW=`$SUDO_CMD "$INVERTER_BIN" -r "QPIRI" 2>&1 | grep "Reply:" | cut -d: -f2- | xargs`
if [ ! -z "$QPIRI_RAW" ] && [ "$QPIRI_RAW" != "NAK" ]; then
break
fi
sleep 1
done
QPIRI_SUCCESS=false QPIRI_SUCCESS=false
if [ ! -z "$QPIRI_RAW" ] && [ "$QPIRI_RAW" != "NAK" ]; then if [ ! -z "$QPIRI_RAW" ] && [ "$QPIRI_RAW" != "NAK" ]; then
@@ -270,18 +336,145 @@ else
echo "⚠ QPIRI failed, configuration parameters unavailable" echo "⚠ QPIRI failed, configuration parameters unavailable"
fi fi
# Extract runtime data for each inverter using QPGS # Extract runtime data for each inverter using QPGS (single session)
PARALLEL_SUCCESS=false PARALLEL_SUCCESS=false
for i in $(seq 1 $PARALLEL_COUNT); do PARALLEL_QPGS_COUNT=$PARALLEL_COUNT
QPGS_IDX=$((i - 1)) # 0-based index if [ "$PARALLEL_QPGS_COUNT" -lt 2 ]; then
SERIAL=`echo "$PARALLEL_DISCOVERY" | grep "INVERTER_${i}_SERIAL=" | cut -d= -f2` PARALLEL_QPGS_COUNT=2
fi
if [ "$MAX_QPGS_IDX" -ge 0 ] && [ $((MAX_QPGS_IDX + 1)) -gt "$PARALLEL_QPGS_COUNT" ]; then
PARALLEL_QPGS_COUNT=$((MAX_QPGS_IDX + 1))
fi
PARALLEL_QPGS_OUTPUT=`$SUDO_CMD "$INVERTER_BIN" -P "$PARALLEL_QPGS_COUNT" 2>/dev/null`
HAS_PARALLEL_QPGS=false
if echo "$PARALLEL_QPGS_OUTPUT" | grep -q "^QPGS[0-9]_REPLY="; then
HAS_PARALLEL_QPGS=true
PARALLEL_QPGS_OUTPUT=`$SUDO_CMD "$INVERTER_BIN" -P "$PARALLEL_QPGS_COUNT" 2>/dev/null`
fi
VALID_SERIALS=()
VALID_QPGS=()
DIRECT_SERIALS=()
DIRECT_QPGS=()
if [ "$PARALLEL_COUNT" -lt 2 ]; then
for idx in 0 1; do
reply_value=`$SUDO_CMD "$INVERTER_BIN" -r "QPGS$idx" 2>&1 | grep "Reply:" | cut -d: -f2- | xargs`
if [ -z "$reply_value" ]; then
continue
fi
serial_from_reply=`echo "$reply_value" | awk '{print $2}'`
if [ -z "$serial_from_reply" ] || [ "$serial_from_reply" = "0.0" ]; then
continue
fi
if echo "$serial_from_reply" | grep -qE '^0+$'; then
continue
fi
if ! echo "$serial_from_reply" | grep -qE '^[0-9]{10,}$'; then
continue
fi
duplicate=false
for existing in "${DIRECT_SERIALS[@]}"; do
if [ "$existing" = "$serial_from_reply" ]; then
duplicate=true
break
fi
done
if [ "$duplicate" = true ]; then
continue
fi
DIRECT_SERIALS+=("$serial_from_reply")
DIRECT_QPGS+=("$idx")
done
fi
if [ ${#DIRECT_SERIALS[@]} -gt 0 ]; then
VALID_SERIALS=("${DIRECT_SERIALS[@]}")
VALID_QPGS=("${DIRECT_QPGS[@]}")
fi
if [ ${#VALID_SERIALS[@]} -eq 0 ] && [ "$HAS_PARALLEL_QPGS" = true ]; then
for attempt in 1 2 3; do
VALID_SERIALS=()
VALID_QPGS=()
for idx in $(seq 0 $((PARALLEL_QPGS_COUNT - 1))); do
reply_line=`echo "$PARALLEL_QPGS_OUTPUT" | grep "^QPGS${idx}_REPLY="`
reply_value=`echo "$reply_line" | cut -d= -f2- | xargs`
if [ -z "$reply_value" ]; then
continue
fi
serial_from_reply=`echo "$reply_value" | awk '{print $2}'`
if [ -z "$serial_from_reply" ] || [ "$serial_from_reply" = "0.0" ]; then
continue
fi
if echo "$serial_from_reply" | grep -qE '^0+$'; then
continue
fi
if ! echo "$serial_from_reply" | grep -qE '^[0-9]{10,}$'; then
continue
fi
duplicate=false
for existing in "${VALID_SERIALS[@]}"; do
if [ "$existing" = "$serial_from_reply" ]; then
duplicate=true
break
fi
done
if [ "$duplicate" = true ]; then
continue
fi
VALID_SERIALS+=("$serial_from_reply")
VALID_QPGS+=("$idx")
done
if [ ${#VALID_SERIALS[@]} -ge 2 ]; then
break
fi
PARALLEL_QPGS_OUTPUT=`$SUDO_CMD "$INVERTER_BIN" -P "$PARALLEL_QPGS_COUNT" 2>/dev/null`
sleep 1
done
fi
if [ ${#VALID_SERIALS[@]} -eq 0 ]; then
VALID_SERIALS=("${DISCOVERY_SERIALS[@]}")
VALID_QPGS=("${DISCOVERY_QPGS[@]}")
fi
VALID_COUNT=${#VALID_SERIALS[@]}
SUCCESS_INV_IDS=()
if [ "$VALID_COUNT" -eq 0 ]; then
echo "⚠ No valid inverter serials found (excluding 0.0/all-zero)"
else
echo "Processing $VALID_COUNT valid parallel inverters"
fi
# Publish discovery info (valid inverters only)
pushMQTTData "system" "parallel_count" "$VALID_COUNT"
for idx in "${!VALID_SERIALS[@]}"; do
inv_id=$((idx + 1))
QPGS_IDX="${VALID_QPGS[$idx]}"
SERIAL="${VALID_SERIALS[$idx]}"
echo "" echo ""
echo "Processing Inverter #$i (Serial: $SERIAL, QPGS$QPGS_IDX)" echo "Processing Inverter #$inv_id (Serial: $SERIAL, QPGS$QPGS_IDX)"
# Get QPGS data: 1 Serial Mode Status GridV GridF OutV OutF VA W PCT BattV ChgA Cap PVV PVA ... # Get QPGS data from single-session output, fallback to direct query
QPGS_RAW=`$SUDO_CMD "$INVERTER_BIN" -r "QPGS$QPGS_IDX" 2>&1 | grep "Reply:" | cut -d: -f2- | xargs` if [ "$HAS_PARALLEL_QPGS" = true ]; then
QPGS_RAW=`echo "$PARALLEL_QPGS_OUTPUT" | grep "^QPGS${QPGS_IDX}_REPLY=" | cut -d= -f2- | xargs`
fi
if [ -z "$QPGS_RAW" ]; then
QPGS_RAW=`$SUDO_CMD "$INVERTER_BIN" -r "QPGS$QPGS_IDX" 2>&1 | grep "Reply:" | cut -d: -f2- | xargs`
fi
if [ ! -z "$QPGS_RAW" ] && [ "$QPGS_RAW" != "NAK" ]; then if [ ! -z "$QPGS_RAW" ] && [ "$QPGS_RAW" != "NAK" ]; then
echo " ✓ QPGS$QPGS_IDX successful" echo " ✓ QPGS$QPGS_IDX successful"
@@ -295,46 +488,57 @@ for i in $(seq 1 $PARALLEL_COUNT); do
# 8=VA, 9=W, 10=PCT, 11=BattV, 12=ChgA, 13=Cap, 14=PVV, 15=PVA # 8=VA, 9=W, 10=PCT, 11=BattV, 12=ChgA, 13=Cap, 14=PVV, 15=PVA
# 16=?, 17=?, 18=?, 19-26=additional fields # 16=?, 17=?, 18=?, 19-26=additional fields
# Prefer serial from QPGS payload if valid
serial_from_data="${DATA[1]}"
if echo "$serial_from_data" | grep -qE '^[0-9]{10,}$'; then
SERIAL="$serial_from_data"
fi
# Publish serial # Publish serial
pushMQTTData "$i" "serial" "$SERIAL" pushMQTTData "$inv_id" "serial" "$SERIAL"
# Runtime data from QPGS (19 parameters) # Runtime data from QPGS (19 parameters)
[ "${DATA[2]}" ] && pushMQTTData "$i" "Inverter_mode" "${DATA[2]}" [ "${DATA[2]}" ] && pushMQTTData "$inv_id" "Inverter_mode" "${DATA[2]}"
[ "${DATA[4]}" ] && pushMQTTData "$i" "AC_grid_voltage" "${DATA[4]}" [ "${DATA[4]}" ] && pushMQTTData "$inv_id" "AC_grid_voltage" "${DATA[4]}"
[ "${DATA[5]}" ] && pushMQTTData "$i" "AC_grid_frequency" "${DATA[5]}" [ "${DATA[5]}" ] && pushMQTTData "$inv_id" "AC_grid_frequency" "${DATA[5]}"
[ "${DATA[6]}" ] && pushMQTTData "$i" "AC_out_voltage" "${DATA[6]}" [ "${DATA[6]}" ] && pushMQTTData "$inv_id" "AC_out_voltage" "${DATA[6]}"
[ "${DATA[7]}" ] && pushMQTTData "$i" "AC_out_frequency" "${DATA[7]}" [ "${DATA[7]}" ] && pushMQTTData "$inv_id" "AC_out_frequency" "${DATA[7]}"
[ "${DATA[8]}" ] && pushMQTTData "$i" "Load_va" "${DATA[8]}" [ "${DATA[8]}" ] && pushMQTTData "$inv_id" "Load_va" "${DATA[8]}"
[ "${DATA[9]}" ] && pushMQTTData "$i" "Load_watt" "${DATA[9]}" [ "${DATA[9]}" ] && pushMQTTData "$inv_id" "Load_watt" "${DATA[9]}"
[ "${DATA[10]}" ] && pushMQTTData "$i" "Load_pct" "${DATA[10]}" [ "${DATA[10]}" ] && pushMQTTData "$inv_id" "Load_pct" "${DATA[10]}"
[ "${DATA[11]}" ] && pushMQTTData "$i" "Battery_voltage" "${DATA[11]}" [ "${DATA[11]}" ] && pushMQTTData "$inv_id" "Battery_voltage" "${DATA[11]}"
[ "${DATA[12]}" ] && pushMQTTData "$i" "Battery_charge_current" "${DATA[12]}" [ "${DATA[12]}" ] && pushMQTTData "$inv_id" "Battery_charge_current" "${DATA[12]}"
[ "${DATA[13]}" ] && pushMQTTData "$i" "Battery_capacity" "${DATA[13]}" [ "${DATA[13]}" ] && pushMQTTData "$inv_id" "Battery_capacity" "${DATA[13]}"
[ "${DATA[14]}" ] && pushMQTTData "$i" "PV_in_voltage" "${DATA[14]}" [ "${DATA[14]}" ] && pushMQTTData "$inv_id" "PV_in_voltage" "${DATA[14]}"
[ "${DATA[15]}" ] && pushMQTTData "$i" "PV_in_current" "${DATA[15]}" [ "${DATA[15]}" ] && pushMQTTData "$inv_id" "PV_in_current" "${DATA[15]}"
# Calculate PV watts (V * A) # Calculate PV watts (V * A)
if [ ! -z "${DATA[14]}" ] && [ ! -z "${DATA[15]}" ]; then if [ ! -z "${DATA[14]}" ] && [ ! -z "${DATA[15]}" ]; then
PV_WATTS=`echo "${DATA[14]} ${DATA[15]}" | awk '{printf "%.1f", $1 * $2}'` PV_WATTS=`echo "${DATA[14]} ${DATA[15]}" | awk '{printf "%.1f", $1 * $2}'`
pushMQTTData "$i" "PV_in_watts" "$PV_WATTS" pushMQTTData "$inv_id" "PV_in_watts" "$PV_WATTS"
fi fi
# Status flags (parse from field 18 bitmap if available) # Status flags (parse from field 18 bitmap if available)
[ "${DATA[18]}" ] && { [ "${DATA[18]}" ] && {
BITMAP="${DATA[18]}" BITMAP="${DATA[18]}"
# Binary flags: bit0=Load_on, bit1=SCC_on, bit2=AC_charge_on if [[ "$BITMAP" =~ ^[0-9]+$ ]]; then
LOAD_ON=$((($BITMAP & 1) ? 1 : 0)) BITMAP=$((10#$BITMAP))
SCC_ON=$((($BITMAP & 2) ? 1 : 0)) # Binary flags: bit0=Load_on, bit1=SCC_on, bit2=AC_charge_on
AC_CHG_ON=$((($BITMAP & 4) ? 1 : 0)) LOAD_ON=$((($BITMAP & 1) ? 1 : 0))
SCC_ON=$((($BITMAP & 2) ? 1 : 0))
pushMQTTData "$i" "Load_status_on" "$LOAD_ON" AC_CHG_ON=$((($BITMAP & 4) ? 1 : 0))
pushMQTTData "$i" "SCC_charge_on" "$SCC_ON"
pushMQTTData "$i" "AC_charge_on" "$AC_CHG_ON" pushMQTTData "$inv_id" "Load_status_on" "$LOAD_ON"
pushMQTTData "$inv_id" "SCC_charge_on" "$SCC_ON"
pushMQTTData "$inv_id" "AC_charge_on" "$AC_CHG_ON"
fi
} }
echo " ✓ Published 17 runtime parameters for inverter #$i" echo " ✓ Published 17 runtime parameters for inverter #$inv_id"
echo " (QPGS data + calculated PV_watts + status flags)" echo " (QPGS data + calculated PV_watts + status flags)"
echo " ⚠ Missing 5 params: PV_watthour, Load_watthour, Bus_voltage, Heatsink_temp, Warnings" echo " ⚠ Missing 5 params: PV_watthour, Load_watthour, Bus_voltage, Heatsink_temp, Warnings"
SUCCESS_INV_IDS+=("$inv_id")
else else
echo " ✗ QPGS$QPGS_IDX failed (NAK or empty)" echo " ✗ QPGS$QPGS_IDX failed (NAK or empty)"
@@ -342,27 +546,71 @@ for i in $(seq 1 $PARALLEL_COUNT); do
done done
# Publish shared configuration parameters for ALL inverters (replicate QPIRI data) # Publish shared configuration parameters for ALL inverters (replicate QPIRI data)
if [ "$QPIRI_SUCCESS" = true ]; then if [ "$QPIRI_SUCCESS" = true ] && [ ${#SUCCESS_INV_IDS[@]} -gt 0 ]; then
echo "" echo ""
echo "Publishing shared configuration to all inverters..." echo "Publishing shared configuration to all inverters..."
for i in $(seq 1 $PARALLEL_COUNT); do for inv_id in "${SUCCESS_INV_IDS[@]}"; do
echo " Replicating QPIRI config to inverter #$i..." echo " Replicating QPIRI config to inverter #$inv_id..."
[ ! -z "$BATT_RECHARGE" ] && pushMQTTData "$i" "Battery_recharge_voltage" "$BATT_RECHARGE" [ ! -z "$BATT_RECHARGE" ] && pushMQTTData "$inv_id" "Battery_recharge_voltage" "$BATT_RECHARGE"
[ ! -z "$BATT_UNDER" ] && pushMQTTData "$i" "Battery_under_voltage" "$BATT_UNDER" [ ! -z "$BATT_UNDER" ] && pushMQTTData "$inv_id" "Battery_under_voltage" "$BATT_UNDER"
[ ! -z "$BATT_BULK" ] && pushMQTTData "$i" "Battery_bulk_voltage" "$BATT_BULK" [ ! -z "$BATT_BULK" ] && pushMQTTData "$inv_id" "Battery_bulk_voltage" "$BATT_BULK"
[ ! -z "$BATT_FLOAT" ] && pushMQTTData "$i" "Battery_float_voltage" "$BATT_FLOAT" [ ! -z "$BATT_FLOAT" ] && pushMQTTData "$inv_id" "Battery_float_voltage" "$BATT_FLOAT"
[ ! -z "$MAX_CHARGE_CURRENT" ] && pushMQTTData "$i" "Max_charge_current" "$MAX_CHARGE_CURRENT" [ ! -z "$MAX_CHARGE_CURRENT" ] && pushMQTTData "$inv_id" "Max_charge_current" "$MAX_CHARGE_CURRENT"
[ ! -z "$MAX_GRID_CHARGE" ] && pushMQTTData "$i" "Max_grid_charge_current" "$MAX_GRID_CHARGE" [ ! -z "$MAX_GRID_CHARGE" ] && pushMQTTData "$inv_id" "Max_grid_charge_current" "$MAX_GRID_CHARGE"
[ ! -z "$OUT_SOURCE_PRIORITY" ] && pushMQTTData "$i" "Out_source_priority" "$OUT_SOURCE_PRIORITY" [ ! -z "$OUT_SOURCE_PRIORITY" ] && pushMQTTData "$inv_id" "Out_source_priority" "$OUT_SOURCE_PRIORITY"
[ ! -z "$CHARGER_SOURCE_PRIORITY" ] && pushMQTTData "$i" "Charger_source_priority" "$CHARGER_SOURCE_PRIORITY" [ ! -z "$CHARGER_SOURCE_PRIORITY" ] && pushMQTTData "$inv_id" "Charger_source_priority" "$CHARGER_SOURCE_PRIORITY"
[ ! -z "$BATT_REDISCHARGE" ] && pushMQTTData "$i" "Battery_redischarge_voltage" "$BATT_REDISCHARGE" [ ! -z "$BATT_REDISCHARGE" ] && pushMQTTData "$inv_id" "Battery_redischarge_voltage" "$BATT_REDISCHARGE"
echo " ✓ Published 9 shared config parameters to inv$i" echo " ✓ Published 9 shared config parameters to inv$inv_id"
done done
fi fi
# Retry shared config once after QPGS if needed
if [ "$QPIRI_SUCCESS" = false ] && [ ${#SUCCESS_INV_IDS[@]} -gt 0 ]; then
echo ""
echo "Retrying shared configuration (QPIRI) after QPGS..."
for attempt in 1 2 3; do
QPIRI_RAW=`$SUDO_CMD "$INVERTER_BIN" -r "QPIRI" 2>&1 | grep "Reply:" | cut -d: -f2- | xargs`
if [ ! -z "$QPIRI_RAW" ] && [ "$QPIRI_RAW" != "NAK" ]; then
QPIRI_SUCCESS=true
break
fi
sleep 1
done
if [ "$QPIRI_SUCCESS" = true ]; then
IFS=' ' read -ra QPIRI <<< "$QPIRI_RAW"
BATT_RECHARGE="${QPIRI[8]}"
BATT_UNDER="${QPIRI[9]}"
BATT_BULK="${QPIRI[10]}"
BATT_FLOAT="${QPIRI[11]}"
MAX_CHARGE_CURRENT="${QPIRI[13]}"
MAX_GRID_CHARGE="${QPIRI[14]}"
OUT_SOURCE_PRIORITY="${QPIRI[15]}"
CHARGER_SOURCE_PRIORITY="${QPIRI[16]}"
BATT_REDISCHARGE="${QPIRI[22]}"
echo "Publishing shared configuration to all inverters..."
for inv_id in "${SUCCESS_INV_IDS[@]}"; do
echo " Replicating QPIRI config to inverter #$inv_id..."
[ ! -z "$BATT_RECHARGE" ] && pushMQTTData "$inv_id" "Battery_recharge_voltage" "$BATT_RECHARGE"
[ ! -z "$BATT_UNDER" ] && pushMQTTData "$inv_id" "Battery_under_voltage" "$BATT_UNDER"
[ ! -z "$BATT_BULK" ] && pushMQTTData "$inv_id" "Battery_bulk_voltage" "$BATT_BULK"
[ ! -z "$BATT_FLOAT" ] && pushMQTTData "$inv_id" "Battery_float_voltage" "$BATT_FLOAT"
[ ! -z "$MAX_CHARGE_CURRENT" ] && pushMQTTData "$inv_id" "Max_charge_current" "$MAX_CHARGE_CURRENT"
[ ! -z "$MAX_GRID_CHARGE" ] && pushMQTTData "$inv_id" "Max_grid_charge_current" "$MAX_GRID_CHARGE"
[ ! -z "$OUT_SOURCE_PRIORITY" ] && pushMQTTData "$inv_id" "Out_source_priority" "$OUT_SOURCE_PRIORITY"
[ ! -z "$CHARGER_SOURCE_PRIORITY" ] && pushMQTTData "$inv_id" "Charger_source_priority" "$CHARGER_SOURCE_PRIORITY"
[ ! -z "$BATT_REDISCHARGE" ] && pushMQTTData "$inv_id" "Battery_redischarge_voltage" "$BATT_REDISCHARGE"
echo " ✓ Published 9 shared config parameters to inv$inv_id"
done
else
echo "⚠ QPIRI retry failed, shared configuration not published"
fi
fi
# Fallback: use standard mode with full JSON output # Fallback: use standard mode with full JSON output
if [ "$PARALLEL_SUCCESS" = false ]; then if [ "$PARALLEL_SUCCESS" = false ]; then
echo "" echo ""
+71 -22
View File
@@ -111,12 +111,55 @@ if [ -z "$PARALLEL_COUNT" ]; then
PARALLEL_COUNT=0 PARALLEL_COUNT=0
fi fi
VALID_SERIALS=()
VALID_QPGS=()
MAX_QPGS_IDX=-1
if [ $PARALLEL_COUNT -gt 0 ]; then if [ $PARALLEL_COUNT -gt 0 ]; then
echo -e "${GREEN}✓ Found $PARALLEL_COUNT parallel inverter(s)${NC}" QPGS_COUNT=$PARALLEL_COUNT
for i in $(seq 1 $PARALLEL_COUNT); do if [ "$QPGS_COUNT" -lt 2 ]; then
SERIAL=$(echo "$PARALLEL_OUTPUT" | grep "INVERTER_${i}_SERIAL=" | cut -d= -f2) QPGS_COUNT=2
QPGS_IDX=$(echo "$PARALLEL_OUTPUT" | grep "INVERTER_${i}_QPGS=" | cut -d= -f2) fi
echo " • Inverter #$i: Serial $SERIAL (QPGS$QPGS_IDX)"
QPGS_OUTPUT=$($SUDO_CMD "$INVERTER_BIN" -P "$QPGS_COUNT" 2>/dev/null)
for idx in $(seq 0 $((QPGS_COUNT - 1))); do
QPGS_DATA=$(echo "$QPGS_OUTPUT" | grep "^QPGS${idx}_REPLY=" | cut -d= -f2- | xargs)
if [ -z "$QPGS_DATA" ]; then
continue
fi
SERIAL=$(echo "$QPGS_DATA" | awk '{print $2}')
if [ -z "$SERIAL" ] || [ "$SERIAL" = "0.0" ]; then
continue
fi
if echo "$SERIAL" | grep -qE '^0+$'; then
continue
fi
if ! echo "$SERIAL" | grep -qE '^[0-9]{10,}$'; then
continue
fi
duplicate=false
for existing in "${VALID_SERIALS[@]}"; do
if [ "$existing" = "$SERIAL" ]; then
duplicate=true
break
fi
done
if [ "$duplicate" = true ]; then
continue
fi
VALID_SERIALS+=("$SERIAL")
VALID_QPGS+=("$idx")
if [ "$idx" -gt "$MAX_QPGS_IDX" ]; then
MAX_QPGS_IDX=$idx
fi
done
VALID_COUNT=${#VALID_SERIALS[@]}
echo -e "${GREEN}✓ Found $VALID_COUNT valid parallel inverter(s)${NC}"
for idx in "${!VALID_SERIALS[@]}"; do
inv_id=$((idx + 1))
echo " • Inverter #$inv_id: Serial ${VALID_SERIALS[$idx]} (QPGS${VALID_QPGS[$idx]})"
done done
else else
echo -e "${YELLOW}⚠ No parallel inverters found, using single mode${NC}" echo -e "${YELLOW}⚠ No parallel inverters found, using single mode${NC}"
@@ -151,25 +194,30 @@ while true; do
echo "" echo ""
# Get parallel data # Get parallel data
if [ $PARALLEL_COUNT -gt 0 ]; then if [ ${#VALID_SERIALS[@]} -gt 0 ]; then
echo -e "${YELLOW}[2.2] Reading parallel inverters data${NC}" echo -e "${YELLOW}[2.2] Reading parallel inverters data${NC}"
for i in $(seq 1 $PARALLEL_COUNT); do QPGS_COUNT=$((MAX_QPGS_IDX + 1))
SERIAL=$(echo "$PARALLEL_OUTPUT" | grep "INVERTER_${i}_SERIAL=" | cut -d= -f2) if [ "$QPGS_COUNT" -lt 1 ]; then
QPGS_IDX=$(echo "$PARALLEL_OUTPUT" | grep "INVERTER_${i}_QPGS=" | cut -d= -f2) QPGS_COUNT=2
fi
QPGS_OUTPUT=$($SUDO_CMD "$INVERTER_BIN" -P "$QPGS_COUNT" 2>/dev/null)
for idx in "${!VALID_SERIALS[@]}"; do
inv_id=$((idx + 1))
SERIAL="${VALID_SERIALS[$idx]}"
QPGS_IDX="${VALID_QPGS[$idx]}"
QPGS_DATA=$(echo "$QPGS_OUTPUT" | grep "^QPGS${QPGS_IDX}_REPLY=" | cut -d= -f2- | xargs)
QPGS_DATA=$($SUDO_CMD "$INVERTER_BIN" -r "QPGS$QPGS_IDX" 2>&1 | grep "Reply:" | cut -d: -f2- | xargs) if [ ! -z "$QPGS_DATA" ]; then
if [ ! -z "$QPGS_DATA" ] && [ "$QPGS_DATA" != "NAK" ]; then
# Parse key values
IFS=' ' read -ra DATA <<< "$QPGS_DATA" IFS=' ' read -ra DATA <<< "$QPGS_DATA"
MODE="${DATA[2]}" MODE="${DATA[2]}"
GRID_V="${DATA[4]}" GRID_V="${DATA[4]}"
BATT_V="${DATA[11]}" BATT_V="${DATA[11]}"
LOAD_W="${DATA[9]}" LOAD_W="${DATA[9]}"
echo -e " ${GREEN}${NC} Inverter #$i ($SERIAL): Mode=$MODE, Grid=${GRID_V}V, Battery=${BATT_V}V, Load=${LOAD_W}W" echo -e " ${GREEN}${NC} Inverter #$inv_id ($SERIAL): Mode=$MODE, Grid=${GRID_V}V, Battery=${BATT_V}V, Load=${LOAD_W}W"
else else
echo -e " ${RED}${NC} Inverter #$i ($SERIAL): No data" echo -e " ${RED}${NC} Inverter #$inv_id ($SERIAL): No data"
fi fi
done done
echo "" echo ""
@@ -184,13 +232,14 @@ while true; do
echo "$MQTT_OUTPUT" | sed 's/^/ /' echo "$MQTT_OUTPUT" | sed 's/^/ /'
# Show sample topics published # Show sample topics published
if [ $PARALLEL_COUNT -gt 0 ]; then if [ ${#VALID_SERIALS[@]} -gt 0 ]; then
echo -e " ${GREEN}Sample topics published:${NC}" echo -e " ${GREEN}Sample topics published:${NC}"
for i in $(seq 1 $PARALLEL_COUNT); do for idx in "${!VALID_SERIALS[@]}"; do
echo "$MQTT_TOPIC/sensor/${MQTT_DEVICE}_inv${i}_serial" inv_id=$((idx + 1))
echo "$MQTT_TOPIC/sensor/${MQTT_DEVICE}_inv${i}_Battery_voltage" echo "$MQTT_TOPIC/sensor/${MQTT_DEVICE}_inv${inv_id}_serial"
echo "$MQTT_TOPIC/sensor/${MQTT_DEVICE}_inv${i}_Load_watt" echo "$MQTT_TOPIC/sensor/${MQTT_DEVICE}_inv${inv_id}_Battery_voltage"
[ $i -eq 1 ] && echo " • ... (and more)" echo "$MQTT_TOPIC/sensor/${MQTT_DEVICE}_inv${inv_id}_Load_watt"
[ $inv_id -eq 1 ] && echo " • ... (and more)"
done done
fi fi