/*
Damon Hart-Davis licenses this file to you
under the Apache Licence, Version 2.0 (the "Licence");
you may not use this file except in compliance
with the Licence. You may obtain a copy of the Licence at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the Licence is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the Licence for the
specific language governing permissions and limitations
under the Licence.

Author(s) / Copyright (s): Damon Hart-Davis 2014
*/

static const char *Id = "$Id: powermng.cpp 14763 2016-06-10 17:23:49Z dhd $";

/*
This is to be run periodically (every few minutes)
to monitor battery levels (and other factors)
and adjust flags and other system parameters to modulate consumption to match.

Stuff is a bit hard-wired here: this replaces a shell script that did the same.

Equivalent to /local/k8055/check-status.sh on SheevaPlug as of 2014/06
but designed to be much lighter on CPU, and also has some locking for safety.
This also gives decent 'verbose' logging of actions and reasons.
*/

/*
See also:
http://hertaville.com/2013/04/01/interfacing-an-i2c-gpio-expander-mcp23017-to-the-raspberry-pi-using-c/
https://code.google.com/p/mas-test-svn/source/browse/trunk/tests/i2c/i2c-base-test/i2c-test.c?r=8
http://unicorn.drogon.net/q2w.c
*/

#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#include <sys/ioctl.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>


#ifndef __cplusplus
typedef enum { false, true } bool;
#endif


// BASIC OPERATIONAL SWITCHES/FEATURES.
// If true then B2 voltage is read and used.
static const bool ENABLE_B2 = false;
// If true then B2 is included in system power status,
// eg very low B2 can prevent B1 dumping.
static const bool B2InSoC = false;
// If true then enable INA219 high-side current/power monitor.
static const bool ENABLE_INA219 = false;
// If true then enable the ambient light sensor.
static const bool ENABLE_AL = false;


// Directory for flags to check and set.
static const char FLAG_DIR[] = "/run";
#define FLAG_DIR_LEN 4

// Log directory.
// If absent, log is not done.
static const char *LOG_DIR = "/var/log/powermng";

// Exclusive lock file to serialise all device (and log) access.
// Behaviour is meant to approximate that of procmail lockfile.
// This process is only intended to need to hold a lock for << 1s.
static const char LOCK_FILE[] = "/var/lock/powermng.lock";
// Time after which lock is considered stale, in seconds.
static const int LOCK_STALE_S = 45;
// Suspend time after forcefully removing a lock, in seconds.
static const int LOCK_SUSPEND_S = 16;


// I2C device path (for RPi rev2 board).
static const char *I2C_FILEPATH = "/dev/i2c-1";
// ADC device 1 address (MCP3424, 4-channel, 1--4 on AB Electronics V2.2 board).
static const uint8_t ADC1_addr = 0x6a;
// ADC device 2 address (MCP3424, 4-channel, 5--8 on AB Electronics V2.2 board).
static const uint8_t ADC2_addr = 0x6b;
// Zero-based channel on ADC2 used for LDR to 5V for crude day/night detector.
static const int LDR_zch_ADC2 = 3;
// Zero-based channel on ADC2 used for Batt2 sense input.
static const int BATT2_zch_ADC2 = 0;
// Zero-based channel on ADC1 used for Batt1 sense input.
static const int BATT1_zch_ADC1 = 0;
// INA219 high-side current/power monitor address.
static const int INA219_addr = 0x40;


// Maximum allow base flag name length (ignoring trailing \0).
#define MAX_FLAG_NAME 64

// Input flag to indicate sunny(ish) weather forecast for next day.
static const char *FLAGI_FORECASTGENGOOD = "FORECAST_PV_GEN_GOOD";

// Input flag to indicate no dumping allowed (volatile; disappears at reboot).
static const char *FLAGI_NODUMP = "NODUMP";
// Input flag to indicate GB grid peak (mainly winter weekday evenings).
// When true then make best efforts to move 'dump' load off grid.
static const char *FLAGI_GBGRIDPEAK = "GBGRIDPEAK";
//// Output flag to indicate daylight (bright enough not to require desk lamp).
//static const char *FLAGO_DAYLIGHT = "DAYLIGHT";
// Output flag to indicate one or both batteries very high, no hysteresis.
// Typically in bulk/absoption charging phase.
static const char *FLAGO_BATTVHIGH = "EXTERNAL_BATTERY_VHIGH";
// Output flag to indicate one or both batteries 'high', no hysteresis.
// If true then likely charging and fairly full.
static const char *FLAGO_BATTHIGH = "EXTERNAL_BATTERY_HIGH";
// Convenience reverse of FLAGO_BATTVHIGH, no hysteresis.
// Exists for benefit of subsystems that use presence of flag as lack of juice.
static const char *FLAGIO_BATTNOTHIGH = "EXTERNAL_BATTERY_NOTHIGH";
// Output flag to indicate one or both batteries low, no hysteresis.
static const char *FLAGO_BATTLOW = "EXTERNAL_BATTERY_LOW";
// Output flag to indicate one or both batteries very low, no hysteresis.
static const char *FLAGO_BATTVLOW = "EXTERNAL_BATTERY_VLOW";
// Input/output (persistent/hysteresis) flag to indicate mild energy dump
// from B1 (moving load off-grid).
static const char *FLAGIO_DUMPING = "DUMPING";
// Input/output (persistent/hysteresis) flag to note when B1 dumping stopped.
static const char *FLAGIO_DUMPINGEND = "DUMPINGEND";
// Pseudo-flag containing last data set collected, written unless 'no-log' mode.
// Mainly to be used for the timestamp;
// this may be truncated and re-written while being read.
static const char *FLAGO_LASTDATA = "LASTDATA";

