#!/bin/sh
# Primary task:
# if the Eddi is diverting or boosting and *grid frequency is low*, pause.
# Logs for each time it is called when grid frequency is too low.
# Picks up credentials from ~/.netrc so may not be suitable for batch.
#
# Should run at least once per minute so needs to be efficient.
# But round-tripping across the Internet to do this is a bit potty...

# Usage:
#     $0 

# If status is 3 (divert), 4 (boost), or 6 (paused) check the frequency.
# If status is 3 or 4 and frequency too low (<49.9Hz) then pause.
#     Will report on stderr when pausing.
# EXCEPTION: not if 4 and "rbt" >= 1000 (s, ~17min) as may be manual boost.
# If status is 6 and frequency OK (>=49.9Hz) then unpause.
# This aims to send a minimum number of stop/resume commands.

# The GB National Grid Dynamic Containment deadband is down to 49.985Hz,
# with the knee point at 49.8Hz and full activation at 49.5Hz.
# https://www.nationalgrideso.com/document/175301/download
# So a threshold of 49.9Hz is in the gentle 'small linear delivery' region.

# Secondary tasks to stop PV diversion when:
#   * to heater 1 while heater 2 is priority,
#   * when mains voltage is low,
#   * when grid carbon intensity is at 7d peak (red),
#   * at grid/SSES peak times,
#   * when the heat battery is priority and not cold,
#         and not yet around solar noon or the grid is not (7-day) green.

# NOTE: when doing DHW pasteurisation with heater 1, most stops may be ignored.


# Output live log directory.
LOGDIR=data/eddi/log/live

# DISFLAG if file exists then disable this script.
DISFLAG="$LOGDIR/.disableFreqResponse.flag"
if [ -f "$DISFLAG" ]; then exit 0; fi


RESPONSE="$(sh script/myenergi/eddiStatsAll-netrc.sh)"

# Status, if it is one that we care about, else "".
STATUS="$(echo "$RESPONSE" | sed -n -e 's/^.*\"sta\":\([346]\)[^0-9].*$/\1/p')"

# Nothing to do this minute: exit!
if [ "" = "$STATUS" ]; then exit 0; fi


# Approximate maximum delay (-1) in seconds when randomising STOP/unSTOP.
# Helps to reduce herding effects, for which bigger is better.
# Must be <<60s because cron runs every minute.
# Must allow time for a couple of network actions/timeouts.
MAXDELAY=43


# Compute UTC YYYYMMDD date to match data logging.
UTCDATE="$(date -u '+%Y%m%d')"
ISOUTCDATETIME="$(date -u '+%Y-%m-%dT%H:%M:%SZ')"

# Today's general log file.
STOPLOGFILE="${LOGDIR}/${UTCDATE}.stop.log"
if [ -f "$STOPLOGFILE" ]; then /bin/chmod a+r,o-wx "$STOPLOGFILE"; fi

# Grid flow: -ve is spill to grid.
GRD="$(echo "$RESPONSE" | sed -n -e 's/^.*\"grd\":\([-0-9]*\)[^-0-9].*$/\1/p')"


