Files
docker-voltronic-homeassistant/sources/inverter-mqtt/mqtt-push-parallel.sh
T
Pi Developer 94ac952644
Build Docker Image for Raspberry Pi / build-and-push (push) Successful in 12m47s
fix: PV_in_current/watts tramite bilancio energetico DC bus
Il protocollo QPGS non espone la corrente reale lato pannelli.
DATA[25] = corrente SCC→batteria SOLTANTO (0 quando batteria carica).

Formula corretta via conservazione energetica del DC bus:
  P_pv = V_batt×DATA[25] + max(0, Load_W − V_batt×DATA[26])

Dove:
  V_batt×DATA[25]  = potenza SCC inviata alla batteria
  Load_W           = potenza consumata dal carico dal bus DC
  V_batt×DATA[26]  = potenza fornita dalla batteria in scarica

Casi coperti:
  1. Bat. in carica (DATA[25]>0, DATA[26]=0):  P = V_b×I_scc + Load
  2. Bat. piena    (DATA[25]=0,  DATA[26]=0):  P = Load
  3. Bat. in scar. (DATA[25]=0,  DATA[26]>0):  P = max(0, Load−Pdisch)

Guard: calcolo solo quando SCC_charging (STATUS b5=1), altrimenti 0.

Aggiunto SCC_current (=DATA[25]) come campo separato per monitorare
la corrente SCC→batteria indipendentemente dalla produzione PV.

Aggiunto SCC_current al topic di discovery HA in mqtt-init-parallel.sh
2026-02-22 15:22:14 +01:00

247 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[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
# SCC is active: estimate total PV power via DC bus balance
# P_pv = V_batt × I_scc_to_battery + max(0, Load V_batt × I_bat_discharge)
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}')
PV_WATTS=$(echo "$BATT_CHARGE_W $LOAD_FROM_SCC" | awk '{printf "%.1f", $1 + $2}')
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 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"