// Externally-provided flags about (GB) grid status.
// If no flags are present then grid is in a 'normal' state.
// If flag is present then grid is at its most carbon intensive.
// NOTE sense is opposite of 'green' flags in greenness terms.
static const char *gridRedFlag = "/rw/docs-public/www.hd.org/Damon/Env/_gridCarbonIntensityGB.red.flag";
// If flag is present then grid is not in lowest-carbon-inrensity region.
//static const char *gridNotGreenFlag = "/rw/docs-public/www.hd.org/Damon/Env/_gridCarbonIntensityGB.flag";
// If flag is present then grid is not in lowest-carbon-inrensity region
// or bulk grid storage is being drawn from (eg pumped hydro).
//static const char *gridNotVeryGreenFlag = "/rw/docs-public/www.hd.org/Damon/Env/_gridCarbonIntensityGB.supergreen.flag";

// External flag for microgeneration / dark / exports / imports.
// If flag is present then it seems to be dark, with no PV generation.
static const char *darkFlag = "/var/log/SunnyBeam/DARK.flag";
// External flag indicating microgen too low to cover base loads.
static const char *lowGenFlag = "/var/log/SunnyBeam/LOW.flag";


// Threshold (on 11-bit sample) bright enough not to require desk lamp.
static const int LDR_daylight_thr = 650;

#if 1 // Direct to supply near RPi (not via signal cable between floors)...
// Volts per ulp for Batt1 and Batt1 (~9.382mV/ulp)
// with series resistance of 47k (off-board) + 10k (on-board) and 6.8k drop.
#define offBoardSeriesResistancek 47
#else // +ve sense connections made at service panel.
// Volts per ulp for Batt1 and Batt1 (~9.529mV/ulp)
// with resistance of 1k+47k (off-board) + 10k (on-board) and 6.8k drop.
#define offBoardSeriesResistancek 48
#endif
static const float mVperUlpBatt = (offBoardSeriesResistancek+10.0f+6.8f)/6.8f;

/* 2014/07/08 k8055/SheevaPlug voltage levels.
# Primary/SLA (gel) battery voltage thresholds in units of 0.1V.
# At/above 'BATTVH' (very high) means in absorption (a little below 14.0V)
# though maybe only ~13.6V in temperatures reaching 30C.
# 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=136
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=134
    BATTL=127
    BATTVL=125

    BATTL2=132
    BATTVL2=130
fi
*/

// Policy:
// 0) Primary aim is to support server load year round;
//    secondary is to support 'dump' load without hurting batteries.
// 1) not necessarily aiming to get to 100% SoC (VHIGH) whenever possible
//    as getting beyond ~80% SoC (HIGH) is releatively inefficient,
// 2) but want to get to HIGH for a few hours each day if possible for health,
// 3) and dumping excess productively at this point may be a good energy use,
// 4) but never want to go below 50% SOC (VLOW) to avoid damage.
//
// Note that the 'dump' load is assumed to be productive,
// eg taking a real load off-grid.
//
// Normal Battery 1 (gel lead-acid) thresholds.
// 12.1V agrees with amber light on MS MPPT ctrl but only leaves ~1 day @ 40Ah.
// 12.2V suggests about 50%/65% SoC unloaded/loaded (~empty if unloaded).
// 12.3V suggests maybe 65%/80% SoC unloaded/loaded.
// 12.4V suggests maybe 75%/90% SoC unloaded/loaded. Sulphation at/below this.
// 12.5V suggests maybe 85%/100% SoC unloaded/loaded (~full if loaded).
// 12.6V to 12.8V is 100%-full resting/light-discharge threshold.
// 13.1V charging and maybe 50% SoC.
// 13.3V charging and maybe 70% SoC.
// 13.4V charging and maybe <80% SoC.
// 13.6V charging and maybe 85% SoC.
// 13.7V charging and maybe 90% SoC or float.
// 14.0V charging and maybe 90+% SoC and gel absorption.
// At/above 13.1V means charging.
// At/above 13.6V means in bulk/absorption/float (actually a little below 14.0V,
// though maybe only ~13.6V in temperatures reaching 30C).
// Above 'High' means mainly full and charging/floating.  ~13.45V at 80% SoC.
// DHD20120725: set at ~13.2V to allow for summer temperature compensation.
// DHD20140813: if H thr much below 13.1V allows triggering in no sun.
// The 'not high' flag is an indication not to waste energy.
// The VL threshold may prematurely terminate overnight powering of (dump) load if too high.
// Roughy: 100mv = 10% SoC = 20% of usable capacity = 4 days for a 20-day store.
// VERY HIGH threshold: >90% and float/absortion charging.
static const int BATT1_THR_VH_mV = 13600;
// HIGH threshold: ~80% SoC, charging.  Above this charging is especially lossy.
static const int BATT1_THR_H_mV = 13450;
// LOW threshold: ~65%/80% SoC un/loaded.
static const int BATT1_THR_L_mV  = 12300;
// VERY LOW threshold: <50%/65% SoC un/loaded, with some line voltage drop.
static const int BATT1_THR_VL_mV = 12100;
// Allowance above LOW before allowing secondary/dump load ~20% of capacity.
enum { BATT1_DLdelta_mV = 50 }; // 50mV ~ 5% SoC ~ 10% cap ~ 2 days.
// Normal Battery 2 (LiPO4) thresholds.
// The 'not high' flag is an indication not to waste energy.
// HIGH threshold: ~100% SoC, no/light load.
static const int BATT2_THR_H_mV = 13180;
// VERY LOW threshold: nearly empty.
static const int BATT2_THR_VL_mV = 12900;

// Thresholds to use for a poor sunshine forecast tomorrow,
// to conserve more aggressively and protect against over-discharge.
// Fixed delta adjustment used for lead-acid, ~10% of capacity.
static const int BATT1_PFTHR_mV = 50; // 50mV ~ 5% SoC ~ 10% cap ~ 2 days.
// Li has flatter discharge curve...
static const int BATT2_PFTHR_H_mV = 13200;
static const int BATT2_PFTHR_VL_mV = 12950;

