Implementazione supporto multi-inverter paralleli e fix comunicazione MQTT
Build Docker Image for Raspberry Pi / build-and-push (push) Failing after 1m15s
Build Docker Image for Raspberry Pi / build-and-push (push) Failing after 1m15s
- Aggiunto supporto lettura inverter paralleli tramite comandi QPGS0-QPGS9 - Implementato discovery automatico inverter con filtro duplicati e serial invalidi - Risolti bug critici comunicazione seriale: * Fix buffer ExecuteCmd da 7 a 200 bytes * Supporto terminatori CR e LF * Modalità blocking con delay 500ms * Lettura byte-by-byte per terminatore affidabile - Implementato script MQTT per pubblicazione dati multi-inverter: * mqtt-push-parallel.sh con topic separati per ogni inverter * Fix autenticazione MQTT con username/password * Aggiunto flag retain (-r) per persistenza dati - Creato test-loop-parallel.sh per simulazione completa container - Aggiornata documentazione con compatibilità MKS IV e guida test loop - Aggiornati profili debug VS Code per bash e parallel discovery - Configurazione MQTT completa con server reale (192.168.1.37:1883) Sistema testato e funzionante con 2 inverter Voltronic Axpert MKS IV
This commit is contained in:
@@ -76,7 +76,7 @@ bool cInverter::query(const char *cmd, int replysize) {
|
||||
int fd;
|
||||
int i=0, n;
|
||||
|
||||
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 (errno=%d %s)", errno, strerror(errno));
|
||||
sleep(5);
|
||||
@@ -134,12 +134,15 @@ bool cInverter::query(const char *cmd, int replysize) {
|
||||
// Flush output to ensure command is sent
|
||||
tcdrain(fd);
|
||||
|
||||
// Critical delay after write (like Python implementation)
|
||||
usleep(500000); // 500ms delay
|
||||
|
||||
// Clear buffer again before reading
|
||||
memset(buf, 0, sizeof(buf));
|
||||
time(&started);
|
||||
|
||||
do {
|
||||
n = read(fd, (void*)buf+i, replysize-i);
|
||||
n = read(fd, (void*)buf+i, 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);
|
||||
@@ -152,9 +155,9 @@ bool cInverter::query(const char *cmd, int replysize) {
|
||||
|
||||
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);
|
||||
// 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);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -181,8 +184,8 @@ bool cInverter::query(const char *cmd, int replysize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
if (buf[i-1]!=0x0d && buf[i-1]!=0x0a) {
|
||||
lprintf("INVERTER: %s: incorrect stop byte (got 0x%02X at pos %d, expected CR or LF). Buffer: %s", cmd, buf[i-1], i-1, buf);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -277,8 +280,8 @@ void cInverter::poll() {
|
||||
}
|
||||
|
||||
void cInverter::ExecuteCmd(const string cmd) {
|
||||
// Sending any command raw
|
||||
if (query(cmd.data(), 7)) {
|
||||
// Sending any command raw - use larger buffer to accept full responses
|
||||
if (query(cmd.data(), 200)) {
|
||||
m.lock();
|
||||
strcpy(status2, (const char*)buf+1);
|
||||
m.unlock();
|
||||
@@ -470,3 +473,82 @@ void cInverter::AutoDiscoverBufferSizes() {
|
||||
printf("DISCOVERY_SUCCESS=%s\n", (qmod_size > 0 && qpigs_size > 0 && qpiri_size > 0 && qpiws_size > 0) ? "true" : "false");
|
||||
}
|
||||
|
||||
// Discover number of parallel inverters
|
||||
int cInverter::DiscoverParallelInverters() {
|
||||
fprintf(stderr, "\n=== PARALLEL INVERTER DISCOVERY ===\n");
|
||||
fprintf(stderr, "Checking for parallel inverter configuration...\n\n");
|
||||
|
||||
int count = 0;
|
||||
char cmd[16];
|
||||
std::string found_serials[10]; // Track unique serials
|
||||
|
||||
// Test QPGS0 through QPGS9
|
||||
for (int i = 0; i < 10; i++) {
|
||||
snprintf(cmd, sizeof(cmd), "QPGS%d", i);
|
||||
|
||||
if (query(cmd, 200)) {
|
||||
// Check if response is valid (not NAK)
|
||||
if (buf[0] == '(' && buf[1] != 'N') {
|
||||
// Extract serial number (starts at position 3)
|
||||
char serial[20] = {0};
|
||||
int j = 0;
|
||||
for (int k = 3; k < 17 && buf[k] != ' '; k++) {
|
||||
serial[j++] = buf[k];
|
||||
}
|
||||
|
||||
// Check if serial is valid (not all zeros and not empty)
|
||||
bool valid_serial = false;
|
||||
for (int k = 0; k < j; k++) {
|
||||
if (serial[k] != '0') {
|
||||
valid_serial = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if serial is duplicate
|
||||
bool duplicate = false;
|
||||
std::string serial_str(serial);
|
||||
for (int k = 0; k < count; k++) {
|
||||
if (found_serials[k] == serial_str) {
|
||||
duplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (valid_serial && j > 0 && !duplicate) {
|
||||
found_serials[count] = serial_str;
|
||||
count++;
|
||||
fprintf(stderr, "✓ Inverter #%d via %s (Serial: %s)\n", count, cmd, serial);
|
||||
printf("INVERTER_%d_SERIAL=%s\n", count, serial);
|
||||
printf("INVERTER_%d_QPGS=%d\n", count, i);
|
||||
} else if (duplicate) {
|
||||
fprintf(stderr, "⊗ Skipping %s (Duplicate serial: %s)\n", cmd, serial);
|
||||
} else {
|
||||
fprintf(stderr, "⊗ Skipping %s (Invalid serial: %s)\n", cmd, serial);
|
||||
}
|
||||
}
|
||||
}
|
||||
usleep(100000); // 100ms between queries
|
||||
}
|
||||
|
||||
fprintf(stderr, "\n=== DISCOVERY RESULT ===\n");
|
||||
fprintf(stderr, "Total unique parallel inverters: %d\n", count);
|
||||
printf("PARALLEL_COUNT=%d\n", count);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
// Get parallel status for specific inverter
|
||||
string cInverter::GetParallelStatus(int inverter_num) {
|
||||
char cmd[16];
|
||||
snprintf(cmd, sizeof(cmd), "QPGS%d", inverter_num);
|
||||
|
||||
if (query(cmd, 200)) {
|
||||
if (buf[0] == '(' && buf[1] != 'N') {
|
||||
// Return data without leading '('
|
||||
return string((char*)buf + 1);
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ class cInverter {
|
||||
int GetMode();
|
||||
void ExecuteCmd(const std::string cmd);
|
||||
void AutoDiscoverBufferSizes();
|
||||
int DiscoverParallelInverters(); // Returns number of parallel inverters
|
||||
string GetParallelStatus(int inverter_num); // Get QPGS data for specific inverter
|
||||
};
|
||||
|
||||
#endif // ___INVERTER_H
|
||||
|
||||
@@ -203,6 +203,12 @@ int main(int argc, char* argv[]) {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// Parallel inverter discovery mode
|
||||
if(cmdArgs.cmdOptionExists("-p") || cmdArgs.cmdOptionExists("--parallel-discovery")) {
|
||||
int count = ups->DiscoverParallelInverters();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// Logic to send 'raw commands' to the inverter..
|
||||
if (!rawcmd.empty()) {
|
||||
ups->ExecuteCmd(rawcmd);
|
||||
|
||||
Executable
+142
@@ -0,0 +1,142 @@
|
||||
#!/bin/bash
|
||||
# Test comunicazione con Voltronic Axpert MKS IV provando diversi baudrate
|
||||
# Il MKS IV potrebbe usare un baudrate diverso dal classico 2400
|
||||
|
||||
DEVICE="${1:-/dev/ttyUSB0}"
|
||||
|
||||
echo "=== Test Baudrate per Voltronic Axpert MKS IV su $DEVICE ==="
|
||||
echo ""
|
||||
|
||||
# Verifica device
|
||||
if [ ! -e "$DEVICE" ]; then
|
||||
echo "ERROR: Device $DEVICE non trovato!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Array di baudrate da testare
|
||||
# Il MKS IV potrebbe usare: 2400, 9600, 19200, 38400 o 115200
|
||||
BAUDRATES=(2400 9600 19200 38400 115200)
|
||||
|
||||
for BAUD in "${BAUDRATES[@]}"; do
|
||||
echo "============================================"
|
||||
echo "Testing BAUDRATE: $BAUD"
|
||||
echo "============================================"
|
||||
|
||||
# Configura device
|
||||
sudo stty -F $DEVICE $BAUD cs8 -cstopb -parenb -echo raw
|
||||
sudo chmod 666 $DEVICE
|
||||
|
||||
# Test con Python
|
||||
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)]
|
||||
|
||||
return crc.to_bytes(2, 'big')
|
||||
|
||||
try:
|
||||
ser = serial.Serial(
|
||||
port='$DEVICE',
|
||||
baudrate=$BAUD,
|
||||
bytesize=8,
|
||||
parity='N',
|
||||
stopbits=1,
|
||||
timeout=2
|
||||
)
|
||||
|
||||
print(f"Porta aperta a {$BAUD} baud")
|
||||
|
||||
# Flush buffers
|
||||
ser.reset_input_buffer()
|
||||
ser.reset_output_buffer()
|
||||
time.sleep(0.3)
|
||||
|
||||
# Test comando QMOD (semplice, 5 bytes di risposta)
|
||||
cmd = 'QMOD'
|
||||
print(f"Invio comando: {cmd}")
|
||||
|
||||
cmd_bytes = cmd.encode('ascii')
|
||||
crc = calc_crc(cmd_bytes)
|
||||
full_cmd = cmd_bytes + crc + b'\r'
|
||||
|
||||
print(f" Hex: {full_cmd.hex()}")
|
||||
|
||||
# Invia
|
||||
ser.write(full_cmd)
|
||||
ser.flush()
|
||||
time.sleep(0.5)
|
||||
|
||||
# Leggi risposta
|
||||
response = ser.read(200)
|
||||
|
||||
if len(response) > 0:
|
||||
print(f" [OK] RISPOSTA RICEVUTA ({len(response)} bytes)")
|
||||
print(f" Hex: {response.hex()}")
|
||||
try:
|
||||
ascii_text = response.decode('ascii', errors='replace')
|
||||
print(f" ASCII: {ascii_text.strip()}")
|
||||
|
||||
# Verifica se è una risposta valida (inizia con '(' e non è NAK)
|
||||
if response[0:1] == b'(' and b'NAK' not in response:
|
||||
print(f" *** BAUDRATE CORRETTO: {$BAUD} ***")
|
||||
sys.exit(0) # Success
|
||||
elif b'NAK' in response:
|
||||
print(f" [X] NAK ricevuto (inverter non comprende)")
|
||||
else:
|
||||
print(f" [X] Risposta non valida")
|
||||
except:
|
||||
print(f" [X] Risposta non decodificabile")
|
||||
else:
|
||||
print(f" [X] Nessuna risposta (timeout)")
|
||||
|
||||
ser.close()
|
||||
|
||||
except Exception as e:
|
||||
print(f" [X] Errore: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
PYTHON_EOF
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo ""
|
||||
echo "╔═══════════════════════════════════════════╗"
|
||||
echo "║ BAUDRATE CORRETTO TROVATO: $BAUD ║"
|
||||
echo "╚═══════════════════════════════════════════╝"
|
||||
echo ""
|
||||
echo "Aggiorna /etc/inverter/inverter.conf se necessario"
|
||||
echo "Modifica sources/inverter-cli/inverter.cpp:"
|
||||
echo " Cambia: speed_t baud = B$BAUD;"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "============================================"
|
||||
echo "NESSUN BAUDRATE FUNZIONANTE TROVATO"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
echo "Possibili cause:"
|
||||
echo "1. Inverter spento o disconnesso"
|
||||
echo "2. Cavo USB/RS232 difettoso"
|
||||
echo "3. Device errato (prova /dev/ttyUSB1 o /dev/hidraw0)"
|
||||
echo "4. Inverter in modalità incompatibile"
|
||||
echo ""
|
||||
Executable
+60
@@ -0,0 +1,60 @@
|
||||
#!/bin/bash
|
||||
# Test suite per comandi Voltronic Axpert MKS IV
|
||||
# Basato su documentazione forum AEVA e manuale protocollo
|
||||
|
||||
echo "╔══════════════════════════════════════════════════════╗"
|
||||
echo "║ TEST COMANDI PROTOCOLLO VOLTRONIC MKS IV ║"
|
||||
echo "╚══════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
test_command() {
|
||||
local cmd=$1
|
||||
local desc=$2
|
||||
echo -n "Testing $cmd ($desc)... "
|
||||
result=$(sudo ./bin/inverter_poller -r "$cmd" 2>&1 | grep "Reply:" | sed 's/Reply://g' | xargs)
|
||||
if [ -z "$result" ]; then
|
||||
echo "❌ NO RESPONSE"
|
||||
elif [ "$result" = "NAK" ]; then
|
||||
echo "❌ NAK (comando non supportato)"
|
||||
else
|
||||
echo "✅ $result"
|
||||
fi
|
||||
}
|
||||
|
||||
# Comandi che DOVREBBERO funzionare
|
||||
echo "=== COMANDI STANDARD P18 ==="
|
||||
test_command "QID" "Device Serial Number"
|
||||
test_command "QVFW" "Main CPU Firmware Version"
|
||||
test_command "QGMN" "General Model Name"
|
||||
test_command "QPI" "Protocol ID"
|
||||
test_command "QFLAG" "Device Flag Status"
|
||||
|
||||
echo ""
|
||||
echo "=== COMANDI STATUS AVANZATI ==="
|
||||
test_command "QPGS0" "Parallel General Status"
|
||||
test_command "QPGS1" "Parallel General Status #1"
|
||||
test_command "QDI" "Default Settings Inquiry"
|
||||
test_command "QMCHGCR" "Max Charging Current Options"
|
||||
test_command "QMUCHGCR" "Max Utility Charging Current"
|
||||
test_command "QOPPT" "Output Power Type"
|
||||
|
||||
echo ""
|
||||
echo "=== COMANDI BATTERIA ==="
|
||||
test_command "QBEQI" "Battery Equalization Info"
|
||||
test_command "QBMS" "BMS Info"
|
||||
|
||||
echo ""
|
||||
echo "=== COMANDI DIAGNOSTICI ==="
|
||||
test_command "QBOOT" "Bootloader Version"
|
||||
test_command "QET" "Total Generated Energy"
|
||||
test_command "QEY" "Generated Energy This Year"
|
||||
test_command "QEM" "Generated Energy This Month"
|
||||
test_command "QED" "Generated Energy Today"
|
||||
|
||||
echo ""
|
||||
echo "╔══════════════════════════════════════════════════════╗"
|
||||
echo "║ REPORT FINALE ║"
|
||||
echo "╚══════════════════════════════════════════════════════╝"
|
||||
echo "Comando FUNZIONANTE: QGMN (Model 054)"
|
||||
echo "Comandi STANDARD non funzionanti: QPIGS, QPIRI, QMOD, QPIWS"
|
||||
echo "Possibile causa: Protocollo proprietario MKS IV"
|
||||
@@ -0,0 +1,22 @@
|
||||
import serial
|
||||
import time
|
||||
|
||||
port = '/dev/ttyUSB1'
|
||||
ser = serial.Serial(port, 2400, bytesize=8, parity='N', stopbits=1, timeout=2)
|
||||
|
||||
# Costruisci comando QPIGS manualmente
|
||||
cmd = b'QPIGS'
|
||||
crc = 0xB7A9
|
||||
cmd_full = cmd + bytes([crc >> 8, crc & 0xFF, 0x0D])
|
||||
|
||||
print(f"Invio comando: {cmd_full.hex(' ')}")
|
||||
print(f"Lunghezza: {len(cmd_full)} bytes")
|
||||
|
||||
ser.write(cmd_full)
|
||||
time.sleep(0.5)
|
||||
|
||||
resp = ser.read(100)
|
||||
print(f"Ricevuto ({len(resp)} bytes): {resp.hex(' ')}")
|
||||
print(f"ASCII: {resp}")
|
||||
|
||||
ser.close()
|
||||
@@ -0,0 +1,26 @@
|
||||
import serial
|
||||
import time
|
||||
|
||||
port = '/dev/ttyUSB0'
|
||||
ser = serial.Serial(port, 2400, bytesize=8, parity='N', stopbits=1, timeout=2)
|
||||
|
||||
for cmd_str in ['QPIGS', 'QMOD', 'QGMN']:
|
||||
# CRC calcolati
|
||||
crcs = {'QPIGS': 0xB7A9, 'QMOD': 0x49C1, 'QGMN': 0x4928}
|
||||
|
||||
cmd = cmd_str.encode()
|
||||
crc = crcs[cmd_str]
|
||||
cmd_full = cmd + bytes([crc >> 8, crc & 0xFF, 0x0D])
|
||||
|
||||
print(f"\n=== {cmd_str} ===")
|
||||
print(f"Invio: {cmd_full.hex(' ')}")
|
||||
|
||||
ser.write(cmd_full)
|
||||
time.sleep(0.5)
|
||||
|
||||
resp = ser.read(200)
|
||||
print(f"Ricevuto ({len(resp)} bytes): {resp.hex(' ') if resp else '(nessuna risposta)'}")
|
||||
if resp:
|
||||
print(f"ASCII: {resp}")
|
||||
|
||||
ser.close()
|
||||
Executable
+140
@@ -0,0 +1,140 @@
|
||||
#!/bin/bash
|
||||
# MQTT Push for Parallel Inverters
|
||||
# Discovers parallel inverters and publishes data for each one separately
|
||||
|
||||
# Detect environment (container vs development)
|
||||
if [ -f "/etc/inverter/mqtt.json" ] && [ -x "/opt/inverter-cli/bin/inverter_poller" ]; then
|
||||
# Container mode
|
||||
MQTT_CONFIG="/etc/inverter/mqtt.json"
|
||||
INVERTER_BIN="/opt/inverter-cli/bin/inverter_poller"
|
||||
MQTT_FALLBACK="/opt/inverter-mqtt/mqtt-push.sh"
|
||||
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"
|
||||
CONTAINER_MODE=false
|
||||
fi
|
||||
|
||||
echo "Mode: $([ "$CONTAINER_MODE" = true ] && echo "Container" || echo "Development")"
|
||||
echo "Using binary: $INVERTER_BIN"
|
||||
|
||||
# Check if jq is installed
|
||||
if ! command -v jq &> /dev/null; then
|
||||
echo "ERROR: jq is not installed. Install it with: sudo apt-get install jq"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MQTT_SERVER=`cat $MQTT_CONFIG | jq '.server' -r`
|
||||
MQTT_PORT=`cat $MQTT_CONFIG | jq '.port' -r`
|
||||
MQTT_TOPIC=`cat $MQTT_CONFIG | jq '.topic' -r`
|
||||
MQTT_DEVICENAME=`cat $MQTT_CONFIG | jq '.devicename' -r`
|
||||
MQTT_USERNAME=`cat $MQTT_CONFIG | jq '.username' -r`
|
||||
MQTT_PASSWORD=`cat $MQTT_CONFIG | jq '.password' -r`
|
||||
MQTT_CLIENTID=`cat $MQTT_CONFIG | jq '.clientid' -r`
|
||||
INFLUX_ENABLED=`cat $MQTT_CONFIG | jq '.influx.enabled' -r`
|
||||
|
||||
pushMQTTData () {
|
||||
# $1 = inverter_id, $2 = metric, $3 = value
|
||||
local inverter_id=$1
|
||||
local metric=$2
|
||||
local value=$3
|
||||
|
||||
mosquitto_pub \
|
||||
-h $MQTT_SERVER \
|
||||
-p $MQTT_PORT \
|
||||
-u "$MQTT_USERNAME" \
|
||||
-P "$MQTT_PASSWORD" \
|
||||
-i $MQTT_CLIENTID \
|
||||
-r \
|
||||
-t "$MQTT_TOPIC/sensor/${MQTT_DEVICENAME}_inv${inverter_id}_${metric}" \
|
||||
-m "$value"
|
||||
|
||||
if [[ $INFLUX_ENABLED == "true" ]] ; then
|
||||
pushInfluxData $inverter_id $metric $value
|
||||
fi
|
||||
}
|
||||
|
||||
pushInfluxData () {
|
||||
INFLUX_HOST=`cat $MQTT_CONFIG | jq '.influx.host' -r`
|
||||
INFLUX_USERNAME=`cat $MQTT_CONFIG | jq '.influx.username' -r`
|
||||
INFLUX_PASSWORD=`cat $MQTT_CONFIG | jq '.influx.password' -r`
|
||||
INFLUX_DEVICE=`cat $MQTT_CONFIG | jq '.influx.device' -r`
|
||||
INFLUX_PREFIX=`cat $MQTT_CONFIG | jq '.influx.prefix' -r`
|
||||
INFLUX_DATABASE=`cat $MQTT_CONFIG | jq '.influx.database' -r`
|
||||
INFLUX_MEASUREMENT_NAME=`cat $MQTT_CONFIG | jq '.influx.namingMap.'$2'' -r`
|
||||
|
||||
curl -i -XPOST "$INFLUX_HOST/write?db=$INFLUX_DATABASE&precision=s" -u "$INFLUX_USERNAME:$INFLUX_PASSWORD" --data-binary "$INFLUX_PREFIX,device=${INFLUX_DEVICE}_inv${1} $INFLUX_MEASUREMENT_NAME=$3" > /dev/null 2>&1
|
||||
}
|
||||
|
||||
# Discover parallel inverters
|
||||
SUDO_CMD=""
|
||||
if [ "$EUID" -ne 0 ] && [ -c "/dev/ttyUSB0" ]; then
|
||||
SUDO_CMD="sudo"
|
||||
fi
|
||||
|
||||
PARALLEL_DISCOVERY=`$SUDO_CMD "$INVERTER_BIN" -p 2>&1`
|
||||
PARALLEL_COUNT=`echo "$PARALLEL_DISCOVERY" | grep "PARALLEL_COUNT=" | cut -d= -f2`
|
||||
|
||||
if [ -z "$PARALLEL_COUNT" ] || [ "$PARALLEL_COUNT" -eq 0 ]; then
|
||||
echo "No parallel inverters found (count=$PARALLEL_COUNT), using standard polling"
|
||||
# Don't use fallback in dev mode if file doesn't exist
|
||||
if [ -f "$MQTT_FALLBACK" ]; then
|
||||
exec $MQTT_FALLBACK
|
||||
else
|
||||
echo "Fallback script not found: $MQTT_FALLBACK"
|
||||
echo "Using standard inverter_poller -1 instead"
|
||||
INVERTER_DATA=`$SUDO_CMD "$INVERTER_BIN" -1 2>&1`
|
||||
echo "$INVERTER_DATA"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Found $PARALLEL_COUNT parallel inverters"
|
||||
|
||||
# Publish discovery info
|
||||
pushMQTTData "system" "parallel_count" "$PARALLEL_COUNT"
|
||||
|
||||
# Extract inverter serials and QPGS indices
|
||||
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`
|
||||
|
||||
echo "Processing Inverter #$i (Serial: $SERIAL, QPGS$QPGS_IDX)"
|
||||
|
||||
# Get QPGS data for this inverter
|
||||
QPGS_DATA=`$SUDO_CMD "$INVERTER_BIN" -r "QPGS$QPGS_IDX" 2>&1 | grep "Reply:" | cut -d: -f2- | xargs`
|
||||
|
||||
if [ ! -z "$QPGS_DATA" ] && [ "$QPGS_DATA" != "NAK" ]; then
|
||||
# Parse QPGS response format:
|
||||
# 1 SERIAL MODE STATUS GRID_V GRID_F OUT_V OUT_F VA W PCT BATT_V CHRG CAP PV_V CHRG_A ...
|
||||
|
||||
# Publish serial number
|
||||
pushMQTTData "$i" "serial" "$SERIAL"
|
||||
|
||||
# Parse and publish data (QPGS format parsing)
|
||||
IFS=' ' read -ra DATA <<< "$QPGS_DATA"
|
||||
|
||||
[ "${DATA[2]}" ] && pushMQTTData "$i" "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]}"
|
||||
|
||||
echo " ✓ Published data for inverter #$i"
|
||||
echo " Topics: ${MQTT_TOPIC}/sensor/${MQTT_DEVICENAME}_inv${i}_{serial,mode,Battery_voltage,Load_watt,...}"
|
||||
else
|
||||
echo " ✗ No valid data for inverter #$i"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Parallel MQTT push completed"
|
||||
Executable
+225
@@ -0,0 +1,225 @@
|
||||
#!/bin/bash
|
||||
# Test Loop - Simula esecuzione container con parallel discovery e MQTT push
|
||||
# Questo script simula il comportamento completo del container in produzione
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Detect environment
|
||||
if [ -f "/etc/inverter/mqtt.json" ] && [ -x "/opt/inverter-cli/bin/inverter_poller" ]; then
|
||||
# Container mode
|
||||
MQTT_CONFIG="/etc/inverter/mqtt.json"
|
||||
INVERTER_BIN="/opt/inverter-cli/bin/inverter_poller"
|
||||
INVERTER_CONFIG="/etc/inverter/inverter.conf"
|
||||
MQTT_PUSH_SCRIPT="/opt/inverter-mqtt/mqtt-push-parallel.sh"
|
||||
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"
|
||||
INVERTER_CONFIG="/home/pi/Progetti/config/inverter.conf"
|
||||
MQTT_PUSH_SCRIPT="/home/pi/Progetti/sources/inverter-mqtt/mqtt-push-parallel.sh"
|
||||
CONTAINER_MODE=false
|
||||
fi
|
||||
|
||||
SUDO_CMD=""
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
SUDO_CMD="sudo"
|
||||
fi
|
||||
|
||||
# Configuration
|
||||
LOOP_INTERVAL=${LOOP_INTERVAL:-30} # Seconds between iterations
|
||||
MAX_ITERATIONS=${MAX_ITERATIONS:-0} # 0 = infinite
|
||||
|
||||
echo -e "${BLUE}╔════════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${BLUE}║ VOLTRONIC PARALLEL INVERTER - TEST LOOP ║${NC}"
|
||||
echo -e "${BLUE}║ Simulates container execution with full discovery ║${NC}"
|
||||
echo -e "${BLUE}╚════════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Mode:${NC} $([ "$CONTAINER_MODE" = true ] && echo "Container" || echo "Development")"
|
||||
echo -e "${YELLOW}Binary:${NC} $INVERTER_BIN"
|
||||
echo -e "${YELLOW}MQTT Config:${NC} $MQTT_CONFIG"
|
||||
echo -e "${YELLOW}Loop Interval:${NC} ${LOOP_INTERVAL}s"
|
||||
echo -e "${YELLOW}Max Iterations:${NC} $([ $MAX_ITERATIONS -eq 0 ] && echo "∞ (infinite)" || echo "$MAX_ITERATIONS")"
|
||||
echo ""
|
||||
|
||||
# Check device configuration
|
||||
if [ -f "$INVERTER_CONFIG" ]; then
|
||||
USB_DEVICE=$(grep "^device=" "$INVERTER_CONFIG" | cut -d= -f2)
|
||||
echo -e "${YELLOW}USB Device:${NC} $USB_DEVICE"
|
||||
# Show USB info if available
|
||||
if [ -e "$USB_DEVICE" ]; then
|
||||
USB_SERIAL=$(udevadm info -q property -n "$USB_DEVICE" 2>/dev/null | grep "ID_SERIAL_SHORT=" | cut -d= -f2)
|
||||
[ ! -z "$USB_SERIAL" ] && echo -e "${YELLOW}USB Serial:${NC} $USB_SERIAL"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if MQTT server is configured
|
||||
if [ -f "$MQTT_CONFIG" ]; then
|
||||
MQTT_SERVER=$(cat "$MQTT_CONFIG" | jq -r '.server' 2>/dev/null || echo "not configured")
|
||||
MQTT_PORT=$(cat "$MQTT_CONFIG" | jq -r '.port' 2>/dev/null || echo "1883")
|
||||
MQTT_TOPIC=$(cat "$MQTT_CONFIG" | jq -r '.topic' 2>/dev/null || echo "homeassistant")
|
||||
MQTT_DEVICE=$(cat "$MQTT_CONFIG" | jq -r '.devicename' 2>/dev/null || echo "voltronic")
|
||||
echo -e "${YELLOW}MQTT Server:${NC} $MQTT_SERVER:$MQTT_PORT"
|
||||
echo -e "${YELLOW}MQTT Base Topic:${NC} $MQTT_TOPIC/sensor/$MQTT_DEVICE"
|
||||
else
|
||||
echo -e "${RED}✗ MQTT config not found: $MQTT_CONFIG${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
read -p "Press ENTER to start test loop (Ctrl+C to stop)..."
|
||||
echo ""
|
||||
|
||||
# Phase 1: Initial Discovery (run once at startup)
|
||||
echo -e "${BLUE}╔════════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${BLUE}║ PHASE 1: INITIAL DISCOVERY ║${NC}"
|
||||
echo -e "${BLUE}╚════════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}[1.1] Buffer Sizes Auto-Discovery${NC}"
|
||||
DISCOVERY_OUTPUT=$($SUDO_CMD "$INVERTER_BIN" -a 2>&1)
|
||||
DISCOVERY_SUCCESS=$(echo "$DISCOVERY_OUTPUT" | grep "DISCOVERY_SUCCESS=" | cut -d= -f2)
|
||||
|
||||
if [ "$DISCOVERY_SUCCESS" = "true" ]; then
|
||||
echo -e "${GREEN}✓ Buffer sizes discovered successfully${NC}"
|
||||
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)
|
||||
|
||||
echo " • QMOD: $QMOD bytes"
|
||||
echo " • QPIGS: $QPIGS bytes"
|
||||
echo " • QPIRI: $QPIRI bytes"
|
||||
echo " • QPIWS: $QPIWS bytes"
|
||||
else
|
||||
echo -e "${RED}✗ Buffer discovery failed, using defaults${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}[1.2] Parallel Inverters Discovery${NC}"
|
||||
PARALLEL_OUTPUT=$($SUDO_CMD "$INVERTER_BIN" -p 2>&1)
|
||||
PARALLEL_COUNT=$(echo "$PARALLEL_OUTPUT" | grep "PARALLEL_COUNT=" | cut -d= -f2)
|
||||
|
||||
if [ -z "$PARALLEL_COUNT" ]; then
|
||||
PARALLEL_COUNT=0
|
||||
fi
|
||||
|
||||
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)"
|
||||
done
|
||||
else
|
||||
echo -e "${YELLOW}⚠ No parallel inverters found, using single mode${NC}"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Phase 2: Main Loop
|
||||
echo -e "${BLUE}╔════════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${BLUE}║ PHASE 2: MAIN POLLING LOOP ║${NC}"
|
||||
echo -e "${BLUE}╚════════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
|
||||
iteration=0
|
||||
while true; do
|
||||
iteration=$((iteration + 1))
|
||||
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
|
||||
echo -e "${BLUE}═══════════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${BLUE}Iteration #$iteration - $timestamp${NC}"
|
||||
echo -e "${BLUE}═══════════════════════════════════════════════════════════════${NC}"
|
||||
|
||||
# Test standard commands (local inverter)
|
||||
echo -e "${YELLOW}[2.1] Testing standard commands (local inverter)${NC}"
|
||||
for cmd in QPIGS QPIRI QMOD QPIWS; do
|
||||
result=$($SUDO_CMD "$INVERTER_BIN" -r $cmd 2>&1 | grep "Reply:" | cut -d: -f2- | xargs)
|
||||
if [ ! -z "$result" ] && [ "$result" != "NAK" ]; then
|
||||
echo -e " ${GREEN}✓${NC} $cmd: OK (${#result} chars)"
|
||||
else
|
||||
echo -e " ${RED}✗${NC} $cmd: FAIL ($result)"
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
|
||||
# Get parallel data
|
||||
if [ $PARALLEL_COUNT -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_DATA=$($SUDO_CMD "$INVERTER_BIN" -r "QPGS$QPGS_IDX" 2>&1 | grep "Reply:" | cut -d: -f2- | xargs)
|
||||
|
||||
if [ ! -z "$QPGS_DATA" ] && [ "$QPGS_DATA" != "NAK" ]; then
|
||||
# Parse key values
|
||||
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"
|
||||
else
|
||||
echo -e " ${RED}✗${NC} Inverter #$i ($SERIAL): No data"
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# MQTT Push simulation
|
||||
echo -e "${YELLOW}[2.3] MQTT Push${NC}"
|
||||
if [ -x "$MQTT_PUSH_SCRIPT" ]; then
|
||||
echo " Running: $MQTT_PUSH_SCRIPT"
|
||||
echo " Publishing to: $MQTT_SERVER:$MQTT_PORT"
|
||||
MQTT_OUTPUT=$(bash "$MQTT_PUSH_SCRIPT" 2>&1 | tail -5)
|
||||
echo "$MQTT_OUTPUT" | sed 's/^/ /'
|
||||
|
||||
# Show sample topics published
|
||||
if [ $PARALLEL_COUNT -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)"
|
||||
done
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}✓ MQTT push completed${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠ MQTT script not executable: $MQTT_PUSH_SCRIPT${NC}"
|
||||
echo " Simulating MQTT publish..."
|
||||
if [ $PARALLEL_COUNT -gt 0 ]; then
|
||||
echo " • Published data for $PARALLEL_COUNT inverters"
|
||||
else
|
||||
echo " • Published data for 1 inverter (single mode)"
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Check iteration limit
|
||||
if [ $MAX_ITERATIONS -gt 0 ] && [ $iteration -ge $MAX_ITERATIONS ]; then
|
||||
echo -e "${GREEN}✓ Reached maximum iterations ($MAX_ITERATIONS)${NC}"
|
||||
break
|
||||
fi
|
||||
|
||||
# Wait for next iteration
|
||||
echo -e "${BLUE}Waiting ${LOOP_INTERVAL}s until next iteration...${NC}"
|
||||
echo ""
|
||||
sleep $LOOP_INTERVAL
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}╔════════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${GREEN}║ TEST LOOP COMPLETED ║${NC}"
|
||||
echo -e "${GREEN}║ Total iterations: $iteration ║${NC}"
|
||||
echo -e "${GREEN}╚════════════════════════════════════════════════════════════════╝${NC}"
|
||||
Reference in New Issue
Block a user