#!/bin/sh

# Designed to be polled slowly as a cron job, ie every few minutes.

# Checks the k8055 card/port 0, if any,
# and writes the input values to a k8055.out file
# or removes it if not available.

# Also transfers the current status of the output.flags/[0-7].flag
# values to the digital outputs.
# If a numeric flag (starts 0. to 7.) is present then the output is turned off
# (except as adjusted by the modifiers table if present,
# which may also enforce minimum on/off times, etc).

# Unless the input voltage as measured at A1 is acceptably high
# ie the battery has a reasonable state of charge
# then this sets a EXTERNAL_BATTERY_LOW.flag for use by various systems
# and forces a sync to storage to help minimise data loss if power fails
# and tries to turn down power in some other subsystems.
# This flag is set by default as a precaution if monitoring in not possible.
# 
# We also set the SheevaPlug blue light to off for low/unknown.
# heartbeat for normal,
# steady for high.

# On any significant change in external power availability
# between one sample and the next this may set the EXT_POWER_CHANGE.flag
# to help keep the system stable.

# Directory to set/clear flags in.
# Should be readable by everyone, and writable by this script,
# but not writable by everyone.
WORKDIR=/var/run

# Log external battery state-of-charge (SOC) good flag (has been over float).
BATTGOOD=${WORKDIR}/EXTERNAL_BATTERY_GOOD.flag
# Log external battery state-of-charge (SOC) warning flag.
BATTWARN=${WORKDIR}/EXTERNAL_BATTERY_LOW.flag
# Log external battery state-of-charge (SOC) warning flag.
BATTVLOW=${WORKDIR}/EXTERNAL_BATTERY_VLOW.flag

# External flag set if PV generation forecast is good for next 24h+.
# Eg prime from weather forecast for next day...
#   <title>Wednesday: Thick Cloud, Maximum Temperature: 15°C (59°F) Minimum Temperature: 13°C (55°F)</title>
#47 11 * 11,12,1 * rm /var/run/FORECAST_PV_GEN_GOOD.flag
#47 6,11,21 * 2-10 * if [ "sunny" != "`wget -q -O - http://open.live.bbc.co.uk/weather/feeds/en/kt1/3dayforecast.rss | awk '/<title>.*emperature/ { if(++count==2) {if($0 ~ /[Ss]unny/){printf("sunny");}exit;} } '`" ] ; then rm -f /var/run/FORECAST_PV_GEN_GOOD.flag; else touch /var/run/FORECAST_PV_GEN_GOOD.flag; fi
FORECASTGENGOOD=${WORKDIR}/FORECAST_PV_GEN_GOOD.flag

# If the '-nolog' flag is set, we do not log the data sample.
# This can be useful during testing for example.
NOLOG="false"
if [ "X$1" = "X-nolog" ]; then
    NOLOG="true"
    shift
elif [ -e $BATTVLOW ]; then
    if [ "X`find $BATTVLOW -mmin -9`" != "X" ]; then
        # When very short of energy we avoid doing expensive logging too often.
        # Forbid a re-run in much less than 9 minutes.
        exit 0
    fi
fi

# Directory of flags to convey to the k8055 board's digital outputs.
# Note that flag zero is ignored and generated internally below.
OUTBITSDIR=/local/k8055/outbits

# Bit map of flags; default is all off.
outbitsValue=0

# SPECIAL CASE FOR BIT 0 FOR EFFICIENCY.
# Set bit/output 0 high if battery status has been good for >>1h, else clear.
# Use to turn on external dump loads such as ADSL/modem.
if [ -f $BATTGOOD ] && [ ! -z "`find $BATTGOOD -mmin +71`" ]; then
    outbitsValue=1
fi

# Assemble output word to send to output latch while collecting input data.
#if [ -e "$OUTBITSDIR/1.flag" ]; then outbitsValue=`/usr/bin/expr   2 + $outbitsValue`; fi
#if [ -e "$OUTBITSDIR/2.flag" ]; then outbitsValue=`/usr/bin/expr   4 + $outbitsValue`; fi
#if [ -e "$OUTBITSDIR/3.flag" ]; then outbitsValue=`/usr/bin/expr   8 + $outbitsValue`; fi
#if [ -e "$OUTBITSDIR/4.flag" ]; then outbitsValue=`/usr/bin/expr  16 + $outbitsValue`; fi
#if [ -e "$OUTBITSDIR/5.flag" ]; then outbitsValue=`/usr/bin/expr  32 + $outbitsValue`; fi
#if [ -e "$OUTBITSDIR/6.flag" ]; then outbitsValue=`/usr/bin/expr  64 + $outbitsValue`; fi
#if [ -e "$OUTBITSDIR/7.flag" ]; then outbitsValue=`/usr/bin/expr 128 + $outbitsValue`; fi
#echo OUTBITS value $outbitsValue


# k8055 binary.
KBIN=/usr/local/bin/k8055

# Data output file.
DATAOUT=${WORKDIR}/k8055.out
# Previous output file, if any.
DATABAK=${WORKDIR}/k8055.bak



# Log directory for long-term storage.
LOGDIR=/local/k8055/data