// Allowance in millivolts when dumping, ie added back to measured LA voltage,
// for some measurement levels.
// This drop is with a target controlled dump (additional) load of ~12W ie ~1A.
// There may be other manual loads such as phone recharging enabled with the controlled load.
// Nominally removed from measured voltage (ie as added safety margin)
// during daylight hours, ie when battery should be charging.
// DHD20150817: observed to be ~150mV with a load of ~8W; voltage recovery time ~40m after load dropped.
// DHD20160309: observed to be ~200mV with a load of ~12W/1A, though instantaneous dV more like ~150mv of which ~40mV is likely from wire run.
// DHD20160601: 299mV chosen to give some hysteresis with 'soggy' battery.
static const int BATT1_DUMPCORR_mV = 299;
// Allowance in millivolts when dumping, ie added back to measured Li voltage,
// for some measurement levels.
// This drop is with a larget dump (additional) load of ~2W.
static const int BATT2_DUMPCORR_mV = 99;

// Normal minimum number of minutes of LA VH (absorption) before starting dump.
// This is a continuous figure, so quite a tough metric.
// Nominally absorption could take up to 24h; not with PV!
// See: http://www.exide.com/Media/files/Downloads/TransAmer/Battery%20Care%20and%20Maintenance/Battery%20Charging%20&%20Storage%20Guidelines%20%205_9_13.pdf
// Many commercial solar controllers have a 2h limit, though not continuous.
static const int BATT1_MINVH_BEFORE_DUMP_M = 29;
// Minimum number of minutes of absorption before dump if poor forecast tomorrow.
static const int BATT1_PFMINVH_BEFORE_DUMP_M = 39;
// Minimum number of minutes at 'high' before dump if not enough absorption.
// Quite long as only a stop-gap if there isn't enough continuous good sun,
// and should allow decent carry-over of load beyond sun-down.
static const int BATT1_MINH_BEFORE_DUMP_M = 119;


// Path to file to set CPU-utilisation threshold (in %)
// Above which the ondemand governor raises the CPU frequency.
static const char *UP_THRESHOLD_PATH = "/sys/devices/system/cpu/cpufreq/ondemand/up_threshold";
// Default up-threshold value (%): RPi defaults to 60 or 70.
static const int DEFAULT_UP_THRESHOLD = 60;
// Power-conserving up-threshold value (%).
static const int CONSERVING_UP_THRESHOLD = 95;


// System mode flags.
static bool verbose = false;
static bool nolog = false;
static bool looping = false;
static bool sleepfirst = false;



// Echo/cat the supplied text into the extant file/device at the given path.
// Does not create the file if it does not exist.
// Returns true in case of success.
static bool doEcho(const char * const path, const char * const text)
    {
    assert(NULL != path);
    assert(NULL != text);
    const int tl = strlen(text);
    const int fd = open(path, O_WRONLY); // Don't create.
    if((fd < 0) ||
       (write(fd, text, tl) != tl))
        {
        fprintf(stderr, "Unable to echo to %s.\n", path);
        return(false);
        }
    close(fd);
    return(true);
    }


// Adjust ondemand governor's up_threshold, if possible.
static void adj_up_threshold(const uint8_t up_threshold)
    {
    if(verbose) { fprintf(stderr, "Setting up_threshold to %d%%\n", (int)up_threshold); }
    char buf[5]; // Big enough for "100\n\0".
    snprintf(buf, sizeof(buf), "%d\n", up_threshold);
    doEcho(UP_THRESHOLD_PATH, buf);
    }


// Write multiple bytes to the given 12c address.
// Returns true iff successful.
static bool write_i2c_bytes(const int fd, const uint8_t addr,
                            const int nbytes, const unsigned char * const buf)
    {
    struct i2c_rdwr_ioctl_data packet;
    struct i2c_msg message;
    message.addr = addr;
    message.flags = 0;
    message.len = nbytes;
    message.buf = (__u8 *) buf;
    packet.msgs = &message;
    packet.nmsgs = 1;
    if(ioctl(fd, I2C_RDWR, &packet) < 0)
        {
        fprintf(stderr, "write of %d i2c byte(s) failed (%d).\n", nbytes, errno);
        return(false);
        }
    return(true); // All apparently OK.
    }

// Write a single byte command to the given i2c address.
// Returns true iff successful.
static bool write_1_byte_command(const int fd,
                                 const uint8_t addr, const unsigned char value)
    { return(write_i2c_bytes(fd, addr, 1, &value)); }

// Read multiple bytes from specified i2c address.
// Buf must be large enough to receive nbytes of data.
// Returns true iff successful.
static bool read_i2c_bytes(const int fd, const uint8_t addr,
                           const int nbytes, unsigned char *buf)
    {
    struct i2c_rdwr_ioctl_data packet;
    struct i2c_msg message;
    message.addr = addr;
    message.flags = I2C_M_RD;
    message.len = nbytes;
    message.buf = buf;
    packet.msgs = &message;
    packet.nmsgs = 1;
    if(ioctl(fd, I2C_RDWR, &packet) < 0)
        {
        fprintf(stderr, "read of i2c byte(s) failed.\n");
        return(false);
        }
    return(true); // All apparently OK.
    }

// Read multiple bytes having written one (register) byte first.
static bool read_i2c_register(const int fd, const uint8_t addr,
                              const uint8_t reg,
                              const int nbytes, unsigned char *buf)
    {
    struct i2c_rdwr_ioctl_data packet;
    struct i2c_msg message[2];
    message[0].addr = addr;
    message[0].flags = 0;
    message[0].len = 1;
    unsigned char r = reg;
    message[0].buf = &r;
    message[1].addr = addr;
    message[1].flags = I2C_M_RD;
    message[1].len = nbytes;
    message[1].buf = buf;
    packet.msgs = message;
    packet.nmsgs = 2;
    if(ioctl(fd, I2C_RDWR, &packet) < 0)
        {
        fprintf(stderr, "read of i2c register failed.\n");
        return(false);
        }
    return(true); // All apparently OK.
    }


