#!/bin/sh
# Computes every few minutes heat-battery target top-up limit % from grid.
# A higher top-up limit also requests more eager and faster top-up.
# Note: at each of 2%/25%/50%/75% target fill, top-up becomes more aggressive.
# At 0% target fill no top-up will happen, even if 'level' becomes negative.


##########
# May be used / adapted / etc without any promise of fitness for purpose
# under the terms of the Apache License Version 2.0, January 2004
#     http://www.apache.org/licenses/LICENSE-2.0
##########


# Usage:
#     $0 [-dryrun] [-policy MAINDHW AGGRESSIVECARBON] [-dirs CONTROLPARAMSDIR GRIDINTENSITYDIR ELOGDIR DLOGDIR LOGDIR HTMLDIR] [-datetime YYYY-MM-ddTHH:mmZ]
#
# With -dryrun no file outputs are written,
# and the log record is echoed to stdout.
# All other INFO/WARNING/ERROR should be written to stderr.

# Atomically updates simple text file with % max grid top-up (or 0 for none).
# Writes a log of computed values and results.
# Atomically updates simple HTML file to stdout with the computation/stats.

# RUN script/test/heat-battery-target-tests.sh AFTER ANY SIGNIFICANT CHANGES.

# THIS LOGIC IS DRIVEN BY CARBON, FOR COMBI + HEAT-BATTERY + PV.
# Logic for eg heat-battery only or ToU-pricing would be simple to swap in.

# See NOTES, TODO and changelog at the end of the file.

dryrun="false"
if [ "-dryrun" = "$1" ]; then
    dryrun="true"
    echo INFO: dryrun 1>&2
    shift
fi

# If true, grid boost is disabled.  Usually false.
# Stats will continue to be generated.
#BOOSTDISABLED="false"
BOOSTDISABLED="true"

# If true, this is the main DHW source, not a backup.
# DHD20220923: combi is acting up, so have turned it off and this "true".
# DHD20221004: combi fixed again...
# DHD20231213: combi is acting up, so combi is off and this is "true"...
# DHD20240103: combi is fixed.
POLICYMAINDHW="false"
# If true, be more aggressive about carbon saving, even if it costs money.
POLICYAGGRESSIVECARBON="true"
# Override from command-line if requested.
#     [-policy MAINDHW AGGRESSIVECARBON
if [ "-policy" = "$1" ]; then
    if [ "true" = "$2" ]; then POLICYMAINDHW="true"; else POLICYMAINDHW="false"; fi
    if [ "true" = "$3" ]; then POLICYAGGRESSIVECARBON="true"; else POLICYAGGRESSIVECARBON="false"; fi
    echo INFO: policies: POLICYMAINDHW=$POLICYMAINDHW POLICYAGGRESSIVECARBON=$POLICYAGGRESSIVECARBON 1>&2
    shift
    shift
    shift
fi

# Soft parameters for energy management.
SOFTP=data/consolidated/softparams.txt
. ${SOFTP}

# Storage CoP threshold: default to 200.
STORECOPPC=${SOFTPMINSTORAGECOPPC-200}

# Directories for key I/O, that can be overridden on command-line eg for test.
# Control parameters.
CONTROLPARAMSDIR=data/heatBattery/16WWDHW
# Live computed grid intensity directory.
GRIDINTENSITYDIR=.
# Enphase AC Battery log directory.
ELOGDIR=/var/log/Enphase/
# Eddi diverter live log directory.
DLOGDIR=data/eddi/log/live
# Output live log directory.
LOGDIR=data/heatBattery/log/live
# Output HTML/txt file directory.
HTMLDIR=.
# Override from command-line if requested.
#    [-dirs CONTROLPARAMSDIR GRIDINTENSITYDIR ELOGDIR LOGDIR HTMLDIR]
if [ "-dirs" = "$1" ]; then
    CONTROLPARAMSDIR="$2"
    GRIDINTENSITYDIR="$3"
    ELOGDIR="$4"
    DLOGDIR="$5"
    LOGDIR="$6"
    HTMLDIR="$7"
    shift
    shift
    shift
    shift
    shift
    shift
    shift
fi
if [ ! -d "$CONTROLPARAMSDIR" ]; then
    echo "ERROR: no CONTROLPARAMSDIR $CONTROLPARAMSDIR" 1>&2
    exit 1
fi
if [ ! -d "$GRIDINTENSITYDIR" ]; then
    echo "WARNING: no (input) GRIDINTENSITYDIR $GRIDINTENSITYDIR" 1>&2
    exit 1
fi
if [ ! -d "$ELOGDIR" ]; then
    echo "WARNING: no (input) ELOGDIR $ELOGDIR" 1>&2
fi
if [ ! -d "$LOGDIR" ]; then
    echo "ERROR: no LOGDIR $LOGDIR" 1>&2
    exit 1
fi
if [ ! -d "$HTMLDIR" ]; then
    echo "ERROR: no HTMLDIR $HTMLDIR" 1>&2
    exit 1
fi

# Output .html/.txt files.
OUTFILEBASE=_heat-battery-target
# Output HTML file.
HTMLOUTFILE=${HTMLDIR}/${OUTFILEBASE}.html
# Output text file (max % to top-up to from grid).
TXTOUTFILE=${HTMLDIR}/${OUTFILEBASE}.txt

# Compute UTC YYYYMMDD date to match data logging.
UTCDATE="`date -u +%Y%m%d`"
ISOUTCDATETIME="`date -u +%Y-%m-%dT%H:%MZ`"
# Numeric month (UTC): no leading 0 when < 10.
MONTH="`echo $ISOUTCDATETIME | awk -F- '{print int(0+$2)}'`"
# Today's log file.
LOGFILE=${LOGDIR}/${UTCDATE}.log
# Yesterday in same format as UTCDATE (if easy to compute), else blank.
#YESTERDAY="`date -u --date yesterday +%Y%m%d`"
# Yesterday's log file, if it exists, else blank.
#YLOGFILE=""
#POSSIBLEYLF=${LOGDIR}/${YESTERDAY}.log
#if [ -e ${POSSIBLEYLF} ]; then YLOGFILE=${POSSIBLEYLF}; fi
# Local timezone.
LZONE="Europe/London"
# Local time (with DST etc) hour of day for 16WW.
LHOUR="`TZ=$LZONE; export TZ; date +%H`"
# Next hour local time HH, and deal with wrapping.
LHOURNEXT=`echo $LHOUR | awk '{if($1>22){print "00"}else{nh=$1+1;nhs=""nh;if(length(nhs)<2){nhs="0"nhs}print nhs}}'`
# Local time minutes (yes, just in case on non-hour timezone!)...
LMINS="`TZ=Europe/London; export TZ; date +%M`"

# Our set of single-letter output flags, possibly empty (shown as "-").
# Order is not important.
OUTPUTFLAGS=""
# For meanings of flags see end of file.

WINTER="false"
if [ $MONTH -gt 10 -o $MONTH -lt 2 ]; then
    WINTER="true"
    OUTPUTFLAGS="${OUTPUTFLAGS}W"
fi

# Directory for system energy-level flags in.
FLAGDIR=/run
# Forecast tomorrow sunny flag.
FORECASTGOOD=${FLAGDIR}/FORECAST_PV_GEN_GOOD.flag
if [ -e $FORECASTGOOD ]; then
    OUTPUTFLAGS="${OUTPUTFLAGS}F"