# SECONDARY TASK(S): STOPPING UNWANTED PV DIVERSION
# Only when already spilling to grid, ie "grd":-NNN is present in status,
# as this indicates that there is PV generation in progress,
# and this should stay negative most of the time diverting or not.
# Do not interrupt any boost (4); may stop/continue current diversion/stop.
# Once per hour skip this whole block to allow Eddi probe/reset.
if [ "4" != "$STATUS" ] && [ "$GRD" -lt 0 ] && [ "11" != "$(date '+%M')" ]; then
    # Test secondary conditions roughly most interesting first,
    # though with an eye to minimising CPU and I/O demand.

    # Try to maximise chance of pasteurisation cycle completing in one day.
    if [ "" != "$(echo "$RESPONSE" | grep -E '"hno":1')" ] && \
        [ "" != "$(echo "$RESPONSE" | grep -E '"hpri":1')" ]; then
        if [ "6" = "$STATUS" ]; then sh script/myenergi/eddiStop-netrc.sh 1; fi
        exit 0
    fi

    # Prevent diversion when grid carbon intensity is at 7d peak (red).
    # Reduces grid and our emissions.
    if [ -f _gridCarbonIntensityGB.7d.red.flag ]; then
        echo "STOPPED $ISOUTCDATETIME ${GRD}W grid 7d red carbon intensity" | tee -a "$STOPLOGFILE" 1>&2
        if [ "6" != "$STATUS" ]; then sh script/myenergi/eddiStop-netrc.sh 0; fi
        exit 1
    fi

    # Prevent diversion when grid voltage is low.
    VOLTS="$(echo "$RESPONSE"|sed -n -e 's/^.*"vol":\([0-9]*\)[^0-9].*$/\1/p')"
    if [ "$VOLTS" -lt 2355 ]; then
        echo "STOPPED $ISOUTCDATETIME ${GRD}W voltage low $VOLTS" | tee -a "$STOPLOGFILE" 1>&2
        if [ "6" != "$STATUS" ]; then sh script/myenergi/eddiStop-netrc.sh 0; fi
        exit 1
    fi

    # Prevent diversion in grid/SSES peak hours.
    # Maximise power export to the grid to help cover GB peak demand.
    # Control parameters.
    CONTROLPARAMSDIR=data/heatBattery/16WWDHW
    # Local timezone.
    LZONE="Europe/London"
    # Local time (with DST etc) hour of day for 16WW.
    LHOUR="$(TZ="$LZONE"; export TZ; date +%H)"
    # GB grid (evening) peak.
    #PEAKS="$CONTROLPARAMSDIR/storage-charge-pref-by-hour-local-time.csv"
    # SSES GB grid peaks.
    PEAKS="$CONTROLPARAMSDIR/SSES-peak-hours.csv"
    EXPGRIDDEMAND="$(awk -F, '$1=='"$LHOUR"'{l=$2;if(""==l){l="-"};print l}' \
        <"$PEAKS")"
    if [ "H" = "$EXPGRIDDEMAND" ]; then
        # High (peak!) grid demand hours: spill to grid - do not divert/boost.
        # Grid-kind anti-herding: dither entering STOP mode.
        DELAY="$(awk 'BEGIN{srand(); print 1+int('"$MAXDELAY"'*rand());}')"
        sleep "$DELAY"
        echo "STOPPED $ISOUTCDATETIME ${GRD}W grid peak demand time (delayed)" | tee -a "$STOPLOGFILE" 1>&2
        if [ "6" != "$STATUS" ]; then sh script/myenergi/eddiStop-netrc.sh 0; fi
        exit 1
    fi

    # Hold some heat battery diversion until about solar noon once not cold,
    # to flatten our solar export peak.
    # Also prevents diversion once not cold unless grid green (7d),
    # to maximise exergy use from PV generation.
    if [ -f "/tmp/heat_battery_not_cold.flag" ] && \
       [ "" != "$(echo "$RESPONSE" | grep -E '"hpri":2')" ]; then
       if [ -f "_gridCarbonIntensityGB.7d.flag" ] || \
           [ "$(TZ=UTC; export TZ; date "+%H%M")" -lt 1156 ]; then
            echo "STOPPED $ISOUTCDATETIME ${GRD}W postponing Thermino until solar noon or grid 7d green" | tee -a "$STOPLOGFILE" 1>&2
            if [ "6" != "$STATUS" ]; then sh script/myenergi/eddiStop-netrc.sh 0; fi
            exit 1
        fi
    fi

    # Prevent diversion to DHW cylinder when it is not the priority.
    # Reduces standing losses.
    if [ "" != "$(echo "$RESPONSE" | grep -E '"hno":1')" ] && \
        [ "" != "$(echo "$RESPONSE" | grep -E '"hpri":2')" ]; then
        echo "STOPPED $ISOUTCDATETIME ${GRD}W diversion to DHW cylinder not allowed" | tee -a "$STOPLOGFILE" 1>&2
        if [ "6" != "$STATUS" ]; then sh script/myenergi/eddiStop-netrc.sh 0; fi
        exit 1
    fi
fi
# Fall through to deal with primary task.
# (And undo any unneeded STOP.)


