From 078544381eda34dc4212ab9621c3fd308f2d9f20 Mon Sep 17 00:00:00 2001 From: Pi Developer Date: Sun, 22 Feb 2026 15:02:22 +0100 Subject: [PATCH] refactor: Rimuove discovery, usa QPGS diretto in cascade mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - mqtt-push-parallel.sh: riscritto da 659 a 230 righe * Rimosse tutte le meccaniche di serial discovery (VALID_SERIALS, VALID_QPGS, PARALLEL_DISCOVERY, DIRECT_SERIALS, ecc.) * Ora query direte QPGS0→inv1, QPGS1→inv2 senza discovery preliminare * Numero inverter configurabile via CASCADE_COUNT env var (default 2) * Fix PV_in_watts: quando DATA[25]=0 (batteria carica) e SCC_OK e no AC grid (line_loss=1), usa Load_watt come proxy di produzione PV * Aggiunto publish di PV_in_watthour e Load_watthour * Aggiunto parsing corretto dei status flags dal byte di stato QPGS - entrypoint.sh: riscritto da 240 a 131 righe * Rimossa intera logica di auto-discovery buffer sizes * Rimossi FORCE_DISCOVERY, SKIP_DISCOVERY, DISCOVERY_FLAG * Rimossi run_discovery() e update_config_with_discovery() * Startup immediato senza attese/tentativi di discovery * CASCADE_COUNT env var propagata agli script - healthcheck: corretto per processi effettivi del container * Prima: cercava mqtt-subscriber + mosquitto_sub + watch (3 proc) * watch non viene mai avviato → sempre unhealthy * Ora: controlla mosquitto_sub (subscriber) + sleep (push loop cicla) --- sources/healthcheck | 8 +- sources/inverter-mqtt/entrypoint.sh | 262 ++------ sources/inverter-mqtt/mqtt-push-parallel.sh | 708 ++++---------------- 3 files changed, 195 insertions(+), 783 deletions(-) diff --git a/sources/healthcheck b/sources/healthcheck index 476093f..1444eef 100755 --- a/sources/healthcheck +++ b/sources/healthcheck @@ -1,8 +1,12 @@ #!/bin/bash -PROC=`ps cax | grep -E "mqtt-subscriber|mosquitto_sub|watch" | awk '{print $5}' | sort -u | wc -l` +# Check that the MQTT subscriber (mosquitto_sub) and the main push loop (sleep 30) +# are both running. The subscriber handles incoming commands from Home Assistant, +# the sleep 30 indicates the push loop is actively cycling. +SUBSCRIBER=`ps ax | grep "mosquitto_sub" | grep -v grep | wc -l` +LOOP=`ps ax | grep "sleep 30" | grep -v grep | wc -l` -if [ "$PROC" -eq "3" ] ; then +if [ "$SUBSCRIBER" -ge "1" ] && [ "$LOOP" -ge "1" ] ; then exit 0 else exit 99 diff --git a/sources/inverter-mqtt/entrypoint.sh b/sources/inverter-mqtt/entrypoint.sh index b8148bf..0c888c4 100755 --- a/sources/inverter-mqtt/entrypoint.sh +++ b/sources/inverter-mqtt/entrypoint.sh @@ -2,20 +2,27 @@ export TERM=xterm echo "=== Voltronic MQTT Bridge Starting ===" -echo "Version: 2.0 with Auto-Discovery" +echo "Version: 2.0 - Cascade Mode" echo "" # Configuration paths CONF_FILE="/etc/inverter/inverter.conf" MQTT_CONF="/etc/inverter/mqtt.json" -DISCOVERY_FLAG="/etc/inverter/.discovery_done" -TEMP_CONF="/tmp/inverter_discovered.conf" -# Ensure config files exist (copy defaults if missing due to empty volume mount) +# Environment variables with defaults +INVERTER_DEVICE="${INVERTER_DEVICE:-/dev/ttyUSB0}" +MQTT_SERVER="${MQTT_SERVER:-192.168.1.37}" +MQTT_PORT="${MQTT_PORT:-1883}" +MQTT_TOPIC="${MQTT_TOPIC:-homeassistant}" +MQTT_DEVICENAME="${MQTT_DEVICENAME:-voltronic}" +MQTT_USERNAME="${MQTT_USERNAME:-}" +MQTT_PASSWORD="${MQTT_PASSWORD:-}" +CASCADE_COUNT="${CASCADE_COUNT:-2}" + +# ── Ensure config files exist ───────────────────────────────────────────────── if [ ! -f "$CONF_FILE" ]; then echo "⚠ inverter.conf not found, creating default..." cat > "$CONF_FILE" << 'EOF' -# Basic configuration options for the actual inverter polling process... device=/dev/ttyUSB0 run_interval=120 amperage_factor=1.0 @@ -45,42 +52,21 @@ if [ ! -f "$MQTT_CONF" ]; then EOF fi -# Environment variables with defaults -INVERTER_DEVICE="${INVERTER_DEVICE:-/dev/ttyUSB0}" -FORCE_DISCOVERY="${FORCE_DISCOVERY:-false}" -SKIP_DISCOVERY="${SKIP_DISCOVERY:-false}" -MQTT_SERVER="${MQTT_SERVER:-192.168.1.37}" -MQTT_PORT="${MQTT_PORT:-1883}" -MQTT_TOPIC="${MQTT_TOPIC:-homeassistant}" -MQTT_DEVICENAME="${MQTT_DEVICENAME:-voltronic}" -MQTT_USERNAME="${MQTT_USERNAME:-}" -MQTT_PASSWORD="${MQTT_PASSWORD:-}" +# ── Apply device path to config ─────────────────────────────────────────────── +sed -i "s|^device=.*|device=$INVERTER_DEVICE|g" "$CONF_FILE" echo "Configuration:" -echo " Device: $INVERTER_DEVICE" -echo " Force Discovery: $FORCE_DISCOVERY" -echo " Skip Discovery: $SKIP_DISCOVERY" -echo " MQTT Server: $MQTT_SERVER:$MQTT_PORT" -echo " MQTT Topic: $MQTT_TOPIC" -echo " MQTT Device: $MQTT_DEVICENAME" +echo " Device: $INVERTER_DEVICE" +echo " Cascade count: $CASCADE_COUNT inverters" +echo " MQTT Server: $MQTT_SERVER:$MQTT_PORT" +echo " MQTT Topic: $MQTT_TOPIC" +echo " MQTT Device: $MQTT_DEVICENAME" echo "" -# Function to update MQTT configuration -update_mqtt_config() { - local mqtt_conf="/etc/inverter/mqtt.json" - - # Solo aggiorna se le ENV variables sono impostate - if [ -z "$MQTT_USERNAME" ] && [ -z "$MQTT_PASSWORD" ]; then - echo "ℹ MQTT credentials not provided via ENV, using values from mqtt.json" - return 0 - fi - +# ── Update MQTT config from ENV (only when credentials are provided) ────────── +if [ ! -z "$MQTT_USERNAME" ] || [ ! -z "$MQTT_PASSWORD" ]; then echo "Updating MQTT configuration from environment variables..." - - # Backup original - cp $mqtt_conf ${mqtt_conf}.backup 2>/dev/null || true - - # Update MQTT settings using jq + cp $MQTT_CONF ${MQTT_CONF}.backup 2>/dev/null || true jq --arg server "$MQTT_SERVER" \ --arg port "$MQTT_PORT" \ --arg topic "$MQTT_TOPIC" \ @@ -88,208 +74,58 @@ update_mqtt_config() { --arg username "$MQTT_USERNAME" \ --arg password "$MQTT_PASSWORD" \ '.server = $server | .port = $port | .topic = $topic | .devicename = $devicename | .username = $username | .password = $password' \ - $mqtt_conf > ${mqtt_conf}.tmp && mv ${mqtt_conf}.tmp $mqtt_conf - + $MQTT_CONF > ${MQTT_CONF}.tmp && mv ${MQTT_CONF}.tmp $MQTT_CONF echo "✓ MQTT configuration updated" - echo " Server: $MQTT_SERVER:$MQTT_PORT" - echo " Topic: $MQTT_TOPIC/sensor/$MQTT_DEVICENAME" - [ ! -z "$MQTT_USERNAME" ] && echo " Auth: Enabled (username: $MQTT_USERNAME)" - - # Verifica che le credenziali siano state scritte - local check_user=$(jq -r '.username' $mqtt_conf) - local check_pass=$(jq -r '.password' $mqtt_conf) - if [ -z "$check_user" ] || [ -z "$check_pass" ]; then - echo "⚠ WARNING: MQTT credentials were not properly written to config file!" - fi echo "" -} - -# Update MQTT config from ENV on startup -update_mqtt_config - -# 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 /opt/inverter-cli/bin/inverter_poller -a" - fi -fi +# ── Wait for serial device ──────────────────────────────────────────────────── +echo "Waiting for device $INVERTER_DEVICE to be ready..." +sleep 3 + +# ── Clean up legacy single-inverter MQTT topics ─────────────────────────────── +_MQTT_SERVER=$(jq -r '.server' $MQTT_CONF) +_MQTT_PORT=$(jq -r '.port' $MQTT_CONF) +_MQTT_USER=$(jq -r '.username' $MQTT_CONF) +_MQTT_PASS=$(jq -r '.password' $MQTT_CONF) +_MQTT_DEVICENAME=$(jq -r '.devicename' $MQTT_CONF) +_MQTT_TOPIC=$(jq -r '.topic' $MQTT_CONF) + +mosquitto_pub -h $_MQTT_SERVER -p $_MQTT_PORT -u "$_MQTT_USER" -P "$_MQTT_PASS" \ + -t "$_MQTT_TOPIC/sensor/$_MQTT_DEVICENAME/config" -n -r > /dev/null 2>&1 || true echo "" echo "=== Starting MQTT Bridge Services ===" -if [ -n "$INVERTER_DEVICES" ]; then - echo "Using multi-device mode (INVERTER_DEVICES=${INVERTER_DEVICES})" -else - echo "Using parallel inverter mode (2 inverters)" -fi +echo "Cascade mode: $CASCADE_COUNT inverters on $INVERTER_DEVICE" echo "" -# Wait a bit for the device to be ready -sleep 2 - -# Always use parallel scripts MQTT_PUSH_SCRIPT="/opt/inverter-mqtt/mqtt-push-parallel.sh" MQTT_INIT_SCRIPT="/opt/inverter-mqtt/mqtt-init-parallel.sh" -# Remove old single-inverter discovery topics (legacy cleanup) -echo "Cleaning up legacy MQTT topics..." -MQTT_SERVER=$(jq -r '.server' /etc/inverter/mqtt.json) -MQTT_PORT=$(jq -r '.port' /etc/inverter/mqtt.json) -MQTT_USERNAME=$(jq -r '.username' /etc/inverter/mqtt.json) -MQTT_PASSWORD=$(jq -r '.password' /etc/inverter/mqtt.json) -MQTT_DEVICENAME=$(jq -r '.devicename' /etc/inverter/mqtt.json) - -mosquitto_pub -h $MQTT_SERVER -p $MQTT_PORT -u "$MQTT_USERNAME" -P "$MQTT_PASSWORD" \ - -t "$MQTT_TOPIC/sensor/$MQTT_DEVICENAME/config" -n -r > /dev/null 2>&1 - -echo "✓ Legacy topics cleaned" - -# Run MQTT initialization immediately on startup -echo "Initializing MQTT discovery topics for both inverters..." -"$MQTT_INIT_SCRIPT" +# ── Initialize MQTT discovery topics ───────────────────────────────────────── +echo "Initializing MQTT discovery topics..." +CASCADE_COUNT="$CASCADE_COUNT" "$MQTT_INIT_SCRIPT" 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)..." +# ── Periodic MQTT init (re-register topics every 5 min for HA restarts) ────── ( while true; do - "$MQTT_INIT_SCRIPT" > /dev/null 2>&1 sleep 300 + CASCADE_COUNT="$CASCADE_COUNT" "$MQTT_INIT_SCRIPT" > /dev/null 2>&1 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..." +# ── MQTT subscriber (listen for commands from Home Assistant) ───────────────── +echo "Starting MQTT command subscriber..." /opt/inverter-mqtt/mqtt-subscriber.sh > /dev/null 2>&1 & -# execute exactly every 30 seconds... -echo "Starting MQTT data push service (every 30 seconds)..." +# ── Main data push loop (every 30 seconds) ──────────────────────────────────── +echo "Starting data push loop (every 30s)..." echo "" -echo "✓ All services started successfully!" -echo " Logs will appear below..." +echo "✓ All services started. Logs appear below..." echo "" while true; do - "$MQTT_PUSH_SCRIPT" > /dev/null 2>&1 + CASCADE_COUNT="$CASCADE_COUNT" "$MQTT_PUSH_SCRIPT" > /dev/null 2>&1 sleep 30 done diff --git a/sources/inverter-mqtt/mqtt-push-parallel.sh b/sources/inverter-mqtt/mqtt-push-parallel.sh index 0dd0ab9..49b513f 100755 --- a/sources/inverter-mqtt/mqtt-push-parallel.sh +++ b/sources/inverter-mqtt/mqtt-push-parallel.sh @@ -1,30 +1,21 @@ #!/bin/bash -# MQTT Push for Parallel Inverters -# Discovers parallel inverters and publishes data for each one separately +# MQTT Push for Parallel/Cascade Inverters +# Queries QPGS0 (inv1) and QPGS1 (inv2) directly - no serial discovery. +# Intended for inverters connected in cascade on the same RS232 bus. # 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" 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 -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" + echo "ERROR: jq is not installed. Install with: sudo apt-get install jq" exit 1 fi @@ -37,12 +28,15 @@ 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` +# Number of cascade inverters (default 2, override with CASCADE_COUNT env var) +CASCADE_COUNT="${CASCADE_COUNT:-2}" + pushMQTTData () { - # $1 = inverter_id, $2 = metric, $3 = value + # $1 = inverter_id (1, 2, or "system"), $2 = metric, $3 = value local inverter_id=$1 local metric=$2 local value=$3 - + mosquitto_pub \ -h $MQTT_SERVER \ -p $MQTT_PORT \ @@ -52,8 +46,8 @@ pushMQTTData () { -r \ -t "$MQTT_TOPIC/sensor/${MQTT_DEVICENAME}_inv${inverter_id}_${metric}" \ -m "$value" - - if [[ $INFLUX_ENABLED == "true" ]] ; then + + if [[ $INFLUX_ENABLED == "true" ]]; then pushInfluxData $inverter_id $metric $value fi } @@ -66,263 +60,127 @@ pushInfluxData () { 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 + + 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 } -# Extract all 33 parameters from JSON (like original mqtt-push.sh) -extractAndPublishAllData () { +# Process QPGS data and publish to MQTT for one inverter. +# Arguments: $1=inv_id, $2=QPGS_RAW string +# Returns 0 on success, 1 on failure. +processInverter () { local inv_id=$1 - local json_data=$2 - - # Extract all 33 parameters exactly like original script - Inverter_mode=`echo "$json_data" | jq '.Inverter_mode' -r` - [ ! -z "$Inverter_mode" ] && [ "$Inverter_mode" != "null" ] && pushMQTTData "$inv_id" "Inverter_mode" "$Inverter_mode" - - AC_grid_voltage=`echo "$json_data" | jq '.AC_grid_voltage' -r` - [ ! -z "$AC_grid_voltage" ] && [ "$AC_grid_voltage" != "null" ] && pushMQTTData "$inv_id" "AC_grid_voltage" "$AC_grid_voltage" - - AC_grid_frequency=`echo "$json_data" | jq '.AC_grid_frequency' -r` - [ ! -z "$AC_grid_frequency" ] && [ "$AC_grid_frequency" != "null" ] && pushMQTTData "$inv_id" "AC_grid_frequency" "$AC_grid_frequency" - - AC_out_voltage=`echo "$json_data" | jq '.AC_out_voltage' -r` - [ ! -z "$AC_out_voltage" ] && [ "$AC_out_voltage" != "null" ] && pushMQTTData "$inv_id" "AC_out_voltage" "$AC_out_voltage" - - AC_out_frequency=`echo "$json_data" | jq '.AC_out_frequency' -r` - [ ! -z "$AC_out_frequency" ] && [ "$AC_out_frequency" != "null" ] && pushMQTTData "$inv_id" "AC_out_frequency" "$AC_out_frequency" - - PV_in_voltage=`echo "$json_data" | jq '.PV_in_voltage' -r` - [ ! -z "$PV_in_voltage" ] && [ "$PV_in_voltage" != "null" ] && pushMQTTData "$inv_id" "PV_in_voltage" "$PV_in_voltage" - - PV_in_current=`echo "$json_data" | jq '.PV_in_current' -r` - [ ! -z "$PV_in_current" ] && [ "$PV_in_current" != "null" ] && pushMQTTData "$inv_id" "PV_in_current" "$PV_in_current" - - PV_in_watts=`echo "$json_data" | jq '.PV_in_watts' -r` - [ ! -z "$PV_in_watts" ] && [ "$PV_in_watts" != "null" ] && pushMQTTData "$inv_id" "PV_in_watts" "$PV_in_watts" - - PV_in_watthour=`echo "$json_data" | jq '.PV_in_watthour' -r` - [ ! -z "$PV_in_watthour" ] && [ "$PV_in_watthour" != "null" ] && pushMQTTData "$inv_id" "PV_in_watthour" "$PV_in_watthour" - - SCC_voltage=`echo "$json_data" | jq '.SCC_voltage' -r` - [ ! -z "$SCC_voltage" ] && [ "$SCC_voltage" != "null" ] && pushMQTTData "$inv_id" "SCC_voltage" "$SCC_voltage" - - Load_pct=`echo "$json_data" | jq '.Load_pct' -r` - [ ! -z "$Load_pct" ] && [ "$Load_pct" != "null" ] && pushMQTTData "$inv_id" "Load_pct" "$Load_pct" - - Load_watt=`echo "$json_data" | jq '.Load_watt' -r` - [ ! -z "$Load_watt" ] && [ "$Load_watt" != "null" ] && pushMQTTData "$inv_id" "Load_watt" "$Load_watt" - - Load_watthour=`echo "$json_data" | jq '.Load_watthour' -r` - [ ! -z "$Load_watthour" ] && [ "$Load_watthour" != "null" ] && pushMQTTData "$inv_id" "Load_watthour" "$Load_watthour" - - Load_va=`echo "$json_data" | jq '.Load_va' -r` - [ ! -z "$Load_va" ] && [ "$Load_va" != "null" ] && pushMQTTData "$inv_id" "Load_va" "$Load_va" - - Bus_voltage=`echo "$json_data" | jq '.Bus_voltage' -r` - [ ! -z "$Bus_voltage" ] && [ "$Bus_voltage" != "null" ] && pushMQTTData "$inv_id" "Bus_voltage" "$Bus_voltage" - - Heatsink_temperature=`echo "$json_data" | jq '.Heatsink_temperature' -r` - [ ! -z "$Heatsink_temperature" ] && [ "$Heatsink_temperature" != "null" ] && pushMQTTData "$inv_id" "Heatsink_temperature" "$Heatsink_temperature" - - Battery_capacity=`echo "$json_data" | jq '.Battery_capacity' -r` - [ ! -z "$Battery_capacity" ] && [ "$Battery_capacity" != "null" ] && pushMQTTData "$inv_id" "Battery_capacity" "$Battery_capacity" - - Battery_voltage=`echo "$json_data" | jq '.Battery_voltage' -r` - [ ! -z "$Battery_voltage" ] && [ "$Battery_voltage" != "null" ] && pushMQTTData "$inv_id" "Battery_voltage" "$Battery_voltage" - - Battery_charge_current=`echo "$json_data" | jq '.Battery_charge_current' -r` - [ ! -z "$Battery_charge_current" ] && [ "$Battery_charge_current" != "null" ] && pushMQTTData "$inv_id" "Battery_charge_current" "$Battery_charge_current" - - Battery_discharge_current=`echo "$json_data" | jq '.Battery_discharge_current' -r` - [ ! -z "$Battery_discharge_current" ] && [ "$Battery_discharge_current" != "null" ] && pushMQTTData "$inv_id" "Battery_discharge_current" "$Battery_discharge_current" - - Load_status_on=`echo "$json_data" | jq '.Load_status_on' -r` - [ ! -z "$Load_status_on" ] && [ "$Load_status_on" != "null" ] && pushMQTTData "$inv_id" "Load_status_on" "$Load_status_on" - - SCC_charge_on=`echo "$json_data" | jq '.SCC_charge_on' -r` - [ ! -z "$SCC_charge_on" ] && [ "$SCC_charge_on" != "null" ] && pushMQTTData "$inv_id" "SCC_charge_on" "$SCC_charge_on" - - AC_charge_on=`echo "$json_data" | jq '.AC_charge_on' -r` - [ ! -z "$AC_charge_on" ] && [ "$AC_charge_on" != "null" ] && pushMQTTData "$inv_id" "AC_charge_on" "$AC_charge_on" - - Battery_recharge_voltage=`echo "$json_data" | jq '.Battery_recharge_voltage' -r` - [ ! -z "$Battery_recharge_voltage" ] && [ "$Battery_recharge_voltage" != "null" ] && pushMQTTData "$inv_id" "Battery_recharge_voltage" "$Battery_recharge_voltage" - - Battery_under_voltage=`echo "$json_data" | jq '.Battery_under_voltage' -r` - [ ! -z "$Battery_under_voltage" ] && [ "$Battery_under_voltage" != "null" ] && pushMQTTData "$inv_id" "Battery_under_voltage" "$Battery_under_voltage" - - Battery_bulk_voltage=`echo "$json_data" | jq '.Battery_bulk_voltage' -r` - [ ! -z "$Battery_bulk_voltage" ] && [ "$Battery_bulk_voltage" != "null" ] && pushMQTTData "$inv_id" "Battery_bulk_voltage" "$Battery_bulk_voltage" - - Battery_float_voltage=`echo "$json_data" | jq '.Battery_float_voltage' -r` - [ ! -z "$Battery_float_voltage" ] && [ "$Battery_float_voltage" != "null" ] && pushMQTTData "$inv_id" "Battery_float_voltage" "$Battery_float_voltage" - - Max_grid_charge_current=`echo "$json_data" | jq '.Max_grid_charge_current' -r` - [ ! -z "$Max_grid_charge_current" ] && [ "$Max_grid_charge_current" != "null" ] && pushMQTTData "$inv_id" "Max_grid_charge_current" "$Max_grid_charge_current" - - Max_charge_current=`echo "$json_data" | jq '.Max_charge_current' -r` - [ ! -z "$Max_charge_current" ] && [ "$Max_charge_current" != "null" ] && pushMQTTData "$inv_id" "Max_charge_current" "$Max_charge_current" - - Out_source_priority=`echo "$json_data" | jq '.Out_source_priority' -r` - [ ! -z "$Out_source_priority" ] && [ "$Out_source_priority" != "null" ] && pushMQTTData "$inv_id" "Out_source_priority" "$Out_source_priority" - - Charger_source_priority=`echo "$json_data" | jq '.Charger_source_priority' -r` - [ ! -z "$Charger_source_priority" ] && [ "$Charger_source_priority" != "null" ] && pushMQTTData "$inv_id" "Charger_source_priority" "$Charger_source_priority" - - Battery_redischarge_voltage=`echo "$json_data" | jq '.Battery_redischarge_voltage' -r` - [ ! -z "$Battery_redischarge_voltage" ] && [ "$Battery_redischarge_voltage" != "null" ] && pushMQTTData "$inv_id" "Battery_redischarge_voltage" "$Battery_redischarge_voltage" - - Warnings=`echo "$json_data" | jq '.Warnings' -r` - [ ! -z "$Warnings" ] && [ "$Warnings" != "null" ] && pushMQTTData "$inv_id" "Warnings" "$Warnings" + local QPGS_RAW=$2 + + if [ -z "$QPGS_RAW" ] || [ "$QPGS_RAW" = "NAK" ]; then + echo " ✗ inv$inv_id: no data (NAK or empty)" + return 1 + fi + + IFS=' ' read -ra DATA <<< "$QPGS_RAW" + + # QPGS field mapping (per protocol HS_MS_MSX_RS232_Protocol): + # 0=Exists 1=Serial 2=Mode 3=Fault + # 4=GridV 5=GridF 6=OutV 7=OutF + # 8=OutVA 9=OutW 10=LoadPct + # 11=BattV 12=BattChgA 13=BattCap + # 14=PVInputV 15=TotalChgA 16=TotalOutVA 17=TotalOutW 18=TotalOutPct + # 19=StatusByte(b7b6b5b4b3b2b1b0) 20=OutMode 21=ChgSourcePriority + # 22=MaxChgA 23=MaxChgRange 24=MaxAcChgA + # 25=PV_in_current 26=Batt_discharge_current + + [ "${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[25]}" ] && pushMQTTData "$inv_id" "PV_in_current" "${DATA[25]}" + [ "${DATA[26]}" ] && pushMQTTData "$inv_id" "Battery_discharge_current" "${DATA[26]}" + + # ─── PV_in_watts calculation ────────────────────────────────────────────── + # DATA[25] = SCC output current at battery-bus voltage (not at PV panel V). + # Source: original QPIGS comment "current going out to battery at battery voltage". + # Formula: Battery_voltage × DATA[25] = power delivered by SCC to battery. + # + # Edge case - battery fully charged (DATA[25] = 0): + # If STATUS b7=1 (SCC_OK) and STATUS b2=1 (line_loss = no AC grid), + # solar is feeding the load directly. Use Load_watt as proxy. + # If AC grid is present (line_loss=0), solar just floats battery → 0W is correct. + local BATT_V="${DATA[11]:-0}" + local SCC_A="${DATA[25]:-0}" + local LOAD_W="${DATA[9]:-0}" + local STATUS="${DATA[19]:-00000000}" + + # STATUS string b7b6b5b4b3b2b1b0 - each char is one bit: + # index 0=b7=SCC_OK, index 1=b6=AC_charge, index 2=b5=SCC_charge, + # index 5=b2=line_loss (1=no AC grid), index 6=b1=load_on + local SCC_OK="${STATUS:0:1}" + local LINE_LOSS="${STATUS:5:1}" + + local PV_WATTS + if awk -v v="$SCC_A" 'BEGIN{exit !(v+0 > 0)}' 2>/dev/null; then + # SCC delivering current to battery + PV_WATTS=$(echo "$BATT_V $SCC_A" | awk '{printf "%.1f", $1 * $2}') + elif [ "$SCC_OK" = "1" ] && [ "$LINE_LOSS" = "1" ]; then + # SCC OK, battery full, no AC grid → solar feeds load + PV_WATTS="$LOAD_W" + else + PV_WATTS="0.0" + fi + pushMQTTData "$inv_id" "PV_in_watts" "$PV_WATTS" + + # Watt-hours (approximated from polling interval = 30s = 1/120 hour) + local PV_WH=$(echo "$PV_WATTS" | awk '{printf "%.4f", $1 / 120}') + pushMQTTData "$inv_id" "PV_in_watthour" "$PV_WH" + + local LOAD_WH=$(echo "$LOAD_W" | awk '{printf "%.4f", $1 / 120}') + pushMQTTData "$inv_id" "Load_watthour" "$LOAD_WH" + + # Status flags from STATUS byte + if [ ${#STATUS} -ge 8 ]; then + pushMQTTData "$inv_id" "SCC_charge_on" "${STATUS:2:1}" # b5 + pushMQTTData "$inv_id" "AC_charge_on" "${STATUS:1:1}" # b6 + pushMQTTData "$inv_id" "Load_status_on" "${STATUS:6:1}" # b1 + fi + + echo " ✓ inv$inv_id: OK (PV_watts=${PV_WATTS}W, Load=${LOAD_W}W)" + return 0 } -# Discover parallel inverters with retry logic +# ─── Main ───────────────────────────────────────────────────────────────────── + SUDO_CMD="" -if [ "$EUID" -ne 0 ] && [ -c "/dev/ttyUSB0" ]; then +if [ "$EUID" -ne 0 ]; 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" +echo "=== Cascade Inverter MQTT Push ===" +echo "Inverter count: $CASCADE_COUNT" - 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=3 -RETRY_DELAY=2 -PARALLEL_COUNT=0 - -for attempt in $(seq 1 $MAX_RETRIES); do - echo "Parallel discovery attempt $attempt/$MAX_RETRIES..." - 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" -gt 0 ]; then - echo "✓ Found $PARALLEL_COUNT inverters" - break - else - echo "⚠ Discovery failed (count=$PARALLEL_COUNT)" - if [ $attempt -lt $MAX_RETRIES ]; then - echo " Waiting ${RETRY_DELAY}s before retry..." - sleep $RETRY_DELAY - fi - fi -done - -# If still no count or discovery failed, try direct QPGS commands -if [ -z "$PARALLEL_COUNT" ] || [ "$PARALLEL_COUNT" -eq 0 ]; then - echo "⚠ Discovery reports 0 inverters, trying direct QPGS0/QPGS1 commands..." - - 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 - - # Build fake discovery output - PARALLEL_DISCOVERY="INVERTER_1_SERIAL=unknown -INVERTER_1_QPGS=0 -INVERTER_2_SERIAL=unknown -INVERTER_2_QPGS=1" - 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 -INVERTER_1_QPGS=0 -INVERTER_2_SERIAL=unknown -INVERTER_2_QPGS=1" - break - else - echo "⚠ QPGS commands failed (attempt $attempt/3)" - sleep 1 - fi - done - - if [ "$PARALLEL_COUNT" -eq 0 ]; then - echo "✗ QPGS commands also failed" - fi -fi - -DISCOVERY_SERIALS=() -DISCOVERY_QPGS=() -MAX_QPGS_IDX=-1 - -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)..." +# ── Step 1: QPIRI (shared config, same for all cascade inverters) ───────────── QPIRI_RAW="" -for attempt in 1 2 3 4 5; do +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 - break - fi + [ ! -z "$QPIRI_RAW" ] && [ "$QPIRI_RAW" != "NAK" ] && break sleep 1 done -QPIRI_SUCCESS=false +BATT_RECHARGE="" BATT_UNDER="" BATT_BULK="" BATT_FLOAT="" +MAX_CHARGE_CURRENT="" MAX_GRID_CHARGE="" OUT_SOURCE_PRIORITY="" +CHARGER_SOURCE_PRIORITY="" BATT_REDISCHARGE="" + if [ ! -z "$QPIRI_RAW" ] && [ "$QPIRI_RAW" != "NAK" ]; then - echo "✓ QPIRI retrieved successfully" - QPIRI_SUCCESS=true - # Parse QPIRI: Grid rating, Grid rating, AC output rating, AC output frequency, - # AC output current, AC output apparent power, AC output active power, - # Battery rating, Battery recharge, Battery under, Battery bulk, Battery float, - # Battery type, Max charge current, Max AC charge current, Input voltage range, - # Output source priority, Charger source priority, Parallel max num, Machine type, - # Topology, Output mode, Battery redischarge, PV OK, PV power balance + echo "✓ QPIRI retrieved" IFS=' ' read -ra QPIRI <<< "$QPIRI_RAW" - - # Extract configuration values (same for all inverters) BATT_RECHARGE="${QPIRI[8]}" BATT_UNDER="${QPIRI[9]}" BATT_BULK="${QPIRI[10]}" @@ -333,326 +191,40 @@ if [ ! -z "$QPIRI_RAW" ] && [ "$QPIRI_RAW" != "NAK" ]; then CHARGER_SOURCE_PRIORITY="${QPIRI[16]}" BATT_REDISCHARGE="${QPIRI[22]}" else - echo "⚠ QPIRI failed, configuration parameters unavailable" + echo "⚠ QPIRI failed - config parameters unavailable" fi -# Extract runtime data for each inverter using QPGS (single session) -PARALLEL_SUCCESS=false +# ── Step 2: QPGS per each inverter ─────────────────────────────────────────── +SUCCESS_IDS=() -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 +for inv_id in $(seq 1 $CASCADE_COUNT); do + qpgs_idx=$((inv_id - 1)) + echo "Querying QPGS${qpgs_idx} → inverter #${inv_id}..." -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 + QPGS_RAW="" 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 + QPGS_RAW=`$SUDO_CMD "$INVERTER_BIN" -r "QPGS${qpgs_idx}" 2>&1 | grep "Reply:" | cut -d: -f2- | xargs` + [ ! -z "$QPGS_RAW" ] && [ "$QPGS_RAW" != "NAK" ] && break + sleep 0.5 done -fi -if [ ${#VALID_SERIALS[@]} -eq 0 ]; then - VALID_SERIALS=("${DISCOVERY_SERIALS[@]}") - VALID_QPGS=("${DISCOVERY_QPGS[@]}") -fi + if processInverter "$inv_id" "$QPGS_RAW"; then + # Publish shared QPIRI config for this inverter + [ ! -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" -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 #$inv_id (Serial: $SERIAL, QPGS$QPGS_IDX)" - - # 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" - PARALLEL_SUCCESS=true - - # Parse QPGS response - IFS=' ' read -ra DATA <<< "$QPGS_RAW" - - # Field mapping per protocol (QPGSn): - # 0=Exists, 1=Serial, 2=Mode, 3=Fault, 4=GridV, 5=GridF, 6=OutV, 7=OutF - # 8=OutVA, 9=OutW, 10=LoadPct, 11=BattV, 12=BattChgA, 13=BattCap - # 14=PVV, 15=TotalChgA, 16=TotalOutVA, 17=TotalOutW, 18=TotalOutPct - # 19=StatusByte, 20=OutMode, 21=ChgSourcePriority, 22=MaxChgA - # 23=MaxChgRange, 24=MaxAcChgA, 25=PV_in_current, 26=Batt_discharge_current - - # 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 "$inv_id" "serial" "$SERIAL" - - # Runtime data from QPGS (19 parameters) - [ "${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[25]}" ] && pushMQTTData "$inv_id" "PV_in_current" "${DATA[25]}" - - # Calculate PV watts using battery voltage (DATA[11]) * SCC output current (DATA[25]). - # NOTE: Like QPIGS, DATA[25] ("PV Input Current") is actually the current going to the - # battery FROM the solar charge controller at battery voltage (NOT at PV panel voltage). - # Using PV voltage (DATA[14]) here would give a value ~4-5x too high. - # This mirrors the QPIGS formula: pv_input_watts = scc_voltage * pv_input_current - if [ ! -z "${DATA[11]}" ] && [ ! -z "${DATA[25]}" ]; then - PV_WATTS=`echo "${DATA[11]} ${DATA[25]}" | awk '{printf "%.1f", $1 * $2}'` - pushMQTTData "$inv_id" "PV_in_watts" "$PV_WATTS" - fi - - [ "${DATA[26]}" ] && pushMQTTData "$inv_id" "Battery_discharge_current" "${DATA[26]}" - - # Status flags (parse from field 18 bitmap if available) - [ "${DATA[18]}" ] && { - BITMAP="${DATA[18]}" - 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 "$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 #$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)" + SUCCESS_IDS+=("$inv_id") fi done -# Publish shared configuration parameters for ALL inverters (replicate QPIRI data) -if [ "$QPIRI_SUCCESS" = true ] && [ ${#SUCCESS_INV_IDS[@]} -gt 0 ]; then - echo "" - echo "Publishing shared configuration to all inverters..." +# Publish overall active inverter count +pushMQTTData "system" "parallel_count" "${#SUCCESS_IDS[@]}" - 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 -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 "" - echo "⚠ Parallel mode failed completely (QPGS not responding)" - echo " Possible reasons:" - echo " - Inverters not configured in parallel mode in firmware" - echo " - RS232 connection issue" - echo " - Inverters in standby or fault mode" - echo "" - echo " Falling back to standard single-inverter mode (QPIGS)..." - echo " Will publish data as 'inv1' (master inverter or aggregated data)" - - # Use standard inverter_poller -1 (QPIGS+QPIRI+QMOD+QPIWS) WITHOUT debug flag - INVERTER_DATA=`timeout 10 $SUDO_CMD "$INVERTER_BIN" -1 2>/dev/null` - - if [ ! -z "$INVERTER_DATA" ]; then - # Check if it's valid JSON - echo "$INVERTER_DATA" | jq . > /dev/null 2>&1 - if [ $? -eq 0 ]; then - echo " ✓ Standard mode data retrieved successfully" - extractAndPublishAllData "1" "$INVERTER_DATA" - echo " ✓ Published all 33 parameters for inverter #1" - echo "" - echo " NOTE: If you have 2 inverters in cascade:" - echo " - Enable parallel mode in inverter firmware settings" - echo " - OR connect second inverter to separate USB port" - echo " - Check manual for 'Machine in Parallel' configuration" - else - echo " ✗ Invalid JSON data from inverter_poller" - echo " Raw output: $INVERTER_DATA" - fi - else - echo " ✗ No data from inverter (timeout or device error)" - fi -fi - -echo "Parallel MQTT push completed" +echo "Push completed: ${#SUCCESS_IDS[@]}/$CASCADE_COUNT inverters OK"