fi

# Get live computed grid intensity.
# Should be relatively up to date (or sister file should be) to be believed.
GRIDINTENSITYFILE=${GRIDINTENSITYDIR}/_gridCarbonIntensityGB.txt
GRIDMEANINTENSITYFILE="${GRIDINTENSITYDIR}/_gridCarbonIntensityGB.mean.txt"
GRIDINTENSITY=""
if [ -s "$GRIDINTENSITYFILE" ]; then
    # Check that intensity has been recomputed recently (<1h), ie is not stale.
    if [ -s _gridCarbonIntensityGB.html ]; then if [ "" != "`find _gridCarbonIntensityGB.html -mmin -60`" ]; then
        GRIDINTENSITY="`cat $GRIDINTENSITYFILE`"
    fi; fi
fi
# Expected grid demand level H / - / L.
# DEFAULT SCHEME:
# Lookup from local time hour of day through grid intensity top-up thresholds.
# Effectively a join.
# However, if a mean intensity value is available,
# force H to be 0, read L from the file, and have - be the mean intensity,
# capped by the L threshold.
if [ ! -s $CONTROLPARAMSDIR/storage-charge-pref-by-hour-local-time.csv ]; then
    echo "ERROR: missing by-hour control file" 1>&2
    exit 1
fi
#if [ ! -s $CONTROLPARAMSDIR/intensity-threshold-by-HML.csv ]; then
#    echo "ERROR: missing by-HML control file" 1>&2
#    exit 1
#fi
EXPGRIDDEMAND=`awk -F, '$1=='$LHOUR'{l=$2;if(""==l){l="-"};print l}'\
    <$CONTROLPARAMSDIR/storage-charge-pref-by-hour-local-time.csv`
# The threshold below which grid intensity has to be for discretionary top-up.
MAXTUGRIDINTENS=0
# The 'L' (off-peak) intensity ceiling, or "".
# Will be "" if not following mean or in 'H' period.
LMAXTUGRIDINTENS=""
# Grid carbon intensity upper threshold to top-up heat battery from grid.
GRIDMEANINTENSITY=""
if [ ! -s "${GRIDMEANINTENSITYFILE}" ]; then
    # Use 0 if no mean intensity available.
    MAXTUGRIDINTENS=0
else
    GRIDMEANINTENSITY="$(cat ${GRIDMEANINTENSITYFILE})"
    if [ "" = "${GRIDMEANINTENSITY}" ] || [ "${GRIDMEANINTENSITY}" -lt 0 ]; then GRIDMEANINTENSITY=0; fi
    # H => 0, else fraction of 7d mean.
    if [ "H" = "$EXPGRIDDEMAND" ]; then
        MAXTUGRIDINTENS=0
    else
        MAXTUGRIDINTENS="$(expr "${GRIDMEANINTENSITY}" \* 100 / "${STORECOPPC}")"
        LMAXTUGRIDINTENS="${MAXTUGRIDINTENS}"
    fi
fi
# Be extra cautious going into high-demand ('H') times.
if [ "-" != "$EXPGRIDDEMAND" ]; then
    OUTPUTFLAGS="${OUTPUTFLAGS}${EXPGRIDDEMAND}"
fi
# Next hour demand/threshold and if threshold is about to fall.
MAXTUGRIDINTENSABOUTTOFALL="false"
NEXPGRIDDEMAND=""
NMAXTUGRIDINTENS=""
# Assumes that this script is called several (>=4) times per hour.
if [ "$LMINS" -ge 40 ]; then 
    # Extra processing in last part of each hour.
    NEXPGRIDDEMAND=`awk -F, '$1=='$LHOURNEXT'{l=$2;if(""==l){l="-"};print l}'\
        <$CONTROLPARAMSDIR/storage-charge-pref-by-hour-local-time.csv`
    if [ "" = "$NEXPGRIDDEMAND" ]; then NEXPGRIDDEMAND="-"; fi
    NMAXTUGRIDINTENS=`awk -F, '$1=="'$NEXPGRIDDEMAND'"{l=$2;if(""==l){l=0};print l}'\
        <$CONTROLPARAMSDIR/intensity-threshold-by-HML.csv`
    if [ 0 -ge "${NMAXTUGRIDINTENS:-1}" ]; then
        # If next hour contains no-top-up high-demand period,
        # then stop any top-up well before.
        MAXTUGRIDINTENS=0;
    elif [ "${MAXTUGRIDINTENS:-0}" -gt "${NMAXTUGRIDINTENS:-1}" ]; then
        MAXTUGRIDINTENSABOUTTOFALL="true"
    fi
fi
GRIDINTENSITYLOWENOUGH="false"
GRIDSUPERGREEN="false"
GRIDRED="false"
if [ "" != "$GRIDINTENSITY" ]; then
    # DHD20221006: switching to 7d version of red/supergreen flags.
    if [ -e _gridCarbonIntensityGB.7d.red.flag ]; then
        GRIDRED="true"
        OUTPUTFLAGS="${OUTPUTFLAGS}R"
        # DHD20221005: push threshold down significantly (~10%) when red.
        MAXTUGRIDINTENS="`expr \( $MAXTUGRIDINTENS \* 9 \) / 10`"
    elif [ ! -e _gridCarbonIntensityGB.7d.supergreen.flag ]; then
        GRIDSUPERGREEN="true"
        OUTPUTFLAGS="${OUTPUTFLAGS}G"
    fi
    # Computed intensity must be *below* threshold.
    # Thus threshold of 0 means no top-up.
    if [ "$GRIDINTENSITY" -lt "$MAXTUGRIDINTENS" ]; then
        GRIDINTENSITYLOWENOUGH="true";
    fi
elif [ "true" != "$POLICYMAINDHW" ]; then
    # If heat battery is NOT the primary DHW source
    # then assume grid intensity may be high/red if unknown.
    GRIDRED="true"
    OUTPUTFLAGS="${OUTPUTFLAGS}r"
fi


# Gated: check if grid-topup may be worthwhile iff supergreen and not "H".
GRIDINTBIGFALL="false"
PREDICTEDINTRISE="false"

if [ "H" != "${EXPGRIDDEMAND}" ] && [ "true" = "${GRIDSUPERGREEN}" ]; then

    # Check for fall in intensity that gives storing now a CoP > 2.
    # See if enough below historic 7d mean.
    if [ "" != "$GRIDINTENSITY" ] && [ "" != "$GRIDMEANINTENSITY" ]; then
        LAGCOPSTORE="$(awk -v GRIDINTENSITY="$GRIDINTENSITY" -vGRIDMEANINTENSITY="$GRIDMEANINTENSITY" 'BEGIN{ printf("%d", int(100 * GRIDMEANINTENSITY / GRIDINTENSITY)); }')"
        if [ "$LAGCOPSTORE" -gt "${STORECOPPC}" ]; then
            GRIDINTBIGFALL="true"
            OUTPUTFLAGS="${OUTPUTFLAGS}Q"
        else
            OUTPUTFLAGS="${OUTPUTFLAGS}q"

    # Check for coming sharp rise in intensity that gives storing now a CoP > 2.
    # (Possibly shade in tank level from 2.0 to 3.0, ie mean >=2.)
    # Use NatGrid carbon intensity prediction.
    # Do this as little as possible to be kind to the NESO and the network!
    # So only use the API when GRIDINTBIGFALL is false.
    PREDICTEDCOPSTORE="$(sh script/predicted_CoP_from_storing_now.sh)"
    if [ "" != "$PREDICTEDCOPSTORE" ]; then
        if [ "$PREDICTEDCOPSTORE" -gt "${STORECOPPC}" ]; then
            PREDICTEDINTRISE="true"
            OUTPUTFLAGS="${OUTPUTFLAGS}P"
        else
            OUTPUTFLAGS="${OUTPUTFLAGS}p"
        fi
    fi

        fi
    fi