# Power-change flag.
PWRCHG=${WORKDIR}/EXT_POWER_CHANGE.flag

# Default LED to off if we're not in a known-good state.
echo "none" > /sys/class/leds/plug\:green\:health/trigger

# If executable is missing, remove data file and quit.
# Set all the warning flags as a precaution.
if [ ! -x $KBIN ]; then
    /bin/rm -f $DATAOUT $DATABAK $BATTGOOD
    echo UNKNOWN > $BATTWARN
    echo UNKNOWN > $BATTVLOW
    #echo UNKNOWN > $PWRCHG
/etc/suspend_inactive_DT200.sh powerdown > /tmp/DT200-k8055.log
    exit 1
fi

# Now try to ensure our power is on and the device is awake...
# Don't autosuspend immediately (0) as this seems to cause unreliability.
# Note the silly trailing space in the manufacturer name.
DEV=`/etc/findUSBdir.sh -sloppy "Velleman " "USB K8055"`
if [ "x$DEV" != "x" -a -d "$DEV" ]; then
    echo 1 > $DEV/power/autosuspend
    echo auto > $DEV/power/level
fi

# If we can't collect any data from port 0, remove data file and quit.
# Set all the warning flags as a precaution.
DATA="`$KBIN -port:0 -d:$outbitsValue 2>/dev/null`"
DATANF=`echo "$DATA" | /usr/bin/awk '-F;' '{print NF;}'`
if [ "6" != "$DATANF" ]; then
    /bin/rm -f $DATAOUT $DATABAK $BATTGOOD
    echo UNKNOWN > $BATTWARN
    echo UNKNOWN > $BATTVLOW
    #echo UNKNOWN > $PWRCHG
/etc/suspend_inactive_DT200.sh powerdown > /tmp/DT200-k8055.log
    exit 1
fi

# Move aside previous data file, if any.
if [ -f $DATAOUT ]; then /bin/mv -f $DATAOUT $DATABAK; fi

# Atomically update captured data.
# Format: (timestamp);(digital);(analog 1);(analog 2);(counter 1);(counter 2)
echo "$DATA" > $DATAOUT.tmp && /bin/mv -f $DATAOUT.tmp $DATAOUT

# Store log files iff output directory exists.
# (And if not explicitly disabled for this run.)
# Only claim resolution to the minute.
if [ "$NOLOG" != "true" -a -d ${LOGDIR} ]; then
    # Store archive/dated copy of data.
    FINALLOGDIR="${LOGDIR}/`date -u +%Y/%m/%d`"
    if [ ! -d ${FINALLOGDIR} ]; then
        mkdir -p ${FINALLOGDIR}
    else
	# Ensure the month dir updates for each point added to it
	# to help track dependencies (at the cost of some disc traffic).
        touch `dirname ${FINALLOGDIR}`
    fi
    chmod a+rx ${FINALLOGDIR}
    FINALLOGFILE="${FINALLOGDIR}/`date -u +%H%M`.dat"
    echo "$DATA" > $FINALLOGFILE
    chmod a+r $FINALLOGFILE
    # Atomically update 'last' data received AFTER the archive copy
    # so Make (etc) can watch this file's timestamp.
    echo "$DATA" > ${LOGDIR}/last.dat.tmp
    mv ${LOGDIR}/last.dat.tmp ${LOGDIR}/last.dat
fi

## Update 'change' before main status to avoid races when ramping up performance.
## If the digital input counters for bits 0 and 1 have changed since last time
## indicating transitions between different battery SOC (states of charge)
## (or that there was no previous data sample)
## then flag the power status as (significantly) changed.
## The fields checked are 5 and 6, eg "3339" and "201" in
## the sample "271;0;134;133;3339;201".
#if [ ! -f $DATABAK ]; then
#    # No previous sample available, so flag as a change.
#    echo MISSING > $PWRCHG
#elif [ "`awk < $DATAOUT '-F;' '{print $5, $6}'`" != "`awk < $DATABAK '-F;' '{print $5, $6}'`" ]; then
#    # Flag a change.
#    echo CHANGE > $PWRCHG
#else
#    # No significant change from previous value.
#    /bin/rm -f $PWRCHG
#fi

# Meaning of various inputs as of 20090922:
# Digital input (bit 0 is least significant):
#   0: Not assigned.
#   1: Not assigned.
#   2: Not assigned.
#   3: True/1 if output from the wind turbine is available.
#   4: Not yet assigned, so should always be false/0.
# Analogue input:
#   1: Approx battery/supply volts in 0.1V, eg 125 indicates ~12.5V.
#   2: Not assigned.

# Meaning of various inputs as of 20070904:
# Digital input (bit 0 is least significant):
#   0: True/1 if battery voltage indicates good charge and currently charging.
#   1: Not assigned.
#   2: True/1 if input to DC-DC converter is good (no low-voltage disconnect).
#   3: True/1 if output from the wind turbine is available.
#   4: Not yet assigned, so should always be false/0.