// Read the power cosumption from 12V (to 5V) in mW.
// Fills in busVoltage with the supply voltage at the shunt top-end.
// Returns -1 if unsuccessful.
static int getPowerDrain(const int fd, int *const busVoltageP)
    {
    if(!ENABLE_INA219) { return(-1); }

    unsigned char buf3[3];

#if 0
    // Reset.
    // Write 1 to RST bit in config register.
    buf3[0] = 0;
    buf3[1] = 0x80;
    buf3[2] = 0;
    if(!write_i2c_bytes(fd, INA219_addr, 3, buf3))
        {
        fprintf(stderr, "Failed reset of INA219 (%d).\n", errno);
        return(-1);
        }
    usleep(1000); // Allow think time..
#endif

    const uint16_t calibration = 8192;
    // Write calibration value.
    buf3[0] = 5;
    buf3[1] = (calibration >> 8);
    buf3[2] = (calibration & 0xff);
    if(!write_i2c_bytes(fd, INA219_addr, 3, buf3))
        {
        fprintf(stderr, "Failed config of INA219.\n");
        return(-1);
        }

    // Configure.
    // D15: RST = 0 (no reset)
    // D14: 0
    // D13: BRNG = 1 (Bus Voltage Range, 32V FSR)
    // D12--D11: PGA = 00 (PGA gain and range, +/- 40mV = 400mA w/ 100mR.
    // D10--D7: BADC = 0011
    // D6--D3: SADC = 0011 (12-bit samples, 532uS)
    // D2--D0: MODE = 011 (Shunt and bus, triggered).
    const uint16_t config = 0b0010000110011011;
    buf3[0] = 0;
    buf3[1] = (config >> 8);
    buf3[2] = (config & 0xff);
    if(!write_i2c_bytes(fd, INA219_addr, 3, buf3))
        {
        fprintf(stderr, "Failed config of INA219.\n");
        return(-1);
        }
    // Allow recovery (40us) and conersion (532us) time.
    usleep(1000);

    // Get bus voltage.
    // Spin (a little) until ready if need be.
    if(!read_i2c_register(fd, INA219_addr, 2, 2, buf3))
        {
        fprintf(stderr, "Failed bus voltage read from INA219.\n");
        return(-1);
        }
    const int bv = (((buf3[0] << 7) & 0x7f00) + ((buf3[1] >> 1) & (0x7e)));
    if(verbose) { fprintf(stderr, "INA219 bus voltage read %2x %2x = %d.\n", (int) buf3[0], (int) buf3[1], bv); }
    if(NULL != busVoltageP) { *busVoltageP = bv; }
    // TODO: check the CNVR (ready) bit 1; loop if 0.
    // TODO: check the OVR (overflow) bit 0; abort if 1.

#if 1
    if(verbose)
        {
        // Get shunt voltage.
        if(!read_i2c_register(fd, INA219_addr, 1, 2, buf3))
            {
            fprintf(stderr, "Failed shunt voltage read from INA219.\n");
            return(-1);
            }
        if(verbose) { fprintf(stderr, "INA219 shunt voltage read %2x %2x = %fV.\n", (int) buf3[0], (int) buf3[1], 10e-6f * ((((int8_t)buf3[0]) << 8) + (buf3[1] & 0xff))); }

        // Get current.
        if(!read_i2c_register(fd, INA219_addr, 4, 2, buf3))
            {
            fprintf(stderr, "Failed current read from INA219.\n");
            return(-1);
            }
        if(verbose) { fprintf(stderr, "INA219 current read %2x %2x = %fA.\n", (int) buf3[0], (int) buf3[1], 50e-6f * ((((int8_t)buf3[0]) << 8) + (buf3[1] & 0xff))); }
    }
#endif

    // Get power.
    if(!read_i2c_register(fd, INA219_addr, 3, 2, buf3))
        {
        fprintf(stderr, "Failed power read from INA219.\n");
        return(-1);
        }
    const int power = ((((int8_t)buf3[0]) << 8) + (buf3[1] & 0xff));
    if(verbose) { fprintf(stderr, "INA219 power read %2x %2x = %dmW.\n", (int) buf3[0], buf3[1], power); }

#if 1
    // Shut down (put into low-power mode).
    // Write 0 to config register.
    buf3[0] = 0;
    buf3[1] = 0;
    buf3[2] = 0;
    if(!write_i2c_bytes(fd, INA219_addr, 3, buf3))
        {
        fprintf(stderr, "Failed shutdown of INA219.\n");
        return(-1);
        }
#endif

    return(power);
    }


// Reads 11-bit value [0,2047], from specified ADC channel.
// Returns -1 in case of failure.
static int readADC(const int fd, const uint8_t addr, const uint8_t channel)
    {
    // Write single-byte command to do one-shot 12-bit conversion from ADC.
    const uint8_t wc = 0x80 | ((channel & 3) << 5);
    if(verbose) { fprintf(stderr, "ADC command %x %x\n", addr, wc); }
    if(!write_1_byte_command(fd, addr, wc)) { return(-1); }
    // Read 3-byte response (MMMMDDDD DDDDDDDD CCCCCCCC).
    // Spin a little until conversion is done (RDY bit low).
    unsigned char rdbuf[3];
    int i;
    for(i = 4; --i >= 0; )
        {
        // Sleep 5ms for conversion to complete (240SPS@12bits).
        usleep(5000);
        if(!read_i2c_bytes(fd, addr, 3, rdbuf))
            {
            fprintf(stderr, "Failed to read ADC result.\n");
            return(-1);
            }
        if(rdbuf[2] & 0x80) { continue; } // Not RDY.
        const int result = ((rdbuf[0] & 0xff) << 8) + (rdbuf[1] & 0xff);
        if(verbose)
            { fprintf(stderr, "ADC (%x %d) response %d (%x %x %x)\n", addr, channel, result, rdbuf[0], rdbuf[1], rdbuf[2]); }
        return(result);
        }
    fprintf(stderr, "ADC read did not complete in reasonable time.\n");
    return(-1);
    }

// Set up IC2 access and return file description (or -1 if error).
static int i2c_setup()
    {
    const int fd = open(I2C_FILEPATH, O_RDWR);
    if(fd < 0)
        {
        fprintf(stderr, "Error opening %s.\n", I2C_FILEPATH);
        return(fd);
        }
    if((ioctl(fd, I2C_TIMEOUT, 2) < 0) ||
       (ioctl(fd, I2C_RETRIES, 1) < 0))
        {
        fprintf(stderr, "setup ioctl() failed.\n");
        close(fd);
        return(-1);
        }
    return(fd);
    }

