874ece5529
- 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
305 lines
14 KiB
Bash
Executable File
305 lines
14 KiB
Bash
Executable File
#!/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"
|