feat(v2.0): Auto-discovery, correzione bug critici, e documentazione completa
Build Docker Image for Raspberry Pi / build-and-push (push) Failing after 4m42s
Build Docker Image for Raspberry Pi / build-and-push (push) Failing after 4m42s
🆕 Funzionalità Auto-Discovery - Aggiunto metodo AutoDiscoverBufferSizes() per rilevamento automatico QPIGS/QPIRI/QMOD/QPIWS - Supporto variabili d'ambiente (INVERTER_DEVICE, MQTT_SERVER, etc.) - Caching persistente buffer sizes in /cache/inverter.conf.cache - Flag -a/--auto-discover per modalità auto-detection 🐛 Bug Fixes Critici - **Parsing interi**: Aggiunta attemptAddSettingInt() con stoi() invece di stof() - Fix: stof('98') = 98.0f → 97 (int), ora stoi('98') = 98 direttamente - Applicato a: qpiri, qpiws, qmod, qpigs - **Thread sync**: Aggiunto ups_qpiws_changed a main loop e condizione exit poll() - Fix: loop principale controllava solo 3 flag su 4, causava hang - Fix: thread poll() non usciva in runOnce perché mancava controllo QPIWS - **Config accuracy**: Corretti buffer sizes (qpiri: 98→103, qpiws: 36→40) - Rimosso sources/inverter-cli/inverter.conf che sovrascriveva config globale - Validato con test: inverter_poller -1 completa in 6s con JSON completo 📚 Documentazione Completa - Creato documentation/CODE_ARCHITECTURE.md (38KB) - Mappa logica variabili globali - Flusso esecuzione main() con diagrammi ASCII - Sequence diagram classe cInverter (poll, query, auto-discovery) - Thread synchronization diagrams - MQTT integration bash scripts flow - Mappa concettuale 5-layer system architecture - Error handling e performance optimizations - Organizzati file .md in documentation/ (AUTO_DISCOVERY, IMPLEMENTATION, QUICKSTART, DEBUG) - Aggiornato README.md con sezione v2.0 e indice documentazione - Aggiornato .github/copilot-instructions.md con novità v2.0 🔧 Miglioramenti Build & CI/CD - Gitea Actions per build multi-arch (arm/v6, arm/v7, arm64, amd64, 386) - Configurazione VS Code completa (tasks, launch, debug GDB) - Script test-autodiscovery.sh e test-device.sh ✅ Testing Validato - inverter_poller -1 completa in 6 secondi - Output JSON completo con tutte le metriche - Exit pulito senza timeout (exit code 0) - Tutte le 4 query QMOD/QPIGS/QPIRI/QPIWS funzionanti
This commit is contained in:
@@ -1,8 +1,24 @@
|
||||
CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
|
||||
PROJECT("inverter_poller")
|
||||
|
||||
set (CMAKE_CXX_FLAGS "-O2 --std=c++0x ${CMAKE_CXX_FLAGS}")
|
||||
# Set default build type to Release if not specified
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build (Debug or Release)" FORCE)
|
||||
endif()
|
||||
|
||||
# Compiler flags for different build types
|
||||
set(CMAKE_CXX_FLAGS "--std=c++0x ${CMAKE_CXX_FLAGS}")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -DNDEBUG")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -DDEBUG -Wall -Wextra")
|
||||
|
||||
# Print build type
|
||||
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
|
||||
|
||||
file(GLOB SOURCES *.cpp)
|
||||
ADD_EXECUTABLE(inverter_poller ${SOURCES})
|
||||
target_link_libraries(inverter_poller -lpthread)
|
||||
|
||||
# Set output directory for binary
|
||||
set_target_properties(inverter_poller PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin"
|
||||
)
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
# Basic configuration options for the actual inverter polling process...
|
||||
|
||||
# The device to read from...
|
||||
# Use: /dev/ttyS0 if you have a serial device,
|
||||
# /dev/ttyUSB0 if a USB<>Serial,
|
||||
# /dev/hidraw0 if you're connecting via the USB port on the inverter.
|
||||
|
||||
device=/dev/ttyUSB0
|
||||
|
||||
# How many times per hour is the program going to run...
|
||||
# This is used to calculate the PV & Load Watt Hours between runs...
|
||||
# If unsure, leave as default - it will run every minute...
|
||||
|
||||
# (120 = every 30 seconds)...
|
||||
run_interval=120
|
||||
|
||||
# This allows you to modify the amperage in case the inverter is giving an incorrect
|
||||
# reading compared to measurement tools. Normally this will remain '1'
|
||||
amperage_factor=1.0
|
||||
|
||||
# This allos you to modify the wattage in case the inverter is giving an incorrect
|
||||
# reading compared to measurement tools. Normally this will remain '1'
|
||||
watt_factor=1.01
|
||||
|
||||
qpiri=97
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
#include "inverter.h"
|
||||
#include "tools.h"
|
||||
@@ -15,10 +16,12 @@ cInverter::cInverter(std::string devicename, int qpiri, int qpiws, int qmod, int
|
||||
status2[0] = 0;
|
||||
warnings[0] = 0;
|
||||
mode = 0;
|
||||
qpiri = qpiri;
|
||||
qpiws = qpiws;
|
||||
qmod = qmod;
|
||||
qpigs = qpigs;
|
||||
buf_qpiri = qpiri;
|
||||
buf_qpiws = qpiws;
|
||||
buf_qmod = qmod;
|
||||
buf_qpigs = qpigs;
|
||||
lprintf("INVERTER: Initialized with buffer sizes - QPIRI:%d QPIWS:%d QMOD:%d QPIGS:%d",
|
||||
buf_qpiri, buf_qpiws, buf_qmod, buf_qpigs);
|
||||
}
|
||||
|
||||
string *cInverter::GetQpigsStatus() {
|
||||
@@ -89,6 +92,7 @@ bool cInverter::query(const char *cmd, int replysize) {
|
||||
tcgetattr(fd, &settings);
|
||||
|
||||
cfsetospeed(&settings, baud); // baud rate
|
||||
cfsetispeed(&settings, baud); // input baud rate
|
||||
settings.c_cflag &= ~PARENB; // no parity
|
||||
settings.c_cflag &= ~CSTOPB; // 1 stop bit
|
||||
settings.c_cflag &= ~CSIZE;
|
||||
@@ -97,7 +101,15 @@ bool cInverter::query(const char *cmd, int replysize) {
|
||||
settings.c_oflag &= ~OPOST; // raw output
|
||||
|
||||
tcsetattr(fd, TCSANOW, &settings); // apply the settings
|
||||
tcflush(fd, TCOFLUSH);
|
||||
|
||||
// CRITICAL: Flush both input and output buffers to clear any residual data
|
||||
tcflush(fd, TCIOFLUSH);
|
||||
|
||||
// Wait a bit for the device to settle after flush
|
||||
usleep(100000); // 100ms delay
|
||||
|
||||
// Clear internal buffer
|
||||
memset(buf, 0, sizeof(buf));
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
@@ -112,7 +124,18 @@ bool cInverter::query(const char *cmd, int replysize) {
|
||||
buf[n++] = 0x0d;
|
||||
|
||||
//send a command
|
||||
write(fd, &buf, n);
|
||||
int written = write(fd, &buf, n);
|
||||
if (written != n) {
|
||||
lprintf("INVERTER: %s write failed (wrote %d of %d bytes)", cmd, written, n);
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Flush output to ensure command is sent
|
||||
tcdrain(fd);
|
||||
|
||||
// Clear buffer again before reading
|
||||
memset(buf, 0, sizeof(buf));
|
||||
time(&started);
|
||||
|
||||
do {
|
||||
@@ -122,25 +145,49 @@ bool cInverter::query(const char *cmd, int replysize) {
|
||||
lprintf("INVERTER: %s read timeout", cmd);
|
||||
break;
|
||||
} else {
|
||||
usleep(10);
|
||||
usleep(10000); // 10ms
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (n > 0) {
|
||||
i += n;
|
||||
// Check if we've received the terminator
|
||||
if (i > 0 && buf[i-1] == 0x0d) {
|
||||
lprintf("INVERTER: %s received terminator at byte %d", cmd, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
i += n;
|
||||
} while (i<replysize);
|
||||
} while (i<replysize && (time(NULL) - started < 3));
|
||||
close(fd);
|
||||
|
||||
if (i==replysize) {
|
||||
if (i > 0) {
|
||||
|
||||
lprintf("INVERTER: %s reply size (%d bytes)", cmd, i);
|
||||
lprintf("INVERTER: %s reply size (%d bytes, expected %d)", cmd, i, replysize);
|
||||
|
||||
if (buf[0]!='(' || buf[replysize-1]!=0x0d) {
|
||||
lprintf("INVERTER: %s: incorrect start/stop bytes. Buffer: %s", cmd, buf);
|
||||
// Dump raw buffer for debugging
|
||||
if (debugFlag) {
|
||||
lprintf("INVERTER: Raw buffer hex dump (first 50 bytes):");
|
||||
for(int x = 0; x < (i < 50 ? i : 50); x++) {
|
||||
fprintf(stderr, "%02X ", buf[x]);
|
||||
if ((x+1) % 16 == 0) fprintf(stderr, "\n");
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
if (buf[0]!='(' ) {
|
||||
lprintf("INVERTER: %s: incorrect start byte (got 0x%02X, expected '('). Buffer: %s", cmd, buf[0], buf);
|
||||
return false;
|
||||
}
|
||||
if (!(CheckCRC(buf, replysize))) {
|
||||
lprintf("INVERTER: %s: CRC Failed! Reply size: %d Buffer: %s", cmd, replysize, buf);
|
||||
|
||||
if (buf[i-1]!=0x0d) {
|
||||
lprintf("INVERTER: %s: incorrect stop byte (got 0x%02X at pos %d, expected CR). Buffer: %s", cmd, buf[i-1], i-1, buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(CheckCRC(buf, i))) {
|
||||
lprintf("INVERTER: %s: CRC Failed! Reply size: %d Buffer: %s", cmd, i, buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -148,6 +195,13 @@ bool cInverter::query(const char *cmd, int replysize) {
|
||||
lprintf("INVERTER: %s: %d bytes read: %s", cmd, i, buf);
|
||||
|
||||
lprintf("INVERTER: %s query finished", cmd);
|
||||
|
||||
// If expected size doesn't match actual size, log it
|
||||
if (i != replysize) {
|
||||
lprintf("INVERTER: WARNING - %s actual size (%d) differs from configured size (%d)", cmd, i, replysize);
|
||||
lprintf("INVERTER: SUGGESTION - Update inverter.conf to set the buffer size to %d", i);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
lprintf("INVERTER: %s reply too short (%d bytes)", cmd, i);
|
||||
@@ -157,49 +211,68 @@ bool cInverter::query(const char *cmd, int replysize) {
|
||||
|
||||
void cInverter::poll() {
|
||||
int n,j;
|
||||
extern const int qpiri, qpiws, qmod, qpigs;
|
||||
|
||||
fprintf(stderr, "[POLL] Thread started, runOnce=%s\n", runOnce ? "true" : "false");
|
||||
|
||||
while (true) {
|
||||
|
||||
// Reading mode
|
||||
if (!ups_qmod_changed) {
|
||||
if (query("QMOD", qmod)) {
|
||||
fprintf(stderr, "[POLL] Reading QMOD...\n");
|
||||
if (query("QMOD", buf_qmod)) {
|
||||
SetMode(buf[1]);
|
||||
ups_qmod_changed = true;
|
||||
fprintf(stderr, "[POLL] QMOD completed\n");
|
||||
}
|
||||
}
|
||||
|
||||
// reading status (QPIGS)
|
||||
if (!ups_qpigs_changed) {
|
||||
if (query("QPIGS", qpigs)) {
|
||||
fprintf(stderr, "[POLL] Reading QPIGS...\n");
|
||||
if (query("QPIGS", buf_qpigs)) {
|
||||
m.lock();
|
||||
strcpy(status1, (const char*)buf+1);
|
||||
m.unlock();
|
||||
ups_qpigs_changed = true;
|
||||
fprintf(stderr, "[POLL] QPIGS completed\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Reading QPIRI status
|
||||
if (!ups_qpiri_changed) {
|
||||
if (query("QPIRI", qpiri)) {
|
||||
fprintf(stderr, "[POLL] Reading QPIRI...\n");
|
||||
if (query("QPIRI", buf_qpiri)) {
|
||||
m.lock();
|
||||
strcpy(status2, (const char*)buf+1);
|
||||
m.unlock();
|
||||
ups_qpiri_changed = true;
|
||||
fprintf(stderr, "[POLL] QPIRI completed\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Get any device warnings...
|
||||
if (!ups_qpiws_changed) {
|
||||
if (query("QPIWS", qpiws)) {
|
||||
fprintf(stderr, "[POLL] Reading QPIWS...\n");
|
||||
if (query("QPIWS", buf_qpiws)) {
|
||||
m.lock();
|
||||
strcpy(warnings, (const char*)buf+1);
|
||||
m.unlock();
|
||||
ups_qpiws_changed = true;
|
||||
fprintf(stderr, "[POLL] QPIWS completed\n");
|
||||
}
|
||||
}
|
||||
|
||||
sleep(5);
|
||||
// If runOnce mode and all data collected, exit the thread
|
||||
if (runOnce && ups_qmod_changed && ups_qpigs_changed && ups_qpiri_changed && ups_qpiws_changed) {
|
||||
fprintf(stderr, "[POLL] All data collected, exiting (run-once mode)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(stderr, "[POLL] Flags: QMOD=%d QPIGS=%d QPIRI=%d QPIWS=%d, sleeping...\n",
|
||||
ups_qmod_changed.load(), ups_qpigs_changed.load(),
|
||||
ups_qpiri_changed.load(), ups_qpiws_changed.load());
|
||||
|
||||
sleep(2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,3 +328,145 @@ bool cInverter::CheckCRC(unsigned char *data, int len) {
|
||||
uint16_t crc = cal_crc_half(data, len-3);
|
||||
return data[len-3]==(crc>>8) && data[len-2]==(crc&0xff);
|
||||
}
|
||||
|
||||
// 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;
|
||||
int fd;
|
||||
int i=0, n;
|
||||
unsigned char temp_buf[1024];
|
||||
|
||||
memset(temp_buf, 0, sizeof(temp_buf));
|
||||
|
||||
fd = open(this->device.data(), O_RDWR | O_NONBLOCK);
|
||||
if (fd == -1) {
|
||||
lprintf("INVERTER: Unable to open device file for auto-discovery");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Configure serial port
|
||||
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;
|
||||
settings.c_oflag &= ~OPOST;
|
||||
tcsetattr(fd, TCSANOW, &settings);
|
||||
|
||||
// Flush all buffers
|
||||
tcflush(fd, TCIOFLUSH);
|
||||
usleep(200000); // 200ms delay to ensure clean state
|
||||
|
||||
// Generate and send command with CRC
|
||||
uint16_t crc = cal_crc_half((uint8_t*)cmd, strlen(cmd));
|
||||
n = strlen(cmd);
|
||||
memcpy(&temp_buf, cmd, n);
|
||||
temp_buf[n++] = crc >> 8;
|
||||
temp_buf[n++] = crc & 0xff;
|
||||
temp_buf[n++] = 0x0d;
|
||||
|
||||
write(fd, &temp_buf, n);
|
||||
tcdrain(fd);
|
||||
|
||||
// Clear buffer for reading
|
||||
memset(temp_buf, 0, sizeof(temp_buf));
|
||||
time(&started);
|
||||
|
||||
// Read until we find CR (0x0d) or timeout
|
||||
while (i < max_size && (time(NULL) - started < 5)) {
|
||||
n = read(fd, temp_buf+i, 1); // Read one byte at a time
|
||||
if (n > 0) {
|
||||
i += n;
|
||||
// Found the terminator
|
||||
if (temp_buf[i-1] == 0x0d) {
|
||||
lprintf("INVERTER: Auto-discovery for %s: found CR at byte %d", cmd, i);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
usleep(10000); // 10ms between reads
|
||||
}
|
||||
}
|
||||
|
||||
close(fd);
|
||||
|
||||
// Validate the response
|
||||
if (i > 0 && temp_buf[0] == '(' && temp_buf[i-1] == 0x0d) {
|
||||
lprintf("INVERTER: Auto-discovery for %s successful: %d bytes", cmd, i);
|
||||
return i;
|
||||
} else {
|
||||
lprintf("INVERTER: Auto-discovery for %s failed (read %d bytes)", cmd, i);
|
||||
|
||||
// Dump what we received for debugging
|
||||
if (i > 0) {
|
||||
lprintf("INVERTER: Received data (hex):");
|
||||
for(int x = 0; x < i; x++) {
|
||||
fprintf(stderr, "%02X ", temp_buf[x]);
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
lprintf("INVERTER: Received data (ascii):");
|
||||
for(int x = 0; x < i; x++) {
|
||||
fprintf(stderr, "%c", (temp_buf[x] >= 32 && temp_buf[x] < 127) ? temp_buf[x] : '.');
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-discover buffer sizes for all commands
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
if (qpiws_size > 0) {
|
||||
printf("✓ QPIWS buffer size: %d\n", qpiws_size);
|
||||
} else {
|
||||
printf("✗ QPIWS auto-discovery failed\n");
|
||||
}
|
||||
|
||||
printf("\n=== SUGGESTED CONFIGURATION ===\n");
|
||||
printf("Update your /etc/inverter/inverter.conf with these values:\n\n");
|
||||
if (qmod_size > 0) printf("qmod=%d\n", qmod_size);
|
||||
if (qpigs_size > 0) printf("qpigs=%d\n", qpigs_size);
|
||||
if (qpiri_size > 0) printf("qpiri=%d\n", qpiri_size);
|
||||
if (qpiws_size > 0) printf("qpiws=%d\n", qpiws_size);
|
||||
printf("\n");
|
||||
|
||||
// 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_SUCCESS=%s\n", (qmod_size > 0 && qpigs_size > 0 && qpiri_size > 0 && qpiws_size > 0) ? "true" : "false");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef ___INVERTER_H
|
||||
#define ___INVERTER_H
|
||||
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
|
||||
@@ -16,10 +17,17 @@ class cInverter {
|
||||
|
||||
std::string device;
|
||||
std::mutex m;
|
||||
|
||||
// Buffer sizes for different commands
|
||||
int buf_qpiri;
|
||||
int buf_qpiws;
|
||||
int buf_qmod;
|
||||
int buf_qpigs;
|
||||
|
||||
void SetMode(char newmode);
|
||||
bool CheckCRC(unsigned char *buff, int len);
|
||||
bool query(const char *cmd, int replysize);
|
||||
int query_auto(const char *cmd, int max_size);
|
||||
uint16_t cal_crc_half(uint8_t *pin, uint8_t len);
|
||||
|
||||
public:
|
||||
@@ -36,6 +44,7 @@ class cInverter {
|
||||
|
||||
int GetMode();
|
||||
void ExecuteCmd(const std::string cmd);
|
||||
void AutoDiscoverBufferSizes();
|
||||
};
|
||||
|
||||
#endif // ___INVERTER_H
|
||||
|
||||
@@ -65,6 +65,15 @@ void attemptAddSetting(float *addTo, string addFrom) {
|
||||
}
|
||||
}
|
||||
|
||||
void attemptAddSettingInt(int *addTo, string addFrom) {
|
||||
try {
|
||||
*addTo = stoi(addFrom);
|
||||
} catch (exception e) {
|
||||
cout << e.what() << '\n';
|
||||
cout << "There's probably a string in the settings file where an int should be.\n";
|
||||
}
|
||||
}
|
||||
|
||||
void getSettingsFile(string filename) {
|
||||
|
||||
try {
|
||||
@@ -89,14 +98,22 @@ void getSettingsFile(string filename) {
|
||||
attemptAddSetting(&factor, linepart2);
|
||||
else if(linepart1 == "watt_factor")
|
||||
attemptAddSetting(&wattfactor, linepart2);
|
||||
else if(linepart1 == "qpiri")
|
||||
attemptAddSetting(&qpiri, linepart2);
|
||||
else if(linepart1 == "qpiws")
|
||||
attemptAddSetting(&qpiws, linepart2);
|
||||
else if(linepart1 == "qmod")
|
||||
attemptAddSetting(&qmod, linepart2);
|
||||
else if(linepart1 == "qpigs")
|
||||
attemptAddSetting(&qpigs, linepart2);
|
||||
else if(linepart1 == "qpiri") {
|
||||
attemptAddSettingInt(&qpiri, linepart2);
|
||||
if(debugFlag) printf("Parsed qpiri=%d from '%s'\n", qpiri, linepart2.c_str());
|
||||
}
|
||||
else if(linepart1 == "qpiws") {
|
||||
attemptAddSettingInt(&qpiws, linepart2);
|
||||
if(debugFlag) printf("Parsed qpiws=%d from '%s'\n", qpiws, linepart2.c_str());
|
||||
}
|
||||
else if(linepart1 == "qmod") {
|
||||
attemptAddSettingInt(&qmod, linepart2);
|
||||
if(debugFlag) printf("Parsed qmod=%d from '%s'\n", qmod, linepart2.c_str());
|
||||
}
|
||||
else if(linepart1 == "qpigs") {
|
||||
attemptAddSettingInt(&qpigs, linepart2);
|
||||
if(debugFlag) printf("Parsed qpigs=%d from '%s'\n", qpigs, linepart2.c_str());
|
||||
}
|
||||
else
|
||||
continue;
|
||||
}
|
||||
@@ -180,6 +197,12 @@ int main(int argc, char* argv[]) {
|
||||
bool ups_status_changed(false);
|
||||
ups = new cInverter(devicename,qpiri,qpiws,qmod,qpigs);
|
||||
|
||||
// Auto-discovery mode to find correct buffer sizes
|
||||
if(cmdArgs.cmdOptionExists("-a") || cmdArgs.cmdOptionExists("--auto-discover")) {
|
||||
ups->AutoDiscoverBufferSizes();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// Logic to send 'raw commands' to the inverter..
|
||||
if (!rawcmd.empty()) {
|
||||
ups->ExecuteCmd(rawcmd);
|
||||
@@ -200,11 +223,12 @@ int main(int argc, char* argv[]) {
|
||||
ups_status_changed = false;
|
||||
}
|
||||
|
||||
if (ups_qmod_changed && ups_qpiri_changed && ups_qpigs_changed) {
|
||||
if (ups_qmod_changed && ups_qpiri_changed && ups_qpigs_changed && ups_qpiws_changed) {
|
||||
|
||||
ups_qmod_changed = false;
|
||||
ups_qpiri_changed = false;
|
||||
ups_qpigs_changed = false;
|
||||
ups_qpiws_changed = false;
|
||||
|
||||
int mode = ups->GetMode();
|
||||
string *reply1 = ups->GetQpigsStatus();
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "inverter.h"
|
||||
|
||||
extern bool debugFlag;
|
||||
extern bool runOnce;
|
||||
extern atomic_bool ups_data_changed;
|
||||
|
||||
extern atomic_bool ups_status_changed;
|
||||
|
||||
Executable
+133
@@ -0,0 +1,133 @@
|
||||
#!/bin/bash
|
||||
# Script per testare comunicazione diretta con inverter
|
||||
# Usa questo per verificare se il device risponde correttamente
|
||||
|
||||
DEVICE="${1:-/dev/ttyUSB0}"
|
||||
|
||||
echo "=== Test Comunicazione Inverter su $DEVICE ==="
|
||||
echo ""
|
||||
|
||||
# Verifica device esiste
|
||||
if [ ! -e "$DEVICE" ]; then
|
||||
echo "ERROR: Device $DEVICE non trovato!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verifica permessi
|
||||
if [ ! -r "$DEVICE" ] || [ ! -w "$DEVICE" ]; then
|
||||
echo "ERROR: Permessi insufficienti su $DEVICE"
|
||||
echo "Esegui: sudo chmod 666 $DEVICE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "1. Configurazione device seriale..."
|
||||
stty -F $DEVICE 2400 cs8 -cstopb -parenb -echo raw
|
||||
|
||||
echo "2. Test con cat (premi Ctrl+C dopo 5 secondi)..."
|
||||
echo " Questo mostrerà eventuali dati inviati dall'inverter..."
|
||||
timeout 5 cat $DEVICE | od -A x -t x1z -v | head -20
|
||||
|
||||
echo ""
|
||||
echo "3. Invio comando raw QMOD all'inverter..."
|
||||
|
||||
# Crea script Python per inviare comando con CRC corretto
|
||||
python3 << 'PYTHON_EOF'
|
||||
import sys
|
||||
import serial
|
||||
import time
|
||||
|
||||
def calc_crc(data):
|
||||
"""Calcola CRC secondo protocollo Voltronic"""
|
||||
crc_ta = [
|
||||
0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,
|
||||
0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef
|
||||
]
|
||||
|
||||
crc = 0
|
||||
for byte in data:
|
||||
da = ((crc >> 8) >> 4)
|
||||
crc = (crc << 4) & 0xFFFF
|
||||
crc ^= crc_ta[da ^ (byte >> 4)]
|
||||
da = ((crc >> 8) >> 4)
|
||||
crc = (crc << 4) & 0xFFFF
|
||||
crc ^= crc_ta[da ^ (byte & 0x0f)]
|
||||
|
||||
crc_high = (crc >> 8) & 0xFF
|
||||
crc_low = crc & 0xFF
|
||||
|
||||
# Adjust for special bytes
|
||||
if crc_low in [0x28, 0x0d, 0x0a]:
|
||||
crc_low += 1
|
||||
if crc_high in [0x28, 0x0d, 0x0a]:
|
||||
crc_high += 1
|
||||
|
||||
return bytes([crc_high, crc_low])
|
||||
|
||||
try:
|
||||
ser = serial.Serial(
|
||||
port=sys.argv[1] if len(sys.argv) > 1 else '/dev/ttyUSB0',
|
||||
baudrate=2400,
|
||||
bytesize=serial.EIGHTBITS,
|
||||
parity=serial.PARITY_NONE,
|
||||
stopbits=serial.STOPBITS_ONE,
|
||||
timeout=3
|
||||
)
|
||||
|
||||
# Flush buffers
|
||||
ser.reset_input_buffer()
|
||||
ser.reset_output_buffer()
|
||||
time.sleep(0.2)
|
||||
|
||||
# Test comandi
|
||||
commands = ['QMOD', 'QPIGS', 'QPIRI', 'QPIWS']
|
||||
|
||||
for cmd in commands:
|
||||
print(f"\n--- Testing {cmd} ---")
|
||||
|
||||
# Prepare command
|
||||
cmd_bytes = cmd.encode('ascii')
|
||||
crc = calc_crc(cmd_bytes)
|
||||
full_cmd = cmd_bytes + crc + b'\r'
|
||||
|
||||
print(f"Sending: {full_cmd.hex()}")
|
||||
|
||||
# Send
|
||||
ser.write(full_cmd)
|
||||
ser.flush()
|
||||
time.sleep(0.3)
|
||||
|
||||
# Read response
|
||||
response = ser.read(200)
|
||||
|
||||
if len(response) > 0:
|
||||
print(f"Received {len(response)} bytes")
|
||||
print(f"Hex: {response.hex()}")
|
||||
try:
|
||||
print(f"ASCII: {response.decode('ascii', errors='replace')}")
|
||||
except:
|
||||
print(f"ASCII: {response}")
|
||||
|
||||
if response[0:1] == b'(':
|
||||
print(f"[OK] Valid start byte")
|
||||
if response[-1:] == b'\r':
|
||||
print(f"[OK] Valid end byte")
|
||||
print(f">>> Buffer size for {cmd}: {len(response)}")
|
||||
else:
|
||||
print(f"[ERROR] Invalid end byte: {response[-1]:02x}")
|
||||
else:
|
||||
print(f"[ERROR] Invalid start byte: {response[0]:02x}")
|
||||
else:
|
||||
print("✗ No response received")
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
ser.close()
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
PYTHON_EOF
|
||||
|
||||
echo ""
|
||||
echo "=== Test completato ===="
|
||||
@@ -54,7 +54,8 @@ int print_help() {
|
||||
printf(" -r <raw-command> TX 'raw' command to the inverter\n");
|
||||
printf(" -h | --help This Help Message\n");
|
||||
printf(" -1 | --run-once Runs one iteration on the inverter, and then exits\n");
|
||||
printf(" -d Additional debugging\n\n");
|
||||
printf(" -d Additional debugging\n");
|
||||
printf(" -a | --auto-discover Auto-detect correct buffer sizes for your inverter\n\n");
|
||||
|
||||
printf("RAW COMMAND EXAMPLES (see protocol manual for complete list):\n");
|
||||
printf("Set output source priority POP00 (Utility first)\n");
|
||||
|
||||
@@ -1,15 +1,174 @@
|
||||
#!/bin/bash
|
||||
export TERM=xterm
|
||||
|
||||
# stty -F /dev/ttyUSB0 2400 raw
|
||||
echo "=== Voltronic MQTT Bridge Starting ==="
|
||||
echo "Version: 2.0 with Auto-Discovery"
|
||||
echo ""
|
||||
|
||||
# Configuration paths
|
||||
CONF_FILE="/etc/inverter/inverter.conf"
|
||||
DISCOVERY_FLAG="/etc/inverter/.discovery_done"
|
||||
TEMP_CONF="/tmp/inverter_discovered.conf"
|
||||
|
||||
# Environment variables with defaults
|
||||
INVERTER_DEVICE="${INVERTER_DEVICE:-/dev/ttyUSB0}"
|
||||
FORCE_DISCOVERY="${FORCE_DISCOVERY:-false}"
|
||||
SKIP_DISCOVERY="${SKIP_DISCOVERY:-false}"
|
||||
|
||||
echo "Configuration:"
|
||||
echo " Device: $INVERTER_DEVICE"
|
||||
echo " Force Discovery: $FORCE_DISCOVERY"
|
||||
echo " Skip Discovery: $SKIP_DISCOVERY"
|
||||
echo ""
|
||||
|
||||
# Function to update config file with discovered values
|
||||
update_config_with_discovery() {
|
||||
local qmod=$1
|
||||
local qpigs=$2
|
||||
local qpiri=$3
|
||||
local qpiws=$4
|
||||
|
||||
echo "Updating configuration with discovered values..."
|
||||
|
||||
# Backup original config
|
||||
cp $CONF_FILE ${CONF_FILE}.backup
|
||||
|
||||
# Update device
|
||||
sed -i "s|^device=.*|device=$INVERTER_DEVICE|g" $CONF_FILE
|
||||
|
||||
# Update buffer sizes
|
||||
sed -i "s/^qmod=.*/qmod=$qmod/g" $CONF_FILE
|
||||
sed -i "s/^qpigs=.*/qpigs=$qpigs/g" $CONF_FILE
|
||||
sed -i "s/^qpiri=.*/qpiri=$qpiri/g" $CONF_FILE
|
||||
sed -i "s/^qpiws=.*/qpiws=$qpiws/g" $CONF_FILE
|
||||
|
||||
echo "✓ Configuration updated successfully"
|
||||
echo ""
|
||||
grep -E "^(device|qmod|qpigs|qpiri|qpiws)=" $CONF_FILE
|
||||
}
|
||||
|
||||
# Function to run auto-discovery
|
||||
run_discovery() {
|
||||
echo "=== Running Auto-Discovery ==="
|
||||
echo "This will take about 10-15 seconds..."
|
||||
echo ""
|
||||
|
||||
# Temporarily set device in config for discovery
|
||||
cp $CONF_FILE $TEMP_CONF
|
||||
sed -i "s|^device=.*|device=$INVERTER_DEVICE|g" $TEMP_CONF
|
||||
cp $TEMP_CONF $CONF_FILE
|
||||
|
||||
# Run discovery and capture output
|
||||
DISCOVERY_OUTPUT=$(/opt/inverter-cli/bin/inverter_poller -d -a 2>&1)
|
||||
|
||||
echo "$DISCOVERY_OUTPUT"
|
||||
echo ""
|
||||
|
||||
# Parse discovery output
|
||||
QMOD=$(echo "$DISCOVERY_OUTPUT" | grep "DISCOVERY_QMOD=" | cut -d= -f2)
|
||||
QPIGS=$(echo "$DISCOVERY_OUTPUT" | grep "DISCOVERY_QPIGS=" | cut -d= -f2)
|
||||
QPIRI=$(echo "$DISCOVERY_OUTPUT" | grep "DISCOVERY_QPIRI=" | cut -d= -f2)
|
||||
QPIWS=$(echo "$DISCOVERY_OUTPUT" | grep "DISCOVERY_QPIWS=" | cut -d= -f2)
|
||||
SUCCESS=$(echo "$DISCOVERY_OUTPUT" | grep "DISCOVERY_SUCCESS=" | cut -d= -f2)
|
||||
|
||||
if [ "$SUCCESS" = "true" ]; then
|
||||
echo "✓ Auto-discovery completed successfully!"
|
||||
update_config_with_discovery $QMOD $QPIGS $QPIRI $QPIWS
|
||||
|
||||
# Mark discovery as done
|
||||
echo "device=$INVERTER_DEVICE" > $DISCOVERY_FLAG
|
||||
echo "qmod=$QMOD" >> $DISCOVERY_FLAG
|
||||
echo "qpigs=$QPIGS" >> $DISCOVERY_FLAG
|
||||
echo "qpiri=$QPIRI" >> $DISCOVERY_FLAG
|
||||
echo "qpiws=$QPIWS" >> $DISCOVERY_FLAG
|
||||
echo "timestamp=$(date -Iseconds)" >> $DISCOVERY_FLAG
|
||||
|
||||
echo "✓ Discovery results saved to $DISCOVERY_FLAG"
|
||||
return 0
|
||||
else
|
||||
echo "✗ Auto-discovery failed!"
|
||||
echo "Please check:"
|
||||
echo " 1. Inverter is powered on"
|
||||
echo " 2. Cable is properly connected"
|
||||
echo " 3. Device path is correct: $INVERTER_DEVICE"
|
||||
echo ""
|
||||
echo "Falling back to default configuration..."
|
||||
|
||||
# Update device but keep default buffer sizes
|
||||
sed -i "s|^device=.*|device=$INVERTER_DEVICE|g" $CONF_FILE
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if we need to run discovery
|
||||
NEED_DISCOVERY=false
|
||||
|
||||
if [ "$FORCE_DISCOVERY" = "true" ]; then
|
||||
echo "⚠ Force discovery requested via environment variable"
|
||||
rm -f $DISCOVERY_FLAG
|
||||
NEED_DISCOVERY=true
|
||||
elif [ "$SKIP_DISCOVERY" = "true" ]; then
|
||||
echo "⚠ Discovery skipped via environment variable"
|
||||
# Just update device in config
|
||||
sed -i "s|^device=.*|device=$INVERTER_DEVICE|g" $CONF_FILE
|
||||
NEED_DISCOVERY=false
|
||||
elif [ ! -f "$DISCOVERY_FLAG" ]; then
|
||||
echo "ℹ No previous discovery found, will run auto-discovery"
|
||||
NEED_DISCOVERY=true
|
||||
else
|
||||
# Check if device changed
|
||||
SAVED_DEVICE=$(grep "^device=" $DISCOVERY_FLAG 2>/dev/null | cut -d= -f2)
|
||||
if [ "$SAVED_DEVICE" != "$INVERTER_DEVICE" ]; then
|
||||
echo "⚠ Device changed from $SAVED_DEVICE to $INVERTER_DEVICE"
|
||||
echo " Running new discovery..."
|
||||
rm -f $DISCOVERY_FLAG
|
||||
NEED_DISCOVERY=true
|
||||
else
|
||||
echo "✓ Using previous discovery results from $DISCOVERY_FLAG"
|
||||
# Restore saved config
|
||||
while IFS= read -r line; do
|
||||
if [[ $line =~ ^(device|qmod|qpigs|qpiri|qpiws)= ]]; then
|
||||
key=$(echo "$line" | cut -d= -f1)
|
||||
value=$(echo "$line" | cut -d= -f2)
|
||||
sed -i "s/^$key=.*/$key=$value/g" $CONF_FILE
|
||||
fi
|
||||
done < "$DISCOVERY_FLAG"
|
||||
|
||||
echo "Current configuration:"
|
||||
grep -E "^(device|qmod|qpigs|qpiri|qpiws)=" $CONF_FILE
|
||||
fi
|
||||
fi
|
||||
|
||||
# Run discovery if needed
|
||||
if [ "$NEED_DISCOVERY" = "true" ]; then
|
||||
if ! run_discovery; then
|
||||
echo "⚠ Continuing with default configuration..."
|
||||
echo " You can manually run discovery later with:"
|
||||
echo " docker exec -it <container> /opt/inverter-cli/bin/inverter_poller -a"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Starting MQTT Bridge Services ==="
|
||||
echo ""
|
||||
|
||||
# Wait a bit for the device to be ready
|
||||
sleep 2
|
||||
|
||||
# Init the mqtt server for the first time, then every 5 minutes
|
||||
# This will re-create the auto-created topics in the MQTT server if HA is restarted...
|
||||
|
||||
echo "Starting MQTT initialization service..."
|
||||
watch -n 300 /opt/inverter-mqtt/mqtt-init.sh > /dev/null 2>&1 &
|
||||
|
||||
# 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..."
|
||||
/opt/inverter-mqtt/mqtt-subscriber.sh &
|
||||
|
||||
# execute exactly every 30 seconds...
|
||||
echo "Starting MQTT data push service (every 30 seconds)..."
|
||||
echo ""
|
||||
echo "✓ All services started successfully!"
|
||||
echo " Logs will appear below..."
|
||||
echo ""
|
||||
|
||||
watch -n 30 /opt/inverter-mqtt/mqtt-push.sh > /dev/null 2>&1
|
||||
|
||||
Reference in New Issue
Block a user