fi


# Grid-coupled storage log directory.
# Log files name of form: /var/log/Enphase/20211102.log
ELOGFILE=${ELOGDIR}/${UTCDATE}.log
# Log records are of the form:
#20220129T21:49Z consumption.readingTime 1643492944 consumption.net.wNow 219.259 consumption.total.wNow 216.537 production.wNow -2.722 storage.percentFull 0 storage.wNow -14 storage.readingTime 1643492186 storage.whNow 0 consumption.rmsVoltage 244.454
# Note storage.whNow and consumption.rmsVoltage values as potential gates.
# Assume for now that the last record/line is reasonably current.
# We may not be able to get any line at the very start of the day.
# We ignore any stats that are clearly stale (>1h old).
ELOGLASTRECORD=""
#ELOGwhNow=""
ELOGSoC=""
ELOGrmsVoltage=""
ELOGnetConsumption=""
# Minimum RMS voltage as seen by Enphase before being regarded as 'low'.
# DHD20220130: TBD: initial inspection suggests 245V+ is 'normal'.
# https://www.leadsdirect.co.uk/knowledge-base/what-is-the-difference-between-uk-voltage-and-european-voltage/
# "In the UK (former 240V nominal) they are: 230V -6% +10% (i.e. 216.2V – 253.0V)"
# "... all modern equipment will therefore be able to accept 230V +/-10% i.e. 207-253V."
# Threshold for mains voltage 'sag': unusually low, implies DNO struggling.
# Inspection of data suggests 243V is v low for 16WW, below 245V ~2.5% of time.
# Using 242V allows for a little sag from the Thermino and other home loads.
# See: note-on-16WW-mains-voltage-monitoring.html 
ERMSVMIN=242
# Minimum +ve export power to be considered significant.
# Set to match "significant export" elsewhere, and half eddi export margin.
EEXPMIN=50
# Maximum electricity battery % SoC to allow drawdown to heat battery.
ESOCMAX=10
if [ "true" = "$POLICYAGGRESSIVECARBON" ]; then
    # Raise when aggressively saving carbon from gas heating.
    # Note that this will waste some energy from leaked heat and AC Battery RT.
    ESOCMAX=43
fi
# Default to assuming that the AC-coupled battery is empty (or off-line).
ELOGSoC=0
# Default to assuming that mains AC voltage is OK.
ELOGrmsVoltageLow="false"
# Default to assuming that there is no significant spill/export to grid.
ELOGexporting="false"
# By default the Enphase battery status does not inhibit top-up.
ELOGbatteryInhibit="false"
# Is the Enphase log file stale/missing?
ELOGnotStale="false"
if [ -s $ELOGFILE ]; then if [ "" != "`find $ELOGFILE -mmin -20`" ]; then
    ELOGnotStale="true"
    # Check if AC Battery has any charge that we'd like to avoid draining.
    ELOGLASTRECORD="`tail -1 < $ELOGFILE`";
    #ELOGwhNow=`echo $ELOGLASTRECORD | awk '$16 = "storage.whNow" {print $17}'`
    #if [ 0 -ne "$ELOGwhNow" ]; then
    #    OUTPUTFLAGS="${OUTPUTFLAGS}B"
    #fi
    ELOGSoC=`echo $ELOGLASTRECORD | awk '$10 = "storage.percentFull" {print $11}'`
    # If (not POLICYAGGRESSIVECARBON or not "L" or not super-green)
    # and if SoC is above ESOCMAX
    #     or SoC is non-zero and this is neither an "L" hour nor 'super-green',
    # then inhibit top-up.
    if [ "true" != "$POLICYAGGRESSIVECARBON" -o \
         "L" != "$EXPGRIDDEMAND" -o "true" != "$GRIDSUPERGREEN" ]; then
        if [ \( 0 -ne "$ELOGSoC" -a \
                \( "L" != "$EXPGRIDDEMAND" -a "true" != "$GRIDSUPERGREEN" \) \) -o \
             \( "$ELOGSoC" -gt "$ESOCMAX" \) ]; then
            #OUTPUTFLAGS="${OUTPUTFLAGS}B"
            ELOGbatteryInhibit="true"
        fi
    fi
    # Check mains voltage.
    ELOGrmsVoltage=`echo $ELOGLASTRECORD | awk '$18 = "consumption.rmsVoltage" {print int($19)}'`
    if [ "$ELOGrmsVoltage" -lt "$ERMSVMIN" ]; then
        ELOGrmsVoltageLow="true";
        OUTPUTFLAGS="${OUTPUTFLAGS}V"
    fi
    # Check export power if any; export is indicated by a -ve net.wNow value.
    ELOGnetConsumption=`echo $ELOGLASTRECORD | awk '$4 = "consumption.net.wNow" {print int($5)}'`
    if [ "$ELOGnetConsumption" -lt "-${EEXPMIN}" ]; then
        ELOGexporting="true"
        OUTPUTFLAGS="${OUTPUTFLAGS}X"
    fi
fi; fi
if [ "true" != "$ELOGnotStale" ]; then
    # Note 'unknown' status.
    OUTPUTFLAGS="${OUTPUTFLAGS}bvx"
    # Inhibit heat-battery top-up unless in 'L' grid demand hour.
    if [ "L" != "$EXPGRIDDEMAND" ]; then
        ELOGbatteryInhibit="true"
    fi
fi

# Compute percentage of recent days where heat battery has NOT filled.
# This is detected as the Eddi not reaching state 5 ('max temp') during a day.
# As this approaches 0% then reduce the amount of grid top-up.
# The aim is to try to capture all available diversion.
# Read our own recent live logs (if any) in LOGDIR to compute this.
# Uses one ~weekly cycle to be able to respond to seasons and weather.
#
# DHD20241205: no longer exclude current day's log record.
#
# Maximum history (live log) age (in days) to use.
# Should be at least 7 days given typical household weekly activity cycle.
TUMODULATEMAXHIST=8
TUMODLOGS="$(find ${LOGDIR} -name '2???????.log' -mtime -$TUMODULATEMAXHIST)"
#TUMODLOGS="`find ${LOGDIR} -name '2???????.log' -mtime -$TUMODULATEMAXHIST \! -name ${UTCDATE}.log`"
#echo INFO: TUMODLOGS "${TUMODLOGS}"
TUMODLOGCOUNT="`echo $TUMODLOGS | wc -w`"
#echo INFO: TUMODLOGCOUNT "${TUMODLOGCOUNT}"
TUMODWEEKPLUS="false"
if [ "$TUMODLOGCOUNT" -ge 7 ]; then
    # At least a week's feedback data.
    TUMODWEEKPLUS="true"