// Construct the full file path name for a flag in buf.
// The buffer should usually be at least FLAG_BUF_SIZE chars.
static const int FLAG_BUF_SIZE =
    FLAG_DIR_LEN + 1 /* / */ + MAX_FLAG_NAME + 6 /* .flag\0 */;
static void makeFullFlagPath(char * const buf, const int bufsize,
                             const char * const flagName)
    {
    if(strlen(flagName) > MAX_FLAG_NAME)
        {
        fprintf(stderr, "Bad flag name (too long); not setting flag.\n");
        buf[0] = '\0';
        return;
        }
    strcpy(buf, FLAG_DIR);
    strcat(buf, "/");
    strncat(buf, flagName, MAX_FLAG_NAME);
    strcat(buf, ".flag");
    }

// Read (input) flag; returns true iff exists/set.
static bool isFlagSet(const char * const flagName)
    {
    char buf[FLAG_BUF_SIZE];
    makeFullFlagPath(buf, sizeof(buf), flagName);
    if('\0' == buf[0]) { return(false); }
    const bool exists = (0 == access(buf, R_OK));
    if(verbose) { fprintf(stderr, "Input flag %s is %s.\n", flagName, exists ? "set" : "unset"); }
    return(exists);
    }

// Get last-modification time of flag, or -1 if flag not set (does not exist).
static time_t flagTime(const char * const flagName)
    {
    char buf[FLAG_BUF_SIZE];
    makeFullFlagPath(buf, sizeof(buf), flagName);
    if('\0' == buf[0]) { return(-1); }
    struct stat s;
    if(stat(buf, &s) < 0) { return(-1); }
    return(s.st_mtime);
    }

// Returns true if specified external flag file is present.
static bool isExternalFlagSet(const char * const pathName)
    {
    const bool exists = (0 == access(pathName, R_OK));
    if(verbose) { fprintf(stderr, "External flag file %s is %s.\n", pathName, exists ? "present" : "absent"); }
    return(exists);
    }

// Create/remove flag in flags directory; flag file present if value true.
// This won't create/touch/remove unless necessary to reduce filesystem load.
// This also means that a flag should retain its modification time until cleared.
// (Flags area may often be in an in-memory volatile filesystem.)
static void doFlag(const char * const flagName, const bool flagValue)
    {
    if(verbose) { fprintf(stderr, "Flag %s is %s.\n", flagName, flagValue ? "set" : "unset"); }
    char buf[FLAG_BUF_SIZE];
    makeFullFlagPath(buf, sizeof(buf), flagName);
    if('\0' == buf[0]) { return; }
    const bool exists = (0 == access(buf, R_OK));
    // If state is already correct, do nothing.
    if(exists == flagValue) { return; }
    // Flag should be absent: remove.
    if(!flagValue)
        {
        if(verbose) { fprintf(stderr, "Removing/clearing flag %s...\n", buf); }
        if(0 != unlink(buf))
            { fprintf(stderr, "ERROR removing flag %s.\n", buf); }
        }
    else
        {
        if(verbose) { fprintf(stderr, "Creating/setting flag %s...\n", buf); }
        const int fd = creat(buf, 0444);
        if(fd < 0)
            { fprintf(stderr, "ERROR creating flag %s.\n", buf); }
        else { close(fd); }
        }
    }
    
// Set external dump control through GPIO to given state.
static void setDumpingGPIO(const bool isDumping)
    {
    static const char *device = "/sys/class/gpio/gpio23/value";
    const bool exists = (0 == access(device, R_OK));
    if(!exists)
        {
        if(verbose) { fprintf(stderr, "GPIO for dumping does not exist yet: %s\n", device); }
        if(!doEcho("/sys/class/gpio/export", "23") ||
           !doEcho("/sys/class/gpio/gpio23/direction", "out") ||
           (0 != access(device, R_OK)))
            {
            fprintf(stderr, "GPIO for dumping CANNOT BE CREATED: %s\n", device);
            return;
            }
        }
    if(!doEcho(device, isDumping ? "1" : "0"))
        { fprintf(stderr, "GPIO for dumping CANNOT BE SET: %s\n", device); }
    }

