Migliora discovery e pubblicazione MQTT per inverter in cascata
Build Docker Image for Raspberry Pi / build-and-push (push) Successful in 9m22s
Build Docker Image for Raspberry Pi / build-and-push (push) Successful in 9m22s
This commit is contained in:
@@ -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 {
|
||||
@@ -425,32 +614,32 @@ int cInverter::query_auto(const char *cmd, int max_size) {
|
||||
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 {
|
||||
printf("✗ QMOD auto-discovery failed\n");
|
||||
}
|
||||
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 {
|
||||
printf("✗ QPIGS auto-discovery failed\n");
|
||||
}
|
||||
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 {
|
||||
printf("✗ QPIRI auto-discovery failed\n");
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ¶llelDataCount = 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);
|
||||
|
||||
Reference in New Issue
Block a user