fi
#
# Compute percentage of days that heat battery DID NOT fill [0,100].
# If this is low, then decrease the grid top-up.
# In the absence of suitable log data allow full top-up.
TUMODNOTFULLPC=100
if [ "$TUMODLOGCOUNT" -gt 0 ]; then
    TUMODFULLCOUNT="`egrep -l ' ES 5' /dev/null $TUMODLOGS | wc -w`"
    TUMODNOTFULLCOUNT="`expr $TUMODLOGCOUNT - $TUMODFULLCOUNT`"
    TUMODNOTFULLPC="`expr \( $TUMODNOTFULLCOUNT \* 100 \) / $TUMODLOGCOUNT`"
fi


# Maximum number of days between DHW tank pasteurisations.
# Must be >= TUMODULATEMAXHIST.
# DHD202501011: extended from 14 to 27 to ensure pasteurised at least monthly.
PASTEURDAYSMAX=27
# Minimum number of days between DHW tank pasteurisations.
PASTEURDAYSMIN=9
# If true then pasteurisation has run recently.
PASTEURRECENT="false"
# If true then pasteurisation has run too long ago.
PASTEURANCIENT="false"
# If true then pasteurisation cycle is pending good grid conditions..
PASTEURPENDING="false"
# If true then run pasteurisation.
PASTEURRUN="false"

# Only even think about pasteurisation during 'L' hours to reduce effort.
if [ "L" = "${EXPGRIDDEMAND}" ]; then
    # Logs must include today, to terminate pasteurisation.
    PASTEURLOGSMIN="$(find ${DLOGDIR} -name '2???????.log' -mtime "-$PASTEURDAYSMIN" | sort)"
    if [ "" != "$(cat /dev/null ${PASTEURLOGSMIN} | sh script/checkForDHWHot.sh)" ]; then
        # Recent pasteurisation.
        PASTEURRECENT="true"
        # Prioritise PV diversion to the Thermino for least lossy heat storage.
        sh script/myenergi/eddiSetHeaterPri-netrc.sh 2
    else
        # Prioritise PV diversion to DHW tank to pasteurise during the day!
        sh script/myenergi/eddiSetHeaterPri-netrc.sh 1
        # Logs must include today, to terminate pasteurisation.
        PASTEURLOGSMAX="$(find ${DLOGDIR} -name '2???????.log' -mtime "-$PASTEURDAYSMAX" | sort)"
        # If no sign of DHW being pasteurised within max days, do it now.
        if [ "" = "$(cat /dev/null ${PASTEURLOGSMAX} | sh script/checkForDHWHot.sh)" ]; then
            PASTEURANCIENT="true"
            PASTEURRUN="true"
            OUTPUTFLAGS="${OUTPUTFLAGS}s"
        # Else if super-green, run pasteurisaton now.
        elif [ "true" = "${GRIDSUPERGREEN}" ]; then
            PASTEURRUN="true"
            OUTPUTFLAGS="${OUTPUTFLAGS}s"
        # Indicate waiting to pasteurise soonish...
        else
            PASTEURPENDING="true"
            OUTPUTFLAGS="${OUTPUTFLAGS}w"
        fi
    fi
fi


# Diverter storage log directory.
# Log files name of form: data/eddi/log/live/20220223.log
DLOGFILE=${DLOGDIR}/${UTCDATE}.log
# Log records are of the form:
#{"eddi":[{"sno":0,"dat":"23-02-2022","tim":"18:57:53","ectp2":38,"ectt1":"Internal Load","ectt2":"Grid","ectt3":"None","bsm":0,"bst":0,"cmt":254,"dst":1,"div":0,"frq":50.14,"fwv":"3200S3.048","grd":41,"pha":1,"pri":1,"sta":1,"tz":0,"vol":2457,"hpri":1,"hno":1,"ht1":"Tank 1","ht2":"Tank 2","r1a":0,"r2a":0,"rbc":0,"rbt":119,"tp1":127,"tp2":127}]}
# Primarily used to add some useful data to the log, closing the loop.
DLOGnotStale="false"
Dstatus=""
DSTOPPED="false"
if [ -s $DLOGFILE ]; then if [ "" != "`find $DLOGFILE -mmin -20`" ]; then
    DLOGnotStale="true"
    Dstatus=`tail -1 $DLOGFILE | sed -n -e 's/^.*"sta":\([1-6]\)[^0-9].*$/\1/p'`
    if [ "6" = "$Dstatus" ]; then
        DSTOPPED="true";
        OUTPUTFLAGS="${OUTPUTFLAGS}S"
    fi
fi; fi
# Today's Eddi frequency response log file (if any).
DLOGFRFILE=${DLOGDIR}/${UTCDATE}.freqResp.log


# TOP-UP LIMIT FROM GRID
# 50% is about 1 day's mean demand, and should be reasonably measurable.
# 25% is enough for washing hands and dishes, or a quick shower.
# Minimum amount to top up heat battery to when top-up is allowed at all.
# This may be above zero when the heat battery is the sole source of DHW.
# Should be fairly high since recovery time is several hours, eg for a shower.
# Should be much less than 100.
# A round number of % (a multiple of 5 or 10) may be helpful.
# DEFAULT TO ZERO (for when the heat battery is not the primary DHW source).
TOPUPMINPCMAX=0
# DHD20231017: enough for hand washing; forces to slowest fill (@<25).
#TOPUPMINPCMAX=24
if [ "true" = "$POLICYMAINDHW" ]; then
    # DHD20231216: enough for a bath, and so that 2/3rds at H/R times is <50%.
    TOPUPMINPCMAX=74
fi
# Maximum amount to top up heat battery to when top-up is allowed at all.
# Should be higher than TOPUPMINPCMAX.
# Should be less than or equal to 100.
# A round number of % (a multiple of 5 or 10) may be helpful.
TOPUPMAXPCMAX=50
if [ "true" = "$POLICYAGGRESSIVECARBON" -o "true" = "$POLICYMAINDHW" ]; then
    #TOPUPMAXPCMAX=95
    TOPUPMAXPCMAX=100
fi


# Compute % top-up limit.
# This is the maximum percent full that the heat battery should be
# topped up from the grid at the moment.
# Zero, or a % less than the current battery content, implies no top-up.
# DEFAULT TO ZERO
TOPUPMAXPC=$TOPUPMINPCMAX


# PTOPUPMAXPC is putative top-up level whether actually used/allowed or not!
# Start at normal target ~half fill.
PTOPUPMAXPC=$TOPUPMAXPCMAX


# Downwards adjustments to max fill level.
# Top-up less if good sunshine forecast for tomorrow.
# Also if not Nov/Dec/Jan since significant diversion very unlikely.
# "Sunny" is often not enough for any significant diversion Nov/Dec/Jan.
if [ "true" != "$WINTER" -a -e $FORECASTGOOD ]; then
    # Leave less space for PV if the forecast is not 'sunny', top-up higher.
    PTOPUPMAXPC="`expr \( $TOPUPMINPCMAX + 6 \* $PTOPUPMAXPC \) / 7`"
fi
# If not 'L' grid time of day and not supergreen then cut max top-up %.
# (So at expected L demand *or* when supergreen at any time, don't cut it!)
if [ "L" != "$EXPGRIDDEMAND" -a "true" != "$GRIDSUPERGREEN" ]; then
    PTOPUPMAXPC="`expr \( $TOPUPMINPCMAX + 3 \* $PTOPUPMAXPC \) / 4`"
