#!/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 task(s):
#   * Stop PV diversion to heater 1 when heater 2 is priority.
#   * Stop all PV diversion when mains voltage is low.
#   * Stop all PV diversion when grid carbon intensity is at 7d peak (red).
#   * Stop all PV diversion at grid peak times.

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

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

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


# SECONDARY TASK(S): STOPPING UNWANTED PV DIVERSION
# Only tried 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.
if [ "4" != "$STATUS" ] && [ "" != "$(echo "$RESPONSE" | grep -E '"grd":-')" ]; then
    # Test secondary conditions roughly most interesting first,
    # though with an eye to minimising CPU and I/O demand.

    # Prevent diversion when grid carbon intensity is at 7d peak (red).
    if [ -f _gridCarbonIntensityGB.7d.red.flag ]; then
        echo "STOPPED: grid 7d red carbon intensity" 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 2360 ]; then
        echo "STOPPED: mains voltage $VOLTS low" 1>&2
        if [ "6" != "$STATUS" ]; then sh script/myenergi/eddiStop-netrc.sh 0; fi
        exit 1
    fi

    # Prevent diversion in grid peak hours.
    # 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)"
    EXPGRIDDEMAND="$(awk -F, '$1=='"$LHOUR"'{l=$2;if(""==l){l="-"};print l}' \
        <$CONTROLPARAMSDIR/storage-charge-pref-by-hour-local-time.csv)"
    if [ "H" = "$EXPGRIDDEMAND" ]; then
        # High (peak!) grid demand hours: spill to grid - do not divert.
        echo "STOPPED: grid peak demand time" 1>&2
        if [ "6" != "$STATUS" ]; then sh script/myenergi/eddiStop-netrc.sh 0; fi
        exit 1
    fi

    # Prevent diversion to DHW cylinder when it is not the priority.
    if [ "" != "$(echo "$RESPONSE" | grep -E '"hno":1')" ] && \
        [ "" != "$(echo "$RESPONSE" | grep -E '"hpri":2')" ]; then
        echo "STOPPED: diversion to DHW cylinder not allowed" 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
        # Resume after a short random delay to be grid-kind.
        sleep "$(awk 'BEGIN{srand(); print int(25*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 now.

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

echo "STOPPED: frequency too low at ${FREQ}Hz" 1>&2

# LOG A LINE TO A SPECIAL 'STOP' LOG
# Output live log directory.
LOGDIR=data/eddi/log/live
# 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 log file.
LOGFILE="${LOGDIR}/${UTCDATE}.freqResp.log"
# Construct a log record with the time stamp down to seconds and the eddi line.
LOGRECORD="$(echo "$ISOUTCDATETIME $RESPONSE" | sed -e 's/"sno":[^,}]*\([,}]\)/"sno":0\1/')"
echo "$LOGRECORD" >> "$LOGFILE"
/bin/chmod a+r,o-wx "$LOGFILE"

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