# PRIMARY TASK: MAINS FREQUENCY RESPONSE
# Mains frequency, decimal Hz, eg "50.08".
# Lower operational limit is 49.8Hz.
# This pauses the Eddi below 49.9Hz which is ~6% of the time.
# (This unpauses when paused and above that floor...)
FREQ="$(echo "$RESPONSE"|sed -n -e 's/^.*\"frq\":\([1-9][0-9.]*\)[^0-9].*$/\1/p')"

# For safety, exit if FREQ could not be extracted.
if [ "" = "$FREQ" ]; then
    echo "ERROR: could not extract mains frequency from eddi stats" 1>&2
    exit 1
fi

if [ "" = "$(echo "$FREQ" | awk '$1 < 49.9 {print 1}')" ]; then
    # If frequency OK and was paused, then unpause/resume.
    if [ "6" = "$STATUS" ]; then
        if [ 0 -eq "$(awk 'BEGIN{srand(); print (rand()<0.5);}')" ]; then
            # Grid-kind anti-herding: sometimes defer unSTOP to next minute.
            echo "STOPPED $ISOUTCDATETIME ${GRD}W deferred-unSTOP" | tee -a "$STOPLOGFILE" 1>&2
            exit 1
        fi
        # Grid-kind anti-herding: dither leaving STOP mode.
        sleep "$(awk 'BEGIN{srand(); print 1+int('"$MAXDELAY"'*rand());}')"
        sh script/myenergi/eddiStop-netrc.sh 1
    fi
    # Frequency OK, not paused, doesn't need anything done.
    exit 0
fi

# Frequency too low but do not pause as in a long manual emergency boost.
if [ "4" = "$STATUS" ]; then
    RBT="$(echo "$RESPONSE"|sed -n -e 's/^.*\"rbt\":\([0-9]*\)[^0-9].*$/\1/p')"
    if [ "$RBT" -gt 999 ]; then
        echo "NOT STOPPED: manual boost in progress (though ${FREQ}Hz)" 1>&2
        exit 0;
    fi
fi

# Frequency too low, pause/stop immediately.
# Don't need to send stop command again if already stopped.
if [ "6" != "$STATUS" ]; then
    sh script/myenergi/eddiStop-netrc.sh
fi

echo "STOPPED $ISOUTCDATETIME ${GRD}W frequency low at ${FREQ}Hz" | tee -a "$STOPLOGFILE" 1>&2


# LOG A LINE TO A SPECIAL FREQUENCY 'STOP' LOG
# This will not happen when something other than low frequency caused STOP.
#
# Today's frequency log file.
FREQLOGFILE="${LOGDIR}/${UTCDATE}.freqResp.log"
# Construct a log record with the time stamp down to seconds and the eddi line.
FREQLOGRECORD="$(echo "$ISOUTCDATETIME $RESPONSE" | sed -e 's/"sno":[^,}]*\([,}]\)/"sno":0\1/')"
echo "$FREQLOGRECORD" >> "$FREQLOGFILE"
/bin/chmod a+r,o-wx "$FREQLOGFILE"

# Exit with error code.
exit 1


# Typical status line:
#% time sh script/myenergi/eddiStatsAll-netrc.sh
#{"eddi":[{"deviceClass":"EDDI","sno":0,"dat":"12-05-2025","tim":"08:49:35","ectp1":911,"ectp2":-360,"ectp3":13,"ectt1":"Internal Load","ectt2":"Grid","ectt3":"Monitor","bsm":0,"bst":0,"div":911,"frq":50.03,"gen":0,"grd":-364,"pha":1,"pri":1,"sta":3,"tz":0,"vol":2371,"che":0.82,"isVHubEnabled":false,"hpri":2,"hno":2,"ht1":"Tank 1","ht2":"Tank 2","r1a":0,"r2a":0,"rbc":0,"tp1":127,"tp2":127,"batteryDischargeEnabled":false,"g100LockoutState":"NONE","cmt":254,"fwv":"3200S5.427","newAppAvailable":false,"newBootloaderAvailable":false,"productCode":"3200"}]}



##########
# 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
##########