// Do work, returning exit code (zero if all is well).
// At a high level, most of the interesting stuff happens here.
int doWork()
    {
    const int fd = i2c_setup();
    if(fd < 0) { return(1); } // Exit with error if set-up not possible.

    // FIRST TAKE POWER MEASUREMENT (possibly after a sleep for accuracy).
    // Measure system power consumption (from 12V).
    int bv = -1;
    const int powermW = getPowerDrain(fd, &bv);
    const int busVoltage = bv;

    // Record when the measurements are being taken.
    struct timeval measurementTime;
    gettimeofday(&measurementTime, NULL);

    // Note if dumping upon entry.
    const bool wasDumping = isFlagSet(FLAGIO_DUMPING);
    if(verbose && wasDumping) { fprintf(stderr, "System dumping power for %ldm; LA/B1 measurement allowance %dmV, Li/B2 %dmV.\n", (long)((measurementTime.tv_sec - flagTime(FLAGIO_DUMPING)) / 60), BATT1_DUMPCORR_mV, BATT2_DUMPCORR_mV); }

    const bool isForecastGood = isFlagSet(FLAGI_FORECASTGENGOOD);
    if(verbose && isForecastGood) { fprintf(stderr, "PV forecast is good.\n"); }

    // Read the LDR and set/remove the 'DAYLIGHT' flag as appropriate.
    const int ldr = ENABLE_AL ? readADC(fd, ADC2_addr, LDR_zch_ADC2) : -1;
    //const bool isDay = (ldr > LDR_daylight_thr);
    //if(verbose) { fprintf(stderr, "LDR %d vs daylight threshold %d.\n", ldr, LDR_daylight_thr); }
    const bool isDark = isExternalFlagSet(darkFlag);
    //const bool isDay = !isDark;
    const bool significantGridTieGeneration = !isDark && !isExternalFlagSet(lowGenFlag);
    // Assume off-grid charging if grid-tie significantly generating.
    const bool isCharging = significantGridTieGeneration;

    // Read both 12V-nominal battery inputs (fsd ~19.22V) raw.
    // DHD20140708: BATT1 1328 raw = 12.48V direct to local rail.
    // DHD20140708: BATT2 1359 raw = 12.77V direct to local rail.
    // If voltage can't be read then -ve result should force VLOW mode.
    const int batt1 = readADC(fd, ADC1_addr, BATT1_zch_ADC1);
    const int batt1mV = (int) (batt1 * mVperUlpBatt);
    const int batt2 = ENABLE_B2 ? readADC(fd, ADC2_addr, BATT2_zch_ADC2) : 0;
    const int batt2mV = ENABLE_B2 ? (int) (batt2 * mVperUlpBatt) : -1;
    if(verbose) { fprintf(stderr, "Battery voltage 1 (LA) %dmV, 2 (Li) %dmV.\n", batt1mV, batt2mV); }

    // Protective compensation on lower thresholds (raising them)
    // based on backward- and forward- looking measures.
    // Add further safety margin if NOT HIGH for more than a day
    // or insolation forecast is poor.
    const bool extraMarginLT = !isForecastGood ||
        (isFlagSet(FLAGIO_BATTNOTHIGH) &&
            ((measurementTime.tv_sec-flagTime(FLAGIO_BATTNOTHIGH))>28*60*60L));
    const int extraMarginmV = extraMarginLT ? BATT1_PFTHR_mV : 0;

    // Adjust output flags based on battery levels.

    // VERY LOW threshold calc.
    // Some extra hindcast/forecast- based protection at this low end.
    // Compensate for sag from load and rise from charging,
    // but never adjust the protective very low threshold downwards.
    // Generally aim to keep no-load voltage >= 12.45V to avoid sulphation.
    const int B1VLb = BATT1_THR_VL_mV
        + extraMarginmV;
    const int B1VL = B1VLb
        + ((!isCharging || wasDumping) ? 0 : BATT1_DUMPCORR_mV);
    const int B2VL = (extraMarginLT || wasDumping) ? BATT2_THR_VL_mV : BATT2_PFTHR_VL_mV;
    const bool isVLOW = (batt1mV <= B1VL) ||
                        (ENABLE_B2 && B2InSoC && (batt2mV <= B2VL));
    if(verbose) { fprintf(stderr, "Battery voltage 1 'very low' threshold %dmV.\n", B1VL); }
    if(ENABLE_B2 && verbose) { fprintf(stderr, "Battery voltage 2 'very low' threshold %dmV.\n", B2VL); }
    doFlag(FLAGO_BATTVLOW, isVLOW);

    // LOW threshold calc.
    // Some extra hindcast/forecast- based protection at this low end.
    // Compensate for sag from load and rise from charging.
    const int B1Lb = BATT1_THR_L_mV
        + extraMarginmV
        + (isCharging ? BATT1_DUMPCORR_mV : 0);
    const int B1Lu = B1Lb
        - (wasDumping ? BATT1_DUMPCORR_mV : 0);
    const int B1L = MAX(B1VL, B1Lu);
    const bool isLOW = isVLOW || (batt1mV <= B1L);
    if(verbose) { fprintf(stderr, "Battery voltage 1 'low' threshold %dmV.\n", B1L); }
    doFlag(FLAGO_BATTLOW, isLOW);
    // Compute margin below which dumping may oscillate or is unsafe.
    const int B1Lm = BATT1_DLdelta_mV + MAX(B1Lu, B1VLb+(wasDumping?0:BATT1_DUMPCORR_mV));
    if(verbose) { fprintf(stderr, "Battery voltage 1 dump margin threshold %dmV.\n", B1Lm); }

    // (Not) HIGH threshold calc.
    // Compensate for sag from load but not rise from charging,
    // because this voltage should only be achievable when charging.
    const int B1NH = BATT1_THR_H_mV
        + extraMarginmV
        - (wasDumping ? BATT1_DUMPCORR_mV : 0);
    const int B2NH = (extraMarginLT ? BATT2_THR_H_mV : BATT2_PFTHR_H_mV) -
        (wasDumping ? BATT2_DUMPCORR_mV : 0);
    const bool isNOTHIGH = isLOW || (batt1mV <= B1NH) ||
                                    (ENABLE_B2 && B2InSoC && (batt2mV <= B2NH));
    if(verbose) { fprintf(stderr, "Battery voltage 1 'not high' threshold %dmV.\n", B1NH); }
    if(ENABLE_B2 && verbose) { fprintf(stderr, "Battery voltage 2 'not high' threshold %dmV.\n", B2NH); }
    doFlag(FLAGIO_BATTNOTHIGH, isNOTHIGH);
    doFlag(FLAGO_BATTHIGH, !isNOTHIGH);

    // VERY HIGH threshold calc.
    // No sag nor rise compensation, nor extra safety margins.
    const bool isVHIGH = (!isNOTHIGH) && (batt1mV > BATT1_THR_VH_mV);
    if(verbose) { fprintf(stderr, "Battery voltage 1 'very high' threshold %dmV.\n", BATT1_THR_VH_mV); }
    doFlag(FLAGO_BATTVHIGH, isVHIGH);

    // Compute short status flag name.
    const char * const bsfl =
        (isVLOW ? "VL" :
        (isLOW ? "L" :
        (isNOTHIGH ? "OK" :
        (!isVHIGH ? "H" : "VH"))));

    // Adjust CPU governor based on flags.
    // Use a higher threshold to save energy unless batteries are good.
    adj_up_threshold(isLOW ? CONSERVING_UP_THRESHOLD : DEFAULT_UP_THRESHOLD);

    // Minimum dump (on) time in normal circumstances (s), to reduce cycling.
    // This assumes that dump power controlled by this is relatively low.
    // Minimum dump off time may be longer, to reduce duty cycle.
    static const long MIN_DUMP_S = 29 * 60; // ~30 minutes.

    // Prepare to recompute if dumping is desireable.
    // Defaults to continuing as before.
    bool isDumping = wasDumping;
    // Single-char indicator of reason for (new) dumping state.
    // Defaults to '-' for no change.
    char dumpReason = '-';
    // Avoid/stop dumping immediately if battery SoC appears to be LOW.
    // This should be the shortest code path to save energy when battery low.

    time_t start; // Inline start times for various points below.
    int minS; // Inline minimum (sec) times for various points below.

    if(isLOW)
        {
        dumpReason = 'L'; // *L*OW battery.
        isDumping = false;
        }
    // Stop/avoid dumping if not permitted.
    else if(isFlagSet(FLAGI_NODUMP))
        {
        if(verbose) { fprintf(stderr, "Dumping not permitted.\n"); }
        dumpReason = 'n'; // Dumping *n*ot permitted.
        isDumping = false;
        }
    // If only recently stopped dumping then don't start.
    // This helps cap the amount of cycling of the dump relay and contact wear.
    else if(!wasDumping &&
        ((measurementTime.tv_sec-flagTime(FLAGIO_DUMPINGEND))<2*MIN_DUMP_S))
        {
        if(verbose) { fprintf(stderr, "Too soon since dumping stopped to start.\n"); }
        dumpReason = 't'; // In minimum *t*ime since dumping stopped.
        isDumping = false;
        }
    // If only recently started dumping then keep going.
    // This helps cap the amount of cycling of the dump relay and contact wear.
    else if(wasDumping &&
        ((measurementTime.tv_sec-flagTime(FLAGIO_DUMPING)) < MIN_DUMP_S))
        {
        if(verbose) { fprintf(stderr, "Too soon since dumping started to stop.\n"); }
        dumpReason = 'T'; // In minimum *T*ime since dumping started.
        isDumping = true;
        }
    // Avoid dumping if battery level can't easily support extra/dump load.
    else if(batt1mV <= B1Lm)
        {
        if(verbose) { fprintf(stderr, "Insufficient margin to dump, threshold %d.\n", B1Lm); }
        dumpReason = 'm'; // Insufficient *m*argin to dump.
        isDumping = false;
        }
    // If VERY HIGH for long enough then dump.
    else if(isVHIGH &&
        ((start = flagTime(FLAGO_BATTVHIGH)) > 0) &&
        ((measurementTime.tv_sec - start) >
            (minS = (60 * (extraMarginLT ? BATT1_MINVH_BEFORE_DUMP_M :
                                           BATT1_PFMINVH_BEFORE_DUMP_M)))))
        {
        // const int minimumAbsS = isForecastGood ?
        //     (60 * BATT1_MINVH_BEFORE_DUMP_M) :
        //     (60 * BATT1_PFMINVH_BEFORE_DUMP_M);
        // if(verbose) { fprintf(stderr, "Battery very high for %ldm vs minimum VH before dumping %dm.\n", (long)((measurementTime.tv_sec - start) / 60), minimumAbsS / 60); }
        // if(measurementTime.tv_sec - start > minimumAbsS)
            {
            isDumping = true;
            dumpReason = 'V'; // *V*ERY HIGH battery.
            }
        }
    // If HIGH for long enough then dump.
    else if((!isNOTHIGH) &&
        ((start = flagTime(FLAGO_BATTHIGH)) > 0) &&
        ((measurementTime.tv_sec - start) > (minS = (60 * BATT1_MINH_BEFORE_DUMP_M))))
        {
        // const int minimumAbsS = (60 * BATT1_MINH_BEFORE_DUMP_M);
        // if(verbose) { fprintf(stderr, "Battery high for %ldm vs minimum H before dumping %dm.\n", (long)((measurementTime.tv_sec - start) / 60), minimumAbsS / 60); }
        // if(measurementTime.tv_sec - start > minimumAbsS)
            {
            isDumping = true;
            dumpReason = 'H'; // *H*IGH battery.
            }
        }
    // Take dump/optional load off grid if:
    //    * is dark (or low microgen) so as to reduce night grid loads
    //    * in peak demand/intensity window
    else
        {
        isDumping = (
            (!significantGridTieGeneration) ||
            isExternalFlagSet(gridRedFlag) ||
            isFlagSet(FLAGI_GBGRIDPEAK));
        if(verbose) { fprintf(stderr, "Dumping driven by external factors.\n"); }
        dumpReason = 'e'; // Dump is *e*xternally decided.
        }
    // Set flag (and converse, for tracking).
    doFlag(FLAGIO_DUMPING, isDumping);
    doFlag(FLAGIO_DUMPINGEND, !isDumping);
    // Set GPIO for dump control.
    setDumpingGPIO(isDumping);

    // Prepare the log output (if logging or verbose).
    struct tm tm;
    const int MAX_LOG_LINE = 256;
    char logLineBuf[MAX_LOG_LINE];
    logLineBuf[0] = '\0'; // Ensure terminated.
    if(!nolog || verbose || looping)
        {
        char tmbuf[64];
        tmbuf[0] = '\0'; // Ensure terminated.
        if(gmtime_r(&measurementTime.tv_sec, &tm) != NULL)
            { strftime(tmbuf, sizeof(tmbuf), "%Y/%m/%dT%H:%M:%SZ", &tm); }
        // Length-limited log-line generation, space-separated fields.
        // Each field value (or fixed-length group of fields)
        // is preceded with a field (group) name.
        // AL ambient light in range [0,2047]; higher is brighter.
        snprintf(logLineBuf, MAX_LOG_LINE,
            "%s AL %d B1 %d B2 %d P %d BV %d ST %s %c %c\n",
            tmbuf,
            ldr,
            batt1mV, batt2mV,
            powermW, busVoltage,
            bsfl, (isDumping ? 'D' : '-'), dumpReason);
        if(verbose) { fprintf(stderr, "%s: %s", FLAGO_LASTDATA, logLineBuf); }
        // If looping but not verbose print shorter summary status line
        // that may be more useful for interactive system tuning.
        else if(looping)
            { fprintf(stderr, "%s", logLineBuf + strlen(tmbuf) + 1); }
        }
    // Write 'flag' and logs unless inhibited.
    if(!nolog)
        {
        char buf[FLAG_BUF_SIZE];
        strcpy(buf, FLAG_DIR);
        strcat(buf, "/");
        strcat(buf, FLAGO_LASTDATA);
        strcat(buf, ".flag");
        assert(strlen(buf) < sizeof(buf));
        const int ldfd = open(buf, O_CREAT | O_WRONLY | O_TRUNC, 0644);
        const int lLBLen = (int) strlen(logLineBuf);
        if((ldfd < 0) ||
           (write(ldfd, logLineBuf, lLBLen) != lLBLen)||
           (0 != close(ldfd)))
            { fprintf(stderr, "Cannot write %s data flag.\n", buf); }
        if(0 == access(LOG_DIR, W_OK))
            {
            char tmbuf[9];
            tmbuf[0] = '\0'; // Ensure terminated.
            if(gmtime_r(&measurementTime.tv_sec, &tm) != NULL)
                { strftime(tmbuf, sizeof(tmbuf), "%Y%m%d", &tm); }
            char lfnbuf[256];
            snprintf(lfnbuf, sizeof(lfnbuf), "%s/%s.log", LOG_DIR, tmbuf);
            const int lffd = open(lfnbuf, O_CREAT | O_WRONLY | O_APPEND, 0644);
            if((lffd < 0) ||
               (write(lffd, logLineBuf, lLBLen) != lLBLen) ||
               (0 != close(lffd)))
                { fprintf(stderr, "Cannot write to log file %s.\n", lfnbuf); }
            }
        else if(verbose)
            { fprintf(stderr, "Cannot write/open log dir %s.\n", LOG_DIR); }
        }

    close(fd);
    return(0);
    }

