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
+257 -28
View File
@@ -7,6 +7,8 @@
#include "tools.h"
#include "main.h"
#include <vector>
#include <fcntl.h>
#include <termios.h>
@@ -20,6 +22,7 @@ cInverter::cInverter(std::string devicename, int qpiri, int qpiws, int qmod, int
buf_qpiws = qpiws;
buf_qmod = qmod;
buf_qpigs = qpigs;
last_reply_size = 0;
lprintf("INVERTER: Initialized with buffer sizes - QPIRI:%d QPIWS:%d QMOD:%d QPIGS:%d",
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 &= ~CSTOPB; // 1 stop bit
settings.c_cflag &= ~CSIZE;
settings.c_cflag |= CS8 | CLOCAL; // 8 bits
// settings.c_lflag = ICANON; // canonical mode
settings.c_oflag &= ~OPOST; // raw output
settings.c_cflag |= CS8 | CLOCAL | CREAD; // 8 bits, local, enable receiver
settings.c_iflag = 0; // raw input
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
@@ -141,8 +147,12 @@ bool cInverter::query(const char *cmd, int replysize) {
memset(buf, 0, sizeof(buf));
time(&started);
bool started_frame = false;
unsigned char ch = 0;
int max_read = (int)sizeof(buf) - 1;
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 (time(NULL) - started > 2) {
lprintf("INVERTER: %s read timeout", cmd);
@@ -154,18 +164,35 @@ bool cInverter::query(const char *cmd, int replysize) {
}
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)
if (i > 0 && (buf[i-1] == 0x0d || buf[i-1] == 0x0a)) {
lprintf("INVERTER: %s received terminator (0x%02X) at byte %d", cmd, buf[i-1], i);
if (ch == 0x0d || ch == 0x0a) {
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;
}
}
} while (i<replysize && (time(NULL) - started < 3));
} while ((i < max_read || !started_frame) && (time(NULL) - started < 5));
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);
@@ -197,6 +224,12 @@ bool cInverter::query(const char *cmd, int replysize) {
buf[i-3] = '\0'; //nullterminating on first CRC byte
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);
// If expected size doesn't match actual size, log it
@@ -207,6 +240,7 @@ bool cInverter::query(const char *cmd, int replysize) {
return true;
} else {
last_reply_size = 0;
lprintf("INVERTER: %s reply too short (%d bytes)", cmd, i);
return false;
}
@@ -227,6 +261,7 @@ void cInverter::poll() {
ups_qmod_changed = true;
fprintf(stderr, "[POLL] QMOD completed\n");
}
usleep(200000); // allow inverter to settle between commands
}
// reading status (QPIGS)
@@ -239,6 +274,7 @@ void cInverter::poll() {
ups_qpigs_changed = true;
fprintf(stderr, "[POLL] QPIGS completed\n");
}
usleep(200000); // allow inverter to settle between commands
}
// Reading QPIRI status
@@ -251,6 +287,7 @@ void cInverter::poll() {
ups_qpiri_changed = true;
fprintf(stderr, "[POLL] QPIRI completed\n");
}
usleep(200000); // allow inverter to settle between commands
}
// Get any device warnings...
@@ -263,6 +300,7 @@ void cInverter::poll() {
ups_qpiws_changed = true;
fprintf(stderr, "[POLL] QPIWS completed\n");
}
usleep(200000); // allow inverter to settle between commands
}
// 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);
}
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
int cInverter::query_auto(const char *cmd, int max_size) {
time_t started;
@@ -341,7 +508,7 @@ int cInverter::query_auto(const char *cmd, int max_size) {
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) {
lprintf("INVERTER: Unable to open device file for auto-discovery");
return -1;
@@ -356,13 +523,17 @@ int cInverter::query_auto(const char *cmd, int max_size) {
settings.c_cflag &= ~PARENB;
settings.c_cflag &= ~CSTOPB;
settings.c_cflag &= ~CSIZE;
settings.c_cflag |= CS8 | CLOCAL;
settings.c_oflag &= ~OPOST;
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] = 5; // 0.5s read timeout
tcsetattr(fd, TCSANOW, &settings);
// Flush all buffers
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
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);
tcdrain(fd);
usleep(500000); // allow inverter to respond
// Clear buffer for reading
memset(temp_buf, 0, sizeof(temp_buf));
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)) {
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) {
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
if (temp_buf[i-1] == 0x0d) {
if (ch == 0x0d || ch == 0x0a) {
lprintf("INVERTER: Auto-discovery for %s: found CR at byte %d", cmd, i);
break;
}
@@ -397,7 +582,11 @@ int cInverter::query_auto(const char *cmd, int max_size) {
close(fd);
// 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);
return i;
} else {
@@ -426,7 +615,7 @@ void cInverter::AutoDiscoverBufferSizes() {
printf("\n=== AUTO-DISCOVERY MODE ===\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) {
printf("✓ QMOD buffer size: %d\n", qmod_size);
} else {
@@ -434,7 +623,7 @@ void cInverter::AutoDiscoverBufferSizes() {
}
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) {
printf("✓ QPIGS buffer size: %d\n", qpigs_size);
} else {
@@ -442,7 +631,7 @@ void cInverter::AutoDiscoverBufferSizes() {
}
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) {
printf("✓ QPIRI buffer size: %d\n", qpiri_size);
} else {
@@ -450,7 +639,7 @@ void cInverter::AutoDiscoverBufferSizes() {
}
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) {
printf("✓ QPIWS buffer size: %d\n", qpiws_size);
} else {
@@ -468,8 +657,8 @@ void cInverter::AutoDiscoverBufferSizes() {
// Output in parsable format for scripts
printf("DISCOVERY_QMOD=%d\n", qmod_size > 0 ? qmod_size : 5);
printf("DISCOVERY_QPIGS=%d\n", qpigs_size > 0 ? qpigs_size : 110);
printf("DISCOVERY_QPIRI=%d\n", qpiri_size > 0 ? qpiri_size : 98);
printf("DISCOVERY_QPIWS=%d\n", qpiws_size > 0 ? qpiws_size : 36);
printf("DISCOVERY_QPIRI=%d\n", qpiri_size > 0 ? qpiri_size : 103);
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");
}
@@ -496,12 +685,19 @@ int cInverter::DiscoverParallelInverters() {
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;
for (int k = 0; k < j; k++) {
if (serial[k] != '0') {
if (j > 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;
break;
}
}
@@ -552,3 +748,36 @@ string cInverter::GetParallelStatus(int inverter_num) {
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 <thread>
#include <mutex>
#include <vector>
using namespace std;
@@ -23,6 +24,12 @@ class cInverter {
int buf_qpiws;
int buf_qmod;
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);
bool CheckCRC(unsigned char *buff, int len);
@@ -47,6 +54,7 @@ class cInverter {
void AutoDiscoverBufferSizes();
int DiscoverParallelInverters(); // Returns number of parallel inverters
string GetParallelStatus(int inverter_num); // Get QPGS data for specific inverter
int QueryParallelQpgs(int count, std::vector<std::string> &replies);
};
#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)
InputParser cmdArgs(argc, argv);
const string &rawcmd = cmdArgs.getCmdOption("-r");
const string &parallelDataCount = cmdArgs.getCmdOption("-P");
if(cmdArgs.cmdOptionExists("-h") || cmdArgs.cmdOptionExists("--help")) {
return print_help();
@@ -209,6 +210,27 @@ int main(int argc, char* argv[]) {
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..
if (!rawcmd.empty()) {
ups->ExecuteCmd(rawcmd);
+15 -3
View File
@@ -236,7 +236,11 @@ fi
echo ""
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 ""
# 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)
# This will re-create the auto-created topics in the MQTT server if HA is restarted...
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)
echo "Starting MQTT subscriber for commands..."
@@ -280,4 +289,7 @@ echo "✓ All services started successfully!"
echo " Logs will appear below..."
echo ""
watch -n 30 "$MQTT_PUSH_SCRIPT" > /dev/null 2>&1
while true; do
"$MQTT_PUSH_SCRIPT" > /dev/null 2>&1
sleep 30
done
+311 -63
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"
INVERTER_BIN="/opt/inverter-cli/bin/inverter_poller"
MQTT_FALLBACK="/opt/inverter-mqtt/mqtt-push.sh"
INVERTER_CONF="/etc/inverter/inverter.conf"
CONTAINER_MODE=true
else
# Development mode
MQTT_CONFIG="/home/pi/Progetti/config/mqtt.json"
INVERTER_BIN="/home/pi/Progetti/sources/inverter-cli/bin/inverter_poller"
MQTT_FALLBACK="/home/pi/Progetti/sources/inverter-mqtt/mqtt-push.sh"
INVERTER_CONF="/home/pi/Progetti/config/inverter.conf"
CONTAINER_MODE=false
fi
@@ -180,8 +182,36 @@ if [ "$EUID" -ne 0 ] && [ -c "/dev/ttyUSB0" ]; then
SUDO_CMD="sudo"
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
MAX_RETRIES=1 # Reduced to 1 since we'll fallback to direct QPGS anyway
MAX_RETRIES=3
RETRY_DELAY=2
PARALLEL_COUNT=0
@@ -206,43 +236,79 @@ done
if [ -z "$PARALLEL_COUNT" ] || [ "$PARALLEL_COUNT" -eq 0 ]; then
echo "⚠ Discovery reports 0 inverters, trying direct QPGS0/QPGS1 commands..."
# Test QPGS0 to see if parallel mode is active
TEST_QPGS0=`$SUDO_CMD "$INVERTER_BIN" -r "QPGS0" 2>&1 | grep "Reply:"`
TEST_QPGS1=`$SUDO_CMD "$INVERTER_BIN" -r "QPGS1" 2>&1 | grep "Reply:"`
for attempt in 1 2 3; do
# Test QPGS0 to see if parallel mode is active
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"
PARALLEL_COUNT=2
if [ ! -z "$TEST_QPGS0" ] && [[ "$TEST_QPGS0" != *"NAK"* ]]; then
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_2_SERIAL=unknown
INVERTER_2_QPGS=1"
elif [ ! -z "$TEST_QPGS1" ] && [[ "$TEST_QPGS1" != *"NAK"* ]]; then
echo "✓ QPGS1 responds, assuming 2 inverters in cascade"
PARALLEL_COUNT=2
break
elif [ ! -z "$TEST_QPGS1" ] && [[ "$TEST_QPGS1" != *"NAK"* ]]; then
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_2_SERIAL=unknown
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"
PARALLEL_COUNT=0
fi
fi
echo "Processing $PARALLEL_COUNT parallel inverters"
DISCOVERY_SERIALS=()
DISCOVERY_QPGS=()
MAX_QPGS_IDX=-1
# Publish discovery info
pushMQTTData "system" "parallel_count" "$PARALLEL_COUNT"
for i in $(seq 1 $PARALLEL_COUNT); do
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)
echo ""
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
if [ ! -z "$QPIRI_RAW" ] && [ "$QPIRI_RAW" != "NAK" ]; then
@@ -270,18 +336,145 @@ else
echo "⚠ QPIRI failed, configuration parameters unavailable"
fi
# Extract runtime data for each inverter using QPGS
# Extract runtime data for each inverter using QPGS (single session)
PARALLEL_SUCCESS=false
for i in $(seq 1 $PARALLEL_COUNT); do
QPGS_IDX=$((i - 1)) # 0-based index
SERIAL=`echo "$PARALLEL_DISCOVERY" | grep "INVERTER_${i}_SERIAL=" | cut -d= -f2`
PARALLEL_QPGS_COUNT=$PARALLEL_COUNT
if [ "$PARALLEL_QPGS_COUNT" -lt 2 ]; then
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 "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 ...
QPGS_RAW=`$SUDO_CMD "$INVERTER_BIN" -r "QPGS$QPGS_IDX" 2>&1 | grep "Reply:" | cut -d: -f2- | xargs`
# Get QPGS data from single-session output, fallback to direct query
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
echo " ✓ QPGS$QPGS_IDX successful"
@@ -295,74 +488,129 @@ for i in $(seq 1 $PARALLEL_COUNT); do
# 8=VA, 9=W, 10=PCT, 11=BattV, 12=ChgA, 13=Cap, 14=PVV, 15=PVA
# 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
pushMQTTData "$i" "serial" "$SERIAL"
pushMQTTData "$inv_id" "serial" "$SERIAL"
# Runtime data from QPGS (19 parameters)
[ "${DATA[2]}" ] && pushMQTTData "$i" "Inverter_mode" "${DATA[2]}"
[ "${DATA[4]}" ] && pushMQTTData "$i" "AC_grid_voltage" "${DATA[4]}"
[ "${DATA[5]}" ] && pushMQTTData "$i" "AC_grid_frequency" "${DATA[5]}"
[ "${DATA[6]}" ] && pushMQTTData "$i" "AC_out_voltage" "${DATA[6]}"
[ "${DATA[7]}" ] && pushMQTTData "$i" "AC_out_frequency" "${DATA[7]}"
[ "${DATA[8]}" ] && pushMQTTData "$i" "Load_va" "${DATA[8]}"
[ "${DATA[9]}" ] && pushMQTTData "$i" "Load_watt" "${DATA[9]}"
[ "${DATA[10]}" ] && pushMQTTData "$i" "Load_pct" "${DATA[10]}"
[ "${DATA[11]}" ] && pushMQTTData "$i" "Battery_voltage" "${DATA[11]}"
[ "${DATA[12]}" ] && pushMQTTData "$i" "Battery_charge_current" "${DATA[12]}"
[ "${DATA[13]}" ] && pushMQTTData "$i" "Battery_capacity" "${DATA[13]}"
[ "${DATA[14]}" ] && pushMQTTData "$i" "PV_in_voltage" "${DATA[14]}"
[ "${DATA[15]}" ] && pushMQTTData "$i" "PV_in_current" "${DATA[15]}"
[ "${DATA[2]}" ] && pushMQTTData "$inv_id" "Inverter_mode" "${DATA[2]}"
[ "${DATA[4]}" ] && pushMQTTData "$inv_id" "AC_grid_voltage" "${DATA[4]}"
[ "${DATA[5]}" ] && pushMQTTData "$inv_id" "AC_grid_frequency" "${DATA[5]}"
[ "${DATA[6]}" ] && pushMQTTData "$inv_id" "AC_out_voltage" "${DATA[6]}"
[ "${DATA[7]}" ] && pushMQTTData "$inv_id" "AC_out_frequency" "${DATA[7]}"
[ "${DATA[8]}" ] && pushMQTTData "$inv_id" "Load_va" "${DATA[8]}"
[ "${DATA[9]}" ] && pushMQTTData "$inv_id" "Load_watt" "${DATA[9]}"
[ "${DATA[10]}" ] && pushMQTTData "$inv_id" "Load_pct" "${DATA[10]}"
[ "${DATA[11]}" ] && pushMQTTData "$inv_id" "Battery_voltage" "${DATA[11]}"
[ "${DATA[12]}" ] && pushMQTTData "$inv_id" "Battery_charge_current" "${DATA[12]}"
[ "${DATA[13]}" ] && pushMQTTData "$inv_id" "Battery_capacity" "${DATA[13]}"
[ "${DATA[14]}" ] && pushMQTTData "$inv_id" "PV_in_voltage" "${DATA[14]}"
[ "${DATA[15]}" ] && pushMQTTData "$inv_id" "PV_in_current" "${DATA[15]}"
# Calculate PV watts (V * A)
if [ ! -z "${DATA[14]}" ] && [ ! -z "${DATA[15]}" ]; then
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
# Status flags (parse from field 18 bitmap if available)
[ "${DATA[18]}" ] && {
BITMAP="${DATA[18]}"
# Binary flags: bit0=Load_on, bit1=SCC_on, bit2=AC_charge_on
LOAD_ON=$((($BITMAP & 1) ? 1 : 0))
SCC_ON=$((($BITMAP & 2) ? 1 : 0))
AC_CHG_ON=$((($BITMAP & 4) ? 1 : 0))
if [[ "$BITMAP" =~ ^[0-9]+$ ]]; then
BITMAP=$((10#$BITMAP))
# Binary flags: bit0=Load_on, bit1=SCC_on, bit2=AC_charge_on
LOAD_ON=$((($BITMAP & 1) ? 1 : 0))
SCC_ON=$((($BITMAP & 2) ? 1 : 0))
AC_CHG_ON=$((($BITMAP & 4) ? 1 : 0))
pushMQTTData "$i" "Load_status_on" "$LOAD_ON"
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 " ⚠ Missing 5 params: PV_watthour, Load_watthour, Bus_voltage, Heatsink_temp, Warnings"
SUCCESS_INV_IDS+=("$inv_id")
else
echo " ✗ QPGS$QPGS_IDX failed (NAK or empty)"
fi
done
# 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 "Publishing shared configuration to all inverters..."
for i in $(seq 1 $PARALLEL_COUNT); do
echo " Replicating QPIRI config to inverter #$i..."
for inv_id in "${SUCCESS_INV_IDS[@]}"; do
echo " Replicating QPIRI config to inverter #$inv_id..."
[ ! -z "$BATT_RECHARGE" ] && pushMQTTData "$i" "Battery_recharge_voltage" "$BATT_RECHARGE"
[ ! -z "$BATT_UNDER" ] && pushMQTTData "$i" "Battery_under_voltage" "$BATT_UNDER"
[ ! -z "$BATT_BULK" ] && pushMQTTData "$i" "Battery_bulk_voltage" "$BATT_BULK"
[ ! -z "$BATT_FLOAT" ] && pushMQTTData "$i" "Battery_float_voltage" "$BATT_FLOAT"
[ ! -z "$MAX_CHARGE_CURRENT" ] && pushMQTTData "$i" "Max_charge_current" "$MAX_CHARGE_CURRENT"
[ ! -z "$MAX_GRID_CHARGE" ] && pushMQTTData "$i" "Max_grid_charge_current" "$MAX_GRID_CHARGE"
[ ! -z "$OUT_SOURCE_PRIORITY" ] && pushMQTTData "$i" "Out_source_priority" "$OUT_SOURCE_PRIORITY"
[ ! -z "$CHARGER_SOURCE_PRIORITY" ] && pushMQTTData "$i" "Charger_source_priority" "$CHARGER_SOURCE_PRIORITY"
[ ! -z "$BATT_REDISCHARGE" ] && pushMQTTData "$i" "Battery_redischarge_voltage" "$BATT_REDISCHARGE"
[ ! -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$i"
echo " ✓ Published 9 shared config parameters to inv$inv_id"
done
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
if [ "$PARALLEL_SUCCESS" = false ]; then
echo ""
+69 -20
View File
@@ -111,12 +111,55 @@ if [ -z "$PARALLEL_COUNT" ]; then
PARALLEL_COUNT=0
fi
VALID_SERIALS=()
VALID_QPGS=()
MAX_QPGS_IDX=-1
if [ $PARALLEL_COUNT -gt 0 ]; then
echo -e "${GREEN}✓ Found $PARALLEL_COUNT parallel inverter(s)${NC}"
for i in $(seq 1 $PARALLEL_COUNT); do
SERIAL=$(echo "$PARALLEL_OUTPUT" | grep "INVERTER_${i}_SERIAL=" | cut -d= -f2)
QPGS_IDX=$(echo "$PARALLEL_OUTPUT" | grep "INVERTER_${i}_QPGS=" | cut -d= -f2)
echo " • Inverter #$i: Serial $SERIAL (QPGS$QPGS_IDX)"
QPGS_COUNT=$PARALLEL_COUNT
if [ "$QPGS_COUNT" -lt 2 ]; then
QPGS_COUNT=2
fi
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
else
echo -e "${YELLOW}⚠ No parallel inverters found, using single mode${NC}"
@@ -151,25 +194,30 @@ while true; do
echo ""
# 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}"
for i in $(seq 1 $PARALLEL_COUNT); do
SERIAL=$(echo "$PARALLEL_OUTPUT" | grep "INVERTER_${i}_SERIAL=" | cut -d= -f2)
QPGS_IDX=$(echo "$PARALLEL_OUTPUT" | grep "INVERTER_${i}_QPGS=" | cut -d= -f2)
QPGS_COUNT=$((MAX_QPGS_IDX + 1))
if [ "$QPGS_COUNT" -lt 1 ]; then
QPGS_COUNT=2
fi
QPGS_OUTPUT=$($SUDO_CMD "$INVERTER_BIN" -P "$QPGS_COUNT" 2>/dev/null)
QPGS_DATA=$($SUDO_CMD "$INVERTER_BIN" -r "QPGS$QPGS_IDX" 2>&1 | grep "Reply:" | cut -d: -f2- | xargs)
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)
if [ ! -z "$QPGS_DATA" ] && [ "$QPGS_DATA" != "NAK" ]; then
# Parse key values
if [ ! -z "$QPGS_DATA" ]; then
IFS=' ' read -ra DATA <<< "$QPGS_DATA"
MODE="${DATA[2]}"
GRID_V="${DATA[4]}"
BATT_V="${DATA[11]}"
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
echo -e " ${RED}${NC} Inverter #$i ($SERIAL): No data"
echo -e " ${RED}${NC} Inverter #$inv_id ($SERIAL): No data"
fi
done
echo ""
@@ -184,13 +232,14 @@ while true; do
echo "$MQTT_OUTPUT" | sed 's/^/ /'
# Show sample topics published
if [ $PARALLEL_COUNT -gt 0 ]; then
if [ ${#VALID_SERIALS[@]} -gt 0 ]; then
echo -e " ${GREEN}Sample topics published:${NC}"
for i in $(seq 1 $PARALLEL_COUNT); do
echo "$MQTT_TOPIC/sensor/${MQTT_DEVICE}_inv${i}_serial"
echo "$MQTT_TOPIC/sensor/${MQTT_DEVICE}_inv${i}_Battery_voltage"
echo "$MQTT_TOPIC/sensor/${MQTT_DEVICE}_inv${i}_Load_watt"
[ $i -eq 1 ] && echo " • ... (and more)"
for idx in "${!VALID_SERIALS[@]}"; do
inv_id=$((idx + 1))
echo "$MQTT_TOPIC/sensor/${MQTT_DEVICE}_inv${inv_id}_serial"
echo "$MQTT_TOPIC/sensor/${MQTT_DEVICE}_inv${inv_id}_Battery_voltage"
echo "$MQTT_TOPIC/sensor/${MQTT_DEVICE}_inv${inv_id}_Load_watt"
[ $inv_id -eq 1 ] && echo " • ... (and more)"
done
fi