fi
# Reduce top-up if it seems that there is likely to be PV diversion available.
LIKELYPVFORDIVERSION="false"
# Enphase ACB not empty implies good PV likely next day: cut max top-up %,
# unless we have at least a week's feedback data...
if [ "false" = "$TUMODWEEKPLUS" -a 0 -ne "$ELOGSoC" ]; then
    PTOPUPMAXPC="`expr \( $TOPUPMINPCMAX + 3 \* $PTOPUPMAXPC \) / 4`"
    LIKELYPVFORDIVERSION="true"
elif [ "$PTOPUPMAXPC" -gt "$TOPUPMINPCMAX" ]; then
    # Modulate downwards given how often heat battery has filled recently.
    # A full heat battery potentially means potential unused/missed diversion.
    # If full every day then no need for big grid top-up.
    # If not full any day then low-carbon grid-top should be allowed.
    if [ "$TUMODNOTFULLPC" -eq 0 -a "$PTOPUPMAXPC" -gt "$TOPUPMINPCMAX" ]; then
        # Even if full every day allow small opportunistic top-up, eg to 1%.
        PTOPUPMAXPC="`expr $TOPUPMINPCMAX + 1`"
        LIKELYPVFORDIVERSION="true"
    else
        PTOPUPMAXPC="`expr \( \( \( $PTOPUPMAXPC - $TOPUPMINPCMAX \) \* $TUMODNOTFULLPC \) / 100 \) + $TOPUPMINPCMAX`"
        if [ "$TUMODNOTFULLPC" -le 50 ]; then
            LIKELYPVFORDIVERSION="true"
        fi
    fi
fi

# Upwards adjustments to max fill level.
# Top up higher the further that live intensity is below the threshold.
# Also should help somewhat randomise 'off' time based on local DHW use.
if [ "true" = "$GRIDINTENSITYLOWENOUGH" ]; then
    # Scale up top up by as much as an extra 25% of current value.
    GIUPLIFTMAXPC=25
    GIUPLIFTMAX="`expr \( $PTOPUPMAXPC \* $GIUPLIFTMAXPC \) / 100`"
    GIUHEADROOM="`expr 100 - $PTOPUPMAXPC`"
    if [ "$GIUHEADROOM" -lt "$GIUPLIFTMAX" ]; then
        if [ "$GIUHEADROOM" -gt 0 ]; then
            GIUPLIFTMAX=$GIUHEADROOM
        else
            GIUPLIFTMAX=0
        fi
    fi
    if [ "true" = "$MAXTUGRIDINTENSABOUTTOFALL" ]; then
        # Reduce this when the threshold is about to fall.
        # This may help further randomise 'off' time.
        GIUPLIFTMAX="`expr $GIUPLIFTMAX / 2`"
    fi
    if [ "$GIUPLIFTMAX" -gt 1 ]; then
        GIUPLIFT="`expr \( $GIUPLIFTMAX \* \( $MAXTUGRIDINTENS - $GRIDINTENSITY \) \) / $MAXTUGRIDINTENS`"
        if [ "$GIUPLIFT" -gt 0 ]; then
            PTOPUPMAXPC="`expr $PTOPUPMAXPC + $GIUPLIFT`"
        fi
    fi
fi
# Force up to minimum value for this day of the week, in "L" period, if any.
DailyMinPCApplied=""
if [ "$PTOPUPMAXPC" -lt 100 ]; then
    if [ "L" = "$EXPGRIDDEMAND" ]; then
        #DOW="`date -u +%u`"
        DOW="`TZ=$LZONE; export TZ; date +%u`"
        if [ ! -s $CONTROLPARAMSDIR/min-fill-by-dow.csv ]; then
            echo "ERROR: missing by-day-of-week control file" 1>&2
            exit 1
        fi
        DOWMINLTARGET=`awk -F, '$1=='$DOW'{l=$2;print l}'\
            <$CONTROLPARAMSDIR/min-fill-by-dow.csv`
        if [ "" != "$DOWMINLTARGET" ]; then
            # There is a current d-o-w floor % target.
            DailyMinPCApplied="$DOWMINLTARGET"
            if [ "$DOWMINLTARGET" -gt "$PTOPUPMAXPC" ]; then
                PTOPUPMAXPC="$DOWMINLTARGET"
                OUTPUTFLAGS="${OUTPUTFLAGS}D"
            fi
        fi
    fi
fi

# Ensure that PTOPUPMAXPC stays in bounds.
if [ "$PTOPUPMAXPC" -lt "$TOPUPMINPCMAX" ]; then PTOPUPMAXPC=$TOPUPMINPCMAX; fi
if [ "$PTOPUPMAXPC" -gt 100 ]; then PTOPUPMAXPC=100; fi


# Allow top-up when:
#   * Grid intensity is acceptably low given time of day
#     OR this is the main DHW source and predicted demand is low
#   * Enphase ACB battery state does not inhibit top-up.
#   * Mains RMS voltage is not unusually low for 16WW.
if [ \( "true" = "$GRIDINTENSITYLOWENOUGH" -o \
         \( "true" = "$POLICYMAINDHW" -a "" != "$DailyMinPCApplied" \) \
         \) -a \
     "false" = "$ELOGbatteryInhibit" -a \
     "false" = "$ELOGrmsVoltageLow" ]; then
    # Use putative top-up level.
    TOPUPMAXPC=$PTOPUPMAXPC
fi

# Allow minimal top-up when:
#    * Top-up level allowed is otherwise zero.
#    * Grid intensity is below the 'L' threshold, so carbon would be saved.
#    * Not in an 'H' hour, nor low mains voltage.
if [ "$TOPUPMAXPC" -le 1 ]; then
    if [ "H" != "$EXPGRIDDEMAND" -a "false" = "$ELOGrmsVoltageLow" -a \
         "${GRIDINTENSITY:-999}" -lt "${LMAXTUGRIDINTENS:-190}" ]; then
        TOPUPMAXPC="1";
    fi
fi

# DEMAND REDUCTION AT PEAK TIMES
# If grid intensity red/unknown, or peak demand time, then cut max top-up %.
# Also if higher than maximum allowed 'L' intensity threshold.
# Also if the grid voltage is unusually low.
# May cut limit to below the normal minimum, but not to zero.
# Attempts to reactively minimise top-ups during the grid's worst times.
# Less restrictive to top-up (eg when info missing) when POLICYMAINDHW=true.
# WHEN CLOSE to H predicted period, set back target a little.
if [ "$TOPUPMAXPC" -gt 1 ]; then
    if [ "true" = "$GRIDRED" -o \
        "H" = "$EXPGRIDDEMAND" -o \
        \( "${GRIDINTENSITY:-999}" -ge "${LMAXTUGRIDINTENS:-190}" \) -o \
        "true" = "$ELOGrmsVoltageLow" ]; then
        TOPUPMAXPC="`expr \( $TOPUPMAXPC \* 2 \) / 3`"
    elif [ "H" = "$NEXPGRIDDEMAND" ]; then
        TOPUPMAXPC="`expr \( $TOPUPMAXPC \* 5 \) / 6`"
    fi
    # Avoid accidentally zeroing TOPUPMAXPC.
    if [ "$TOPUPMAXPC" -lt 1 ]; then TOPUPMAXPC=1; fi
fi

