#!/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) warning flag.
BATTWARN=${WORKDIR}/EXTERNAL_BATTERY_LOW.flag
# Log external battery state-of-charge (SOC) warning flag.
BATTVLOW=${WORKDIR}/EXTERNAL_BATTERY_VLOW.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.
OUTBITSDIR=/local/k8055/outbits

# First assemble output word to send while collecting input data.
outbitsValue=0
#for i in 7 6 5 4 3 2 1 0; do
for i in 0; do
# For now only check one flag (0) for efficiency.
    if [ -e "$OUTBITSDIR/$i.flag" ]; then
        # If the flag exists then turn off the output.
        outbitsValue=`/usr/bin/expr 2 \* $outbitsValue`
    else
        # If the flag doesn't exist then turn on the output.
        outbitsValue=`/usr/bin/expr 1 + 2 \* $outbitsValue`
    fi
done
#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
    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
    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`"
    mkdir -p ${FINALLOGDIR}
    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.
# Above 'High' means mainly full and charging.
# Above 'Low' means probably reasonably full and charging but no 'excess'.
# At or below 'VLow' means urgent conservation needed.
# See LA SoC chart: http://solarjohn.blogspot.com/2007/03/measuring-battery-state-of-charge.html
BATTH=136
BATTL=130
# 12.1V agrees with amber light on MS MPPT ctrl but only leaves ~1 day.
# 12.4V roughly indicates 50% SoC minus slight drain by plug.
BATTVL=124

# Secondary/LiFePO4 battery voltage thresholds in units of 0.1V.
# At or below 'Vlow2' the battery is effectively exhausted.
BATTVL2=125

# 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.
    # Unconditionally set LOW and VLOW flags (if absent).
    # 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
    # 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" ]; then
    # No 'excess', eg batteries not full and/or sun not out.
    # Unconditionally set LOW flag (if absent) and remove VLOW flag.
    /bin/rm -f $BATTVLOW
    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
    # 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 remove the 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 $BATTLOW ]; 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