#define SLEEP_TIME_S 5

int main (const int argc, char *const argv [])
    {
    int c;
    while((c = getopt(argc,argv,"hlnsv")) >= 0)
        {
        switch(c)
            {
            case 'h':
            case '?':
                {
                fprintf(stderr, "Version %s\n", Id);
                fprintf(stderr, "%s [options]\n", argv[0]);
                fprintf(stderr, " -h this help\n");
                fprintf(stderr, " -l looping for performance tuning\n");
                fprintf(stderr, " -n no-log\n");
                fprintf(stderr, " -s sleep before power measurement\n");
                fprintf(stderr, " -v verbose\n");
                break;
                }

            case 'l':
                {
                looping = true;
                if(verbose) { fprintf(stderr, "Looping mode; ^C to exit.\n"); }
                break;
                }

            case 'n':
                {
                nolog = true;
                if(verbose) { fprintf(stderr, "No-log mode.\n"); }
                break;
                }

            case 's':
                {
                sleepfirst = true;
                if(verbose) { fprintf(stderr, "Sleep before power measurement.\n"); }
                break;
                }

            case 'v':
                {
                verbose = true;
                fprintf(stderr, "Verbose mode (%s).\n", Id);
                break;
                }

            }
        }

    int exitCode = 1;
    do  {
        // Allow system to settle is required.
        if(sleepfirst) { sleep(SLEEP_TIME_S); }

        // ACQUIRE MUTEX
        // Attempt to create read-only lock file.
        const int lockfd0 = open(LOCK_FILE, O_CREAT | O_EXCL | O_RDONLY, 0444);
        if(lockfd0 >= 0) { close(lockfd0); }
        else
            {
            fprintf(stderr, "Initial attempt to acquire lock failed: %s\n", LOCK_FILE);
            struct stat statbuf;
            if(0 != stat(LOCK_FILE, &statbuf))
                {
                fprintf(stderr, "Cannot stat lock: %s\n", LOCK_FILE);
                if(looping) { continue; }
                exit(2);
                }
            struct timeval tv;
            gettimeofday(&tv, NULL);
            if(tv.tv_sec - statbuf.st_mtime > LOCK_STALE_S)
                {
                fprintf(stderr, "Removing stale lock, sleeping: %s\n", LOCK_FILE);
                unlink(LOCK_FILE);
                sleep(LOCK_SUSPEND_S);
                const int lockfdr = open(LOCK_FILE, O_CREAT | O_EXCL | O_RDONLY, 0444);
                if(lockfdr >= 0) { close(lockfdr); }
                else
                    {
                    fprintf(stderr, "Attempt to re-acquire lock failed: %s\n", LOCK_FILE);
                    if(looping) { continue; }
                    exit(3);
                    }
                }
            }

        // Once the lock is acquired, try to run without interruption.
        signal(SIGINT, SIG_IGN);

        // Take the data samples.
        exitCode = doWork();

        // RELEASE MUTEX
        unlink(LOCK_FILE);
        // Allow SIGINT again.
        signal(SIGINT, SIG_DFL);

        if(looping && !sleepfirst) { sleep(SLEEP_TIME_S); }
        } while(looping);

    return(exitCode);
    }