# Dither towards new TOPUPMAXPC value to defuse ramp-up if new value is higher.
# Only in the first part of the hour so will be full value by end of hour,
# OR old value was zero, so this may be ramping up after a back-off.
if [ "$TOPUPMAXPC" -gt 1 ]; then
    # Read old TOPUPMAXPC if present to help dither/randomise transition.
    OLDTOPUPMAXPC=""
    if [ -s $TXTOUTFILE ]; then
        # Canonicalise/sanitise a bit...
        OLDTOPUPMAXPC="`awk < $TXTOUTFILE '($1>=0)&&($1<=100) {print int($1)}'`"
    fi
    if [ "" != "$OLDTOPUPMAXPC" ]; then
        # Either in first 20 minutes of the hour and this is a ramp up
        # or old value was 0 so this may be recovery from voltage sag.
        if [ \( "$LMINS" -lt 20 -a "$TOPUPMAXPC" -gt "$OLDTOPUPMAXPC" \) -o \
             \( 0 -eq "$OLDTOPUPMAXPC" \) ]; then
            # Result higher than old target, up to current target.
            # Slow the ramp-up: linear gives ~2 ticks to reach full value.
            TOPUPMAXPC="`awk -v MIN=$OLDTOPUPMAXPC -v MAX=$TOPUPMAXPC 'BEGIN{srand(); print int(MIN+1+(rand()*rand())*(MAX-MIN-1));}'`"
        fi
    fi
fi

# OPPORTUNISTIC MINIMAL DHW 'KEEP-HOT' TOP-UP.
# This may also allow dynamic response to draw-down.
#
# If grid not red and not at or about to be peak time (and voltage is not low)
# and there is not likely to be lots of PV available for diversion
# and grid intensity is enough below gas burn per the off-peak threshold ('L')
# and the top-up target percentage is otherwise zero
# then keep at least a minimal amount of directly-usuable DHW available
# to try to further reduce or eliminate gas use for small DHW demands
# by raising the target to 1.
if [ "true" != "$GRIDRED" -a \
     "H" != "$EXPGRIDDEMAND" -a "H" != "$NEXPGRIDDEMAND" -a \
     "" != "$LMAXTUGRIDINTENS" -a \
     "false" = "$ELOGrmsVoltageLow" ]; then
    if [ "false" = "$LIKELYPVFORDIVERSION" ]; then
        if [ "$GRIDINTENSITY" -lt "$LMAXTUGRIDINTENS" ]; then
            if [ "$TOPUPMAXPC" -le 0 ]; then
                TOPUPMAXPC=1
                OUTPUTFLAGS="${OUTPUTFLAGS}k"
            fi
        fi
    fi
fi

# Final overriding boost disable if set.
if [ "true" = "$BOOSTDISABLED" ]; then
    OUTPUTFLAGS="${OUTPUTFLAGS}N"
    TOPUPMAXPC=0
fi


# START OUTPUT GENERATION (txt, HTML, log)...
TMPFILE=${OUTFILEBASE}.$$.tmp
# Trap unexpected exits with tidyup.
trap "/bin/rm -f ${TMPFILE}; exit 1" 1 2 15


# ASCII plain-text file generation
##################################
# Contains single decimal number in range [0-100] for simplicity in parsing.
# Eg, entire file content might be the two characters "50".
if [ "false" = "$dryrun" ]; then
    echo "$TOPUPMAXPC" > $TMPFILE
    # Atomically move into place.
    # TODO: consider reducing filesystem traffic if value unchanged...
    /bin/chmod -f 644 $TMPFILE
    /bin/mv -f $TMPFILE $TXTOUTFILE
    /bin/chmod a+r,a-wx $TXTOUTFILE
fi

# LOG GENERATION
################
# Appends record to log file for today.
LOGRECORD="$ISOUTCDATETIME MT ${TOPUPMAXPC:--} GI ${GRIDINTENSITY:--} IT ${MAXTUGRIDINTENS:--} F ${OUTPUTFLAGS:--} ES ${Dstatus:--}"
# Format / example record:
#     2022-02-23T19:08Z MT 0 GI - IT 50 F rbvx ES -
# ie:
#   * UTC-timestamp
#   * "MT%" max-top-up-percentage
#   * "GT" grid-intensity-gCO2perkWh-or-dash
#   * "IT" intensity-threshold
#   * "F" status-flags-or-dash
#   * "ES" eddi-status-or-dash
if [ "false" = "$dryrun" ]; then
    if [ -f $LOGFILE ]; then /bin/chmod u+w $LOGFILE; fi
    echo $LOGRECORD >> $LOGFILE
    /bin/chmod a+r,o-wx $LOGFILE
else
    echo INFO: log record 1>&2
    echo $LOGRECORD
fi


# HTML GENERATION
#################
# Generates HTML5 (not necessarily XHMTL).

# Could put back auto-refresh on this...
OTHERMETA=

# Generate HTML header in simplified form...
# Title of this page.
TITLE="Heat Battery Grid Top-up Maximum"
TITLEFLAG=""
if [ "true" = "$UNUSED" ]; then
    TITLEFLAG="!! "
fi
# Series title text.
SERIESTITLE="Earth Notes"
# Canonical URL.
CANONURL=https://www.earth.org.uk/${OUTFILEBASE}.html 
# Home link to the index page.
rm -f $TMPFILE
cat >> $TMPFILE << EOHEADER
<!DOCTYPE html>
<html lang=en>
<head>
<meta charset=utf-8>
<meta name=viewport content="width=device-width,initial-scale=1,minimum-scale=1">$OTHERMETA
<title>$TITLEFLAG$TITLE - $SERIESTITLE</title>
<meta property=og:title content="$TITLEFLAG$TITLE - $SERIESTITLE">
<meta property=og:description name=description content="Heat battery target and stats for 16WW.">
<meta name=twitter:card content=summary_large_image><meta name=twitter:site content=@EarthOrgUK><meta property=og:image content=https://www.earth.org.uk/img/2022/20220205-heatbatterytarget.png>
<link href=$CANONURL rel=canonical>
<style>@media print{.noprint{display:none!important}}</style>
</head>
<body>
<div itemscope itemtype=http://schema.org/WebPage>
<h1 itemprop="headline name" style="font-family:sans-serif;margin:0;padding:0"><span style="color:green">$SERIESTITLE</span>: $TITLE</h1>
<p>(See the <a href="//www.earth.org.uk/data/heatBattery/16WWDHW/">control parameters</a>, <a href="//www.earth.org.uk/data/heatBattery/log/">logs</a>, <a href="//www.earth.org.uk/heat-battery-topup-dataset.html">dataset</a> and <a href="note-on-solar-DHW-for-16WW-UniQ-and-PV-diversion.html">backstory</a>.)</p>
<p itemprop=description>This page shows the maximum percent-full to top-up to from the grid, if any, for the DHW heat battery.</p>
EOHEADER

#if [ 0 = "$TOPUPMAXPC" ]; then
#    echo "<p><strong>NO HEAT BATTERY TOP-UP FROM GRID CURRENTLY</strong>.</p>" >> $TMPFILE
#fi

# Collect energy input for logging last as may be fragile.
# May end up capturing a tiny bit of a current boost's energy.
ET="`sh script/myenergi/eddiDaySummary.sh -kWhToday`"
echo "<p>Diversion plus top-up today: ${ET}kWh</p>" >> $TMPFILE