# Primary/SLA (gel) battery voltage thresholds in units of 0.1V.
# At/above 'BATTVH' (very high) means in absorption (a little below 14.0V).
# Above 'High' means mainly full and charging/floating.  ~13.45V at 80% SoC.
# DHD20120725: set at 13.2V to allow for summer temperature compensation.
# Above 'Low' means probably reasonably full and/or charging but no 'excess'.
# At or below 'BATTVL' (very low) means urgent conservation needed.
# See LA SoC chart: http://solarjohn.blogspot.com/2007/03/measuring-battery-state-of-charge.html
BATTVH=138
BATTH=132
# A BATTL of 12.6V to 12.8V is 100%-full resting/light-discharge threshold.
BATTL=126
# 12.1V agrees with amber light on MS MPPT ctrl but only leaves ~1 day @ 40Ah.
# 12.2V suggests about 50% SoC minus slight drain by plug.
# 12.4V suggests maybe 60% SoC minus slight drain by plug.
BATTVL=123

# Secondary/LiFePO4 battery voltage thresholds in units of 0.1V.
# At or below 'BATTL2' the battery is not full nor nearly full.
# At or below 'BATTVL2' the battery is nearly empty.
BATTL2=131
BATTVL2=127

# Unless generation forecast is good then adjust thresholds to conserve further.
if [ ! -e $FORECASTGENGOOD ]; then
    BATTH=133
    BATTL=128
    BATTVL=126

    BATTL2=132
    BATTVL2=130
fi

# Extract the primary/SLA battery voltage.
BATTV=`echo "$DATA" | /usr/bin/awk '-F;' '{ print $3; }'`

# Extract the secondary/LiFePO4 battery voltage.
BATTV2=`echo "$DATA" | /usr/bin/awk '-F;' '{ print $4; }'`

# Put the battery voltage (in 0.1v) where it can be seen...
echo $BATTV > /var/run/BATTV.new
chmod 644 /var/run/BATTV.new
mv /var/run/BATTV.new /var/run/BATTV
chmod 644 /var/run/BATTV

# This is written to minimise the code to be executed when power is low.
if [ "$BATTV" -le "$BATTVL" -o "$BATTV2" -le "$BATTVL2" ]; then
    # Either battery very low: urgent conservation required.
    # Ensure set LOW and VLOW flags.
    # Always at least touch $BATTVLOW to enable minimum poll interval mechanism.
    if [ ! -e $BATTVLOW ]; then echo VLOW > $BATTVLOW; else touch $BATTVLOW; fi
    if [ ! -e $BATTWARN ]; then echo LOW > $BATTWARN; fi
    # Unconditionally remove the GOOD flag.
    /bin/rm -f $BATTGOOD
    # As a precaution, in case power will soon fail,
    # flush disc data to permanent storage now.
    # Flash the LED on while we're doing it...
    echo "default-on" > /sys/class/leds/plug\:green\:health/trigger
    sync
    echo "none" > /sys/class/leds/plug\:green\:health/trigger
elif [ "$BATTV" -le "$BATTL" -o "$BATTV2" -le "$BATTL2" ]; then
    # No 'excess' (but SoC OK), eg batteries not full and/or sun not out.
    # Unconditionally remove the GOOD and VLOW flags.
    /bin/rm -f $BATTGOOD $BATTVLOW
    # Set LOW flag if absent.
    if [ ! -e $BATTWARN ]; then echo LOW > $BATTWARN; fi
    # Turn the light off.
    echo "none" > /sys/class/leds/plug\:green\:health/trigger
elif [ "$BATTV" -gt "$BATTH" ]; then
    # Excess: batteries full and plenty more energy available.
    # Unconditionally remove LOW and VLOW flags.
    /bin/rm -f $BATTWARN $BATTVLOW
    # Set 'good' flag if above float...
    # Only touch if not present to allow measuring its age...
    if [ "$BATTV" -ge "$BATTVH" -a ! -e $BATTGOOD ]; then touch $BATTGOOD; fi
    # Show a full-on light to show that all is well (but burn some juice).
    echo "default-on" > /sys/class/leds/plug\:green\:health/trigger
else
    # In between low and high thresholds.
    # Unconditionally VLOW flag.
    /bin/rm -f $BATTVLOW
    # For hysteresis,
    # leave the LOW flag set or unset as per last excursion high or low.
    # If 'high' then show a heartbeat light to indicate temporary sag...
    # If 'low' then turn the light off.
    #if [ ! -e $BATTWARN ]; then
    #    echo "heartbeat" > /sys/class/leds/plug\:green\:health/trigger
    #else
        echo "none" > /sys/class/leds/plug\:green\:health/trigger
    #fi
fi


# Now try to reduce power consumed by storage/USB
# (because polling k8055 seems to wake it up)
# unless we've really got lots of excess juice in the system to dump.
if [ "$BATTV" -le "$BATTH" ]; then
    /etc/suspend_inactive_DT200.sh powerdown > /tmp/DT200-k8055.log
fi

# When we've finished, try to ensure that our consumption is as low as possible.
if [ "x$DEV" != "x" -a -d "$DEV" ]; then
    echo 1 > $DEV/power/autosuspend
    echo auto > $DEV/power/level
fi

exit 0