/** Example outpuf 2016/06/01: dump margin threshold 12699mV is good.
Verbose mode ($Id: powermng.cpp 14763 2016-06-10 17:23:49Z dhd $).
Input flag DUMPING is unset.
Input flag FORECAST_PV_GEN_GOOD is unset.
External flag file /var/log/SunnyBeam/DARK.flag is absent.
ADC command 6a 80
ADC (6a 0) response 1342 (5 3e 0)
Battery voltage 1 (LA) 12591mV, 2 (Li) -1mV.
Battery voltage 1 'very low' threshold 12449mV.
Flag EXTERNAL_BATTERY_VLOW is unset.
Battery voltage 1 'low' threshold 12649mV.
Flag EXTERNAL_BATTERY_LOW is set.
Battery voltage 1 dump margin threshold 12699mV.
Battery voltage 1 'not high' threshold 13500mV.
Flag EXTERNAL_BATTERY_NOTHIGH is set.
Flag EXTERNAL_BATTERY_HIGH is unset.
Battery voltage 1 'very high' threshold 13600mV.
Flag EXTERNAL_BATTERY_VHIGH is unset.
Setting up_threshold to 95%
Flag DUMPING is unset.
Flag DUMPINGEND is set.
LASTDATA: 2016/06/01T11:37:40Z AL -1 B1 12591 B2 -1 P -1 BV -1 ST L - L
*/