echo "<p>Heat battery control log entry: <code>$LOGRECORD</code></p>" >> $TMPFILE
echo "<p>Putative current raw top-up level (when allowed): ${PTOPUPMAXPC}% (normal min ${TOPUPMINPCMAX}%, max ${TOPUPMAXPCMAX}%).<p>" >> $TMPFILE
case "$Dstatus" in
  6) echo "<li>Eddi state ${Dstatus}: stopped (low grid frequency?).</li>";;
  5) echo "<li>Eddi state ${Dstatus}: maximum temperature reached (heat battery full).</li>";;
  4) echo "<li>Eddi state ${Dstatus}: boost (heat battery grid top-up).</li>";;
  3) echo "<li>Eddi state ${Dstatus}: divert (heat battery PV top-up).</li>";;
esac >> $TMPFILE
echo "<p>Flags/status:</p><ul>" >> $TMPFILE
echo "<li>[MT] <meter min=0 max=100 value=$TOPUPMAXPC title=$TOPUPMAXPC%>$TOPUPMAXPC%</meter> heat battery Maximum Top-up level: $TOPUPMAXPC%</li>" >> $TMPFILE
if [ "" = "$GRIDINTENSITY" ]; then
    echo "<li>[GI] Grid Intensity currently unknown.</li>" >> $TMPFILE
else
    echo "<li>[GI] Grid Intensity gCO2/kWh: $GRIDINTENSITY (7d mean ${GRIDMEANINTENSITY:-unknown})</li>" >> $TMPFILE
fi
echo "<li>[IT] Grid gCO2/kWh Intensity Threshold below which discretionary top-up is allowed: $MAXTUGRIDINTENS.</li>" >> $TMPFILE
echo "<li>[F ${EXPGRIDDEMAND}] expected grid demand from High (peak) / - (normal) / <!-- Medium (off-peak) / --> Low (off-peak).</li>" >> $TMPFILE
if [ "false" != "$ELOGbatteryInhibit" ]; then
    echo "<li>[F B] Enphase AC Battery SoC too high to allow transfer to heat battery, or unknown [b].</li>" >> $TMPFILE
fi
if [ "" != "$DailyMinPCApplied" ]; then
    echo "<li>[F D] Daily minimum top-up value (${DailyMinPCApplied}%) applicable.</li>" >> $TMPFILE
fi
if [ "true" = "$DSTOPPED" ]; then
    echo "<li>[F S] grid response; Stopped boost/divert.</li>" >> $TMPFILE
fi
if [ -e $FORECASTGOOD ]; then
    echo "<li>[F F] good Forecast PV generation tomorrow.</li>" >> $TMPFILE
fi
if [ "true" = "$GRIDSUPERGREEN" ]; then
    echo "<li>[F G] grid is super-Green (low) intensity and no draw-down from storage.</li>" >> $TMPFILE
fi
if [ "true" = "$GRIDRED" ]; then
    echo "<li>[F R] grid is Red (high) intensity, or unknown [r].</li>" >> $TMPFILE
fi
if [ "false" != "$ELOGrmsVoltageLow" ]; then
    echo "<li>[F V] mains Voltage low, or unknown [v].</li>" >> $TMPFILE
fi
if [ "false" != "$ELOGexporting" ]; then
    echo "<li>[F X] eXporting/spilling to grid significantly, or unknown [x].</li>" >> $TMPFILE
fi
if [ "false" != "$WINTER" ]; then
    echo "<li>[F W] solar generation Winter minimum.</li>" >> $TMPFILE
fi
if [ "false" != "$PASTEURPENDING" ]; then
    echo "<li>[F w] waiting for good grid conditions to run sterilisation/pasteurisation.</li>" >> $TMPFILE
fi
if [ "false" != "$PASTEURRUN" ]; then
    echo "<li>[F s] sterilisation/pasteurisation in progress.</li>" >> $TMPFILE
fi
if [ "false" != "$POLICYMAINDHW" ]; then
    echo "<li>Heat battery set as primary DHW source.</li>" >> $TMPFILE
fi
echo "<li>Recent ($TUMODNOTFULLCOUNT/$TUMODLOGCOUNT) days when tank or battery did not fill: ${TUMODNOTFULLPC}%</li>" >> $TMPFILE
if [ -s "$DLOGFRFILE" ]; then
    FRPC="?"
    DLOGS6="`egrep -c '"sta":[6]' $DLOGFILE`"
    DLOGS346="`egrep -c '"sta":[346]' $DLOGFILE`"
    if [ "" != "$DLOGS346" -a "0" != "$DLOGS346" ]; then
        FRPC="`expr \( 100 \* $DLOGS6 \) / $DLOGS346`"
    fi
    echo "<li>Grid frequency response minutes today (~${FRPC}% of active): `wc -l < $DLOGFRFILE`</li>" >> $TMPFILE
fi
echo "</ul>" >> $TMPFILE

if [ -s out/hourly/heatBatTarget.png ]; then
    echo "<figure>" >> $TMPFILE
    echo '<img src=out/hourly/heatBatTarget.png width=800 height=200 style="max-width:100%;height:auto" alt="last 24/48h" title="last 24/48h">' >> $TMPFILE
    echo "<figcaption>Previous day and today: heat battery max % fill/target from grid, grid intensity, intensity threshold, eddi state (black=stopped=60, red=full=50, yellow=boost=40, green=divert=30).</figcaption>" >> $TMPFILE
    echo "</figure>" >> $TMPFILE
fi

echo "<p>External control tables:</p><ul>" >> $TMPFILE
echo "<li>Expected <a href="$CONTROLPARAMSDIR/storage-charge-pref-by-hour-local-time.csv">grid demand by local hour of day</a>.</li>" >> $TMPFILE
echo "<li>Grid <a href="$CONTROLPARAMSDIR/intensity-threshold-by-HML.csv">intensity limit by demand level</a>.</li>" >> $TMPFILE
echo "<li><a href="$CONTROLPARAMSDIR/min-fill-by-dow.csv">Minimum (low-carbon) top-up by day of week</a>.</li>" >> $TMPFILE
echo "</ul>" >> $TMPFILE

# Wrap up the HTML page.
cat >> $TMPFILE << FOOTER
<footer><p><small>
This page was automatically generated on ${UTCDATE} (<span itemprop=dateModified>$ISOUTCDATETIME</span>).<br />
First published <span itemprop=datePublished>2022-01-29</span>.<br />
Copyright &copy; <a href="http://d.hd.org/"><span itemprop=author>Damon Hart-Davis</span></a> <span itemprop=copyrightYear>2022</span> to $(cat .work/copyrightYearLatest.txt). [<a href="/">home</a>]<br />
</small></p></footer>
</div></body></html>
FOOTER

if [ "false" = "$dryrun" ]; then
    # Atomically move into place.
    /bin/chmod -f 644 $TMPFILE
    /bin/mv -f $TMPFILE $HTMLOUTFILE
    /bin/chmod a+r,a-wx $HTMLOUTFILE
else
    #more $TMPFILE
    rm -f $TMPFILE
fi


# Pasteurisation, as late as possible and slightly randomised start, if needed.
# Boost heater 1 (DHW cyclinder) directly.
# Boost for slightly longer than the interval between script runs.
if [ "true" = "$PASTEURRUN" ]; then
    if [ "false" = "$dryrun" ]; then
        sleep "$(awk 'BEGIN{srand(); print int(33*rand());}')"
        sh script/myenergi/eddiBoost-netrc.sh 11 1
    fi
