Files
docker-voltronic-homeassistant/sources/inverter-mqtt/mqtt-push-parallel.sh
T
Pi Developer 74508bd330
Build Docker Image for Raspberry Pi / build-and-push (push) Has been cancelled
fix: PV_in_current ora è la corrente reale lato pannelli
Precedentemente DATA[25] (corrente SCC→batteria a tensione batteria)
veniva pubblicato direttamente come PV_in_current, dando valori errati.

Calcolo corretto via conservazione energia nell'SCC (DC-DC converter):
  P_pv   = V_batt × I_scc        (potenza SCC output = potenza pannelli)
  I_pv   = P_pv / V_pv           (corrente reale pannelli a tensione PV)
  P_pv   = V_pv × I_pv           (invariato - solo presentazione diversa)

Esempio con: V_pv=240V, V_batt=55V, I_scc=5A
  Prima: PV_in_current = 5A   (corrente SCC a 55V, errata)
  Dopo:  PV_in_current = 1.15A (corrente reale pannelli a 240V, corretta)
  PV_in_watts = 275W in entrambi i casi (matematicamente invariato)
2026-02-22 15:07:00 +01:00

250 lines
11 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[26]}" ] && pushMQTTData "$inv_id" "Battery_discharge_current" "${DATA[26]}"
# ─── PV panel-side power calculation ─────────────────────────────────────
# DATA[25] is labeled "PV input current" by the inverter but is actually
# the SCC *output* current flowing to the battery at battery voltage.
# The inverter protocol does NOT expose the real panel-side current directly.
#
# The SCC is a DC-DC converter: power_in ≈ power_out (ignoring ~5% losses).
# P_pv = V_batt × I_scc (SCC output power = panel power)
# I_pv = P_pv / V_pv (real panel current at panel voltage)
#
# Edge case - battery fully charged (DATA[25] = 0):
# If SCC_OK (b7=1) and line_loss (b2=1, no AC grid) → solar feeds load directly.
# Best proxy: Load_watt (solar production = what the load is consuming).
# If AC grid is present → solar floats battery, nothing meaningful to report.
local BATT_V="${DATA[11]:-0}"
local SCC_A="${DATA[25]:-0}"
local PV_V="${DATA[14]:-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 PV_CURRENT
if awk -v v="$SCC_A" 'BEGIN{exit !(v+0 > 0)}' 2>/dev/null; then
# SCC delivering current to battery: compute panel-side values
# P_pv = V_batt × I_scc
PV_WATTS=$(echo "$BATT_V $SCC_A" | awk '{printf "%.1f", $1 * $2}')
# I_pv = P_pv / V_pv (real current from panels at panel voltage)
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
elif [ "$SCC_OK" = "1" ] && [ "$LINE_LOSS" = "1" ]; then
# SCC OK, battery full, no AC grid → solar feeds load directly
PV_WATTS="$LOAD_W"
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
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 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"