Files
docker-voltronic-homeassistant/sources/inverter-mqtt/mqtt-push-parallel.sh
T
Pi Developer 874ece5529
Build Docker Image for Raspberry Pi / build-and-push (push) Successful in 13m15s
Docker Image Cleanup / cleanup-old-images (push) Waiting to run
fix: PV_in_watts tramite QPIGS DATA[19] per inv1 (MPPT diretto) + correzione efficienza η per inv2
- Aggiunto Step 1.5: query QPIGS prima del loop QPGS
- inv1 (master RS232): usa QPIGS DATA[19] = potenza PV misurata direttamente
  dal controller MPPT (campo aggiunto nel firmware 'after_current_upgrade')
- inv2 (slave): formula DC-bus balance divisa per η_total (SCC+inverter losses)
- η calcolato dinamicamente da QPIGS quando batteria ferma (<2A carica/scarica)
  altrimenti usa default 0.93 (~93.7% da misura reale su questo impianto)
- Corregge sottostima sistematica ~7% dovuta alle perdite DC→AC ignorate
2026-02-22 15:50:26 +01:00

305 lines
14 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# 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
MQTT_CONFIG="/etc/inverter/mqtt.json"
INVERTER_BIN="/opt/inverter-cli/bin/inverter_poller"
INVERTER_CONF="/etc/inverter/inverter.conf"
else
MQTT_CONFIG="/home/pi/Progetti/config/mqtt.json"
INVERTER_BIN="/home/pi/Progetti/sources/inverter-cli/bin/inverter_poller"
INVERTER_CONF="/home/pi/Progetti/config/inverter.conf"
fi
if ! command -v jq &> /dev/null; then
echo "ERROR: jq is not installed. Install 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`
# Number of cascade inverters (default 2, override with CASCADE_COUNT env var)
CASCADE_COUNT="${CASCADE_COUNT:-2}"
pushMQTTData () {
# $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 \
-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
}
# 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 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" "SCC_current" "${DATA[25]}"
[ "${DATA[26]}" ] && pushMQTTData "$inv_id" "Battery_discharge_current" "${DATA[26]}"
# ─── Real PV panel power calculation via DC-bus energy balance ────────────
# The QPGS protocol does NOT expose the true PV panel input current directly.
# DATA[25] = "PV input current for battery" = SCC output current to battery ONLY.
# This is 0 when battery is full even though panels are producing.
# DATA[26] = battery discharge current (at battery voltage).
# DATA[9] = AC output load watt (power drawn from DC bus by the inverter).
#
# The DC bus balance equation gives us the real SCC output power:
# SCC_out = Load_watt + Bat_charge Bat_discharge
# = Load + (V_batt × DATA[25]) (V_batt × DATA[26])
# When SCC is the sole DC source:
# P_pv ≈ SCC_out = V_batt×DATA[25] + max(0, Load V_batt×DATA[26])
#
# Guard: only calculate when SCC_charging bit (STATUS b5, index 2) = 1.
# When SCC is off, PV production = 0 regardless of field values.
local BATT_V="${DATA[11]:-0}"
local SCC_A="${DATA[25]:-0}"
local DISCH_A="${DATA[26]:-0}"
local PV_V="${DATA[14]:-0}"
local LOAD_W="${DATA[9]:-0}"
local STATUS="${DATA[19]:-00000000}"
# STATUS bit layout (b7b6b5b4b3b2b1b0 as string, index 0=b7):
# index 2 = b5 = SCC_charging (1 = SCC actively converting solar)
local SCC_CHARGING="${STATUS:2:1}"
local PV_WATTS PV_CURRENT
if [ "$SCC_CHARGING" = "1" ]; then
if [ "$inv_id" = "1" ] && awk -v v="$INV1_PV_DIRECT" 'BEGIN{exit !(v+0 > 0)}' 2>/dev/null; then
# inv1 (RS232 master): use QPIGS DATA[19] = direct PV watts from MPPT controller.
# This is the most accurate reading, bypassing DC-bus estimation entirely.
PV_WATTS=$(printf "%.1f" "$INV1_PV_DIRECT")
else
# inv2+ (slaves, no QPIGS): DC bus balance corrected for SCC+inverter losses.
# Without efficiency correction Load_W (AC) < P_PV (DC) by factor η_total ≈ 0.93.
# P_PV = (V_batt×SCC_A + max(0, Load_W V_batt×DISCH_A)) / η_total
local BATT_CHARGE_W=$(echo "$BATT_V $SCC_A" | awk '{printf "%.1f", $1 * $2}')
local BATT_DISCH_W=$(echo "$BATT_V $DISCH_A" | awk '{printf "%.1f", $1 * $2}')
local LOAD_FROM_SCC=$(echo "$LOAD_W $BATT_DISCH_W" | awk '{v=$1-$2; printf "%.1f", (v>0)?v:0}')
local PV_DC_EST=$(echo "$BATT_CHARGE_W $LOAD_FROM_SCC" | awk '{printf "%.1f", $1 + $2}')
PV_WATTS=$(echo "$PV_DC_EST $ETA_EFF" | awk '{printf "%.1f", $1 / $2}')
fi
if awk -v v="$PV_V" 'BEGIN{exit !(v+0 > 0)}' 2>/dev/null; then
PV_CURRENT=$(echo "$PV_WATTS $PV_V" | awk '{printf "%.2f", $1 / $2}')
else
PV_CURRENT="0.00"
fi
else
# SCC off: no solar conversion
PV_WATTS="0.0"
PV_CURRENT="0.00"
fi
pushMQTTData "$inv_id" "PV_in_current" "$PV_CURRENT"
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
}
# ─── Main ─────────────────────────────────────────────────────────────────────
SUDO_CMD=""
if [ "$EUID" -ne 0 ]; then
SUDO_CMD="sudo"
fi
echo "=== Cascade Inverter MQTT Push ==="
echo "Inverter count: $CASCADE_COUNT"
# ── Step 1: QPIRI (shared config, same for all cascade inverters) ─────────────
QPIRI_RAW=""
for attempt in 1 2 3; do
QPIRI_RAW=`$SUDO_CMD "$INVERTER_BIN" -r "QPIRI" 2>&1 | grep "Reply:" | cut -d: -f2- | xargs`
[ ! -z "$QPIRI_RAW" ] && [ "$QPIRI_RAW" != "NAK" ] && break
sleep 1
done
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"
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]}"
else
echo "⚠ QPIRI failed - config parameters unavailable"
fi
# ── Step 1.5: QPIGS (direct PV measurement from master/inv1) ─────────────────
# QPIGS DATA[19] = PV panel watts measured directly by the MPPT controller.
# This field was added in the firmware upgrade (present in HS_MS_MSX_RS232_Protocol_..._after_current_upgrade).
# QPIGS only returns data for the RS232 master (inv1); inv2 must use the formula.
# QPIGS status byte layout (differs from QPGS!): b7=SBU,b6=cfg,b5=SCC_fw,b4=load_on,b3=batt_steady,b2=charging,b1=SCC_on,b0=AC_on
INV1_PV_DIRECT=0
ETA_EFF="0.93" # combined SCC+inverter efficiency (default, overridden dynamically from QPIGS)
QPIGS_RAW=""
for attempt in 1 2 3; do
QPIGS_RAW=`$SUDO_CMD "$INVERTER_BIN" -r "QPIGS" 2>&1 | grep "Reply:" | cut -d: -f2- | xargs`
[ ! -z "$QPIGS_RAW" ] && [ "$QPIGS_RAW" != "NAK" ] && break
sleep 0.5
done
if [ ! -z "$QPIGS_RAW" ] && [ "$QPIGS_RAW" != "NAK" ]; then
echo "✓ QPIGS retrieved"
IFS=' ' read -ra QPIGS_F <<< "$QPIGS_RAW"
# QPIGS fields ("after_current_upgrade" firmware):
# [0]=GridV [1]=GridF [2]=OutV [3]=OutF [4]=OutVA [5]=OutW [6]=LoadPct
# [7]=BusV [8]=BattV [9]=BattChgA [10]=BattCap [11]=Temp
# [12]=PV_I_for_Batt [13]=PV_V1 [14]=BattV_SCC [15]=BattDischA [16]=Status
# [17-18]=reserved [19]=PV_input_watts (direct MPPT reading) [20]=unknown
QPIGS_STATUS="${QPIGS_F[16]:-00000000}"
QPIGS_SCC_ON="${QPIGS_STATUS:6:1}" # b1 = index 6 (b7=index 0) SCC on/off
QPIGS_PV_W="${QPIGS_F[19]:-0}"
QPIGS_LOAD="${QPIGS_F[5]:-0}"
QPIGS_BATT_CHG_I=$(echo "${QPIGS_F[9]:-0}" | awk '{printf "%d", $1+0}')
QPIGS_BATT_DCH_I=$(echo "${QPIGS_F[15]:-0}" | awk '{printf "%d", $1+0}')
# Direct PV reading for inv1
if [ "$QPIGS_SCC_ON" = "1" ] && awk -v v="$QPIGS_PV_W" 'BEGIN{exit !(v+0 > 0)}' 2>/dev/null; then
INV1_PV_DIRECT="$QPIGS_PV_W"
echo " QPIGS inv1: PV_direct=${QPIGS_PV_W}W Load=${QPIGS_LOAD}W SCC=ON"
fi
# Derive η when battery effects are negligible (batt charge <2A AND discharge <2A)
# η_total = Load_AC / PV_direct ≈ η_scc × η_inv
if [ "$QPIGS_BATT_CHG_I" -lt 2 ] && [ "$QPIGS_BATT_DCH_I" -lt 2 ] \
&& awk -v v="$QPIGS_PV_W" 'BEGIN{exit !(v+0 > 0)}' 2>/dev/null \
&& awk -v v="$QPIGS_LOAD" 'BEGIN{exit !(v+0 > 0)}' 2>/dev/null; then
ETA_EFF=$(echo "$QPIGS_LOAD $QPIGS_PV_W" | awk '{e=$1/$2; if(e<0.75)e=0.75; if(e>0.98)e=0.98; printf "%.4f",e}')
echo " η computed from QPIGS: $ETA_EFF (${QPIGS_LOAD}W AC / ${QPIGS_PV_W}W PV)"
else
echo " η default: $ETA_EFF (battery active or QPIGS PV=0)"
fi
else
echo "⚠ QPIGS failed - using default efficiency η=0.93"
fi
# ── Step 2: QPGS per each inverter ───────────────────────────────────────────
SUCCESS_IDS=()
for inv_id in $(seq 1 $CASCADE_COUNT); do
qpgs_idx=$((inv_id - 1))
echo "Querying QPGS${qpgs_idx} → inverter #${inv_id}..."
QPGS_RAW=""
for attempt in 1 2 3; do
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
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"
SUCCESS_IDS+=("$inv_id")
fi
done
# Publish overall active inverter count
pushMQTTData "system" "parallel_count" "${#SUCCESS_IDS[@]}"
echo "Push completed: ${#SUCCESS_IDS[@]}/$CASCADE_COUNT inverters OK"