fi


exit 0


# NOTE: interaction with Enphase AC Battery (ACB).
#   * A general aim is to avoid taking energy from the ACB
#     that has already taken some storage losses,
#     and load it into the heat battery where it will leak faster,
#     and maybe empty the ACB forcing imports eg until after dawn.
#     (However a very-nearly-empty ACB need not prevent heat top-up.)
#   * A non-empty ACB overnight is taken as an indication of good solar PV
#     generation the previous day, and a predictor of likely decent PV
#     the next day allowing direct heat battery fill by diversion.
#     Ie if we have a fullish ACB we probably shouldn't top-up the heat battery.

# NOTE: meaning of flags (in F field)
#   B AC-coupled Batteries not (nearly) empty
#   b AC-coupled Batteries state unknown
#   D daily minimum % fill applied to calculations
#   F good PV generation Forecast tomorrow
#   G grid is super-Green
#   H expected High/peak grid demand
#   I Importing from grid significantly (reserved)
#   i Importing from grid unknown (reserved)
#   k Keep warm and allow dynamic response
#   L expected Low/off-peak grid demand
#   M expected Medium/off-peak grid demand
#   N no boost permitted (ie boost is disabled)
#   P Predicted intensity to rise significantly (>2x) over next days
#   p Predicted intensity not forecast to rise significantly.
#   Q intensity fallen significantly (>2x) compared to last 7d.
#   q intensity not fallen significantly (>2x) compared to last 7d.
#   R grid Red intensity flag
#   r grid Red intensity assumed as state unknown
#   S eddi Stopped due to low grid frequency or otherwise
#   s forcing sterilisation cycle (even if general boost not allowed).
#   V grid Voltage sag
#   v grid Voltage sag unknown
#   W Winter PV gen minimum (Nov/Dec/Jan)
#   w waiting for good grid conditions to run pasteurisation cycle.
#   X eXporting to grid significantly
#   x eXporting to grid unknown


#
# TODO: let non-empty ACB inhibit Thermino top-up if intensity due to rise much.
# TODO: modulate 'minimum' a little based on grid intensity vs target and RAG.
# TODO: if battery actually at ~100% then don't inhibit topup.
#
# TODO: allow stand-alone mode (ie combi broken or not present):
#   * There is no backup source of DHW: heat battery must never get empty.
#   * Trivial but bad solution would be to keep full from mains 24x7.
#   * Better may be to set a minimum % (lower in H/red grid times).
#   * Better still may be to push minimum up to cover predicted demand.
#   * This should still let PV diversion do as much as possible in summer.
# TODO: aggressive carbon-saving mode, eg top-up even from battery <<'M' GI.
#
# TODO: allow use of yesterday's Enphase log until 00:30-ish.
# TODO: increase % if 'L' hour and no sun forecast and intensity << threshold.

# NOTES for lower-level controller:
# Randomly reduce power at on transitions and close to target, ~1--4 mins in 5.
#   * Should slightly randomly delay on transitions.
#   * Should taper heating power as target is reached.


# CHANGELOG (partial)
# 2024-12-08: added 'w' to indicate waiting for super-green to sterilise.
# 2024-12-05: starting support for DHW tank sterilisation cycle 's'.
# 2024-11-27: computing IT as fraction of rolling 7d mean.
# 2024-11-27: loading soft parameters from data/consolidated/softparams.txt now.
# 2024-11-24: minimising calls to NESO API and thus P/p/Q/q flag computation.
# 2024-11-12: BOOSTDISABLED ('N' flag) added; set ready for heat pump works.
# 2024-09-04: 'Q'(/'q') flag for when significant intensity fall seen.
# 2024-09-03: 'P'(/'p') flag for when significant intensity rise predicted.
# 2023-11-07: when GI below 'L' threshold always allow minimal (to 1%) top-up.
# 2023-10-26: reduce MT when intensity > 'L' level (190 if not set).
# 2023-07-28: improvement on Thermino side to top-up reluctantly when MT<50%.
# 2023-07-02: bugfix: day-of-week fill minimum is now for local timezone.
# 2023-01-29: dropped ERMSVMIN from 244 to 242 observing transients this am.
# 2023-01-29: raised TOPUPMAXPCMAX from 95 to 100; allows more LC grid capture.
# 2023-01-21: when not peak/H and not R and below 'L', 'k'eep warm %-fill > 0.
# 2022-11-10: use 7d mean intensity (capped by 'L') threshold for 'normal'.
# 2022-11-06: switch from 24h to 7d versions of grid red/supergreen flags.
# 2022-11-05: raising GI threshold a little when green, lowering when red.
# 2022-10-15: ERMSVMIN now <244V to allow a little sag from Thermino itself.
# 2022-10-03: more gradual ramp up of MT target %-fill value.
# 2022-09-27: less 'R'/'H' reduction in % - less likely to cause annoyance.
# 2022-09-26: when near to 'H' predicted demand, drop target a little.
# 2022-09-24: ignore intensity for d-o-w target low period when POLICYMAINDHW.
# 2022-09-23: when POLICYMAINDHW="true" maintain top-up when some info missing.
# 2022-09-23: min/max thresholds adjusted (upwards).
# 2022-09-23: emergency flip to POLICYMAINDHW="true" as combi playing up!
# 2022-06-25: potentially allow small top-up (eg to 1%) even if full every day.
# 2022-05-23: grid low voltage thresh ERMSVMIN now <245V, and effects broadened.
# 2022-04-21: ignore current-day log in counting days where battery filled.
# 2022-04-20: 'D' flag assigned to show daily nominal minimum being applied.
# 2022-04-18: allow minimum "L"-period top-up by day of week, eg for bath day!
# 2022-04-07: allow higher top-up in practice by re-arranging calculations.
# 2022-04-04: removing B flag when battery inhibition removed by TUMOD.
# 2022-04-02: indicating in HTML page what top-up would be, when allowed.
# 2022-03-15: with enough TUMODLOGCOUNT then ACB status need not inhibit topup.
# 2022-03-14: multi-day feedback to leave enough space for most diversion.
# 2022-03-09: shortened stale log file limit for Enphase and Eddi to 20 minutes.
# 2022-03-07: top-up higher if Nov/Dec/Jan.
# 2022-03-07: trimming top-up % boost when int threshold about to fall.
# 2022-02-23: added ES eddi-status field to end of log line.
# 2022-02-20: allowing higher top-up the further GI is below IT.
# 2022-02-20: removed downward IT dither complexity.
# 2022-02-15: reduced amount by which MT is reduced eg for AC Battery non-empty.
# 2022-02-14: making script testable (and creating unit-test script alongside).
# 2022-02-06: check that both intensity and battery stats are not stale.
# 2022-02-04: allow system to possibly top-up even if no grid intensity data.
# 2022-02-04: allow system to top-up in 'L' hours even if Enphase data absent.
# 2022-02-02: changed poor-forecast uplift in top-up max % from +5% abs to *1.2.
# 2022-02-02: added 'H'/'-'/'M'/'L' grid demand flags now shown explicitly.
# 2022-02-02: added 'R' grid red intensity flag.
# 2022-02-02: added 'G' super-green grid intensity flag.
# ...
# 2022-01-29: created


# VERSION
IDINTENSd: heat-battery-target.sh 48495 2022-11-05 13:42:01Z dhd $ #
