/** \file
 *
 *  Contains the Battery Pack Controller (BPC1) class implementation.
 *
 *  Copyright (c) 2014 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 */


/***************************************************************
Why does reading in some lines of hex seem so comlicated?


The Ocean Server cards only support monitoring for 8 battery
sticks per card. Thus Battery Pack 1 was created to mux those
other batteries so monitoring could take place. There are 4 mux
channels duplicated across 2 uarts. 6 have 8 battery sticks
and the remaining 2 channels have 7 battery sticks. The mux is
uncontrolled at this time, but will be controlled once fully
integrated with motherboard 3.0.

Additionaly, there is a non-deterministic response from
the Ocean Server cards when issuing a space command and the
"X" Host Hex command. The space command issues a banner only
every 4th time or so

=====================================
IBPS  V2.10
OceanServer Technology, Inc (c) 2007
 www.ocean-server.com

 S - Setup Controller
 B - Battery Status
 X - Host HEX
 H - Help

->


The Host Hex command randomly start with either a short message
or the full message that the BPC1 interface needs to get
necessary information about the battery sticks. Additionally, there
are 3 less bytes transmitted when a short/full message are sent together.
When a standalone full message is sent it's 3 bytes more for the 7
or 8 battery version.

Didn't quite get that? Here's message sizes in bytes, both individually
and combined:

										Short		Full		Combined		Sum
Minimum (7 stick)		199			1676		1872				1875
Maximum (8 stick) 	216			1904		2117				2120

Notice that the sum of the individual messages is 3 bytes greater
than when the messages are combined. For example, if one receives
a full 8 stick message upon sending "X", 1940 bytes shall be
expected. However, if the 216 byte short message arrives first to
be followed by the full 1904 byte message, the total number of
bytes to be read is 2117 rather than the sum of the two messages
which equals 2120 bytes.


This is a short message:
$S,01,270F,02,00,04,52%20
$C1,01,7F,02,00,03,7F,04,00,05,00,06,00,07,00%72
$B11,0A,FFFC%36
$B12,0A,FFFC%35
$B13,0A,FFFC%34
$B14,0A,FFFC%33
$B15,0A,0000%37
$B16,0A,0000%34
$B17,0A,0000%35

This is a full message:
$S,01,270F,02,00,04,52%20
$C1,01,7F,02,00,03,7F,04,00,05,00,06,00,07,00%72
$B11,02,000A,01,026C,03,0001,08,0B8F,09,3D1A,0A,FFFC,0B,FFFE%4A
$B11,0C,0001,0D,0051,0E,004B,0F,1205,10,1672,11,FFFF,12,FFFF%33
$B11,13,FFFF,14,0BB8,15,41A0,16,00C0,17,0001,18,1838,19,3840%42
$B11,1A,0031,1B,4357,1C,00A4%41
$B12,02,000A,01,026C,03,0001,08,0B90,09,3D1B,0A,FFFB,0B,FFFE%3C
$B12,0C,0001,0D,0050,0E,004B,0F,122A,10,16E9,11,D9F8,12,FFFF%3D
$B12,13,FFFF,14,0BB8,15,41A0,16,00C0,17,0001,18,1838,19,3840%41
$B12,1A,0031,1B,4357,1C,0083%3C
$B13,02,000A,01,026C,03,0001,08,0B96,09,3D17,0A,FFFC,0B,FFFE%4F
$B13,0C,0001,0D,0051,0E,004C,0F,1231,10,16B5,11,FFFF,12,FFFF%45
$B13,13,FFFF,14,0BB8,15,41A0,16,00C0,17,0001,18,1838,19,3840%40
$B13,1A,0031,1B,4357,1C,009A%4E
$B14,02,000A,01,026C,03,0001,08,0B90,09,3D19,0A,FFFC,0B,FFFE%40
$B14,0C,0001,0D,0051,0E,004C,0F,1265,10,16E8,11,FFFF,12,FFFF%49
$B14,13,FFFF,14,0BB8,15,41A0,16,00C0,17,0001,18,1838,19,3840%47
$B14,1A,0031,1B,4357,1C,008C%4A
$B15,02,000A,01,026C,03,0001,08,0B8A,09,3D74,0A,000A,0B,0001%4C
$B15,0C,0001,0D,0052,0E,004E,0F,12BD,10,16A7,11,FFFF,12,FFFF%43
$B15,13,EAD8,14,0BB8,15,41A0,16,00C0,17,0001,18,1838,19,3840%3E
$B15,1A,0031,1B,4357,1C,00BB%30
$B16,02,000A,01,026C,03,0001,08,0B86,09,3D77,0A,000A,0B,0001%3B
$B16,0C,0001,0D,0053,0E,004D,0F,129A,10,166D,11,FFFF,12,FFFF%3A
$B16,13,E574,14,0BB8,15,41A0,16,00C0,17,0001,18,1838,19,3840%36
$B16,1A,0031,1B,4357,1C,00B8%49
$B17,02,000A,01,026C,03,0001,08,0B8E,09,3D72,0A,0000,0B,0001%3D
$B17,0C,0001,0D,0053,0E,004E,0F,12DD,10,16C7,11,FFFF,12,FFFF%44
$B17,13,EAD8,14,0BB8,15,41A0,16,00C0,17,0001,18,1838,19,3840%3C
$B17,1A,0031,1B,4357,1C,00AF%35

When an "X" is issued, it cannot be determined which message will
be output first.


******************************/


#include "BPC1.h"
#include "BPC1IF.h"
#include "BPC1_BattBank.h"

#include <cstdlib>
#include <unistd.h>  // include for the sleep method
#include <algorithm> // std::find

#include "bitModule/IBITIF.h"
#include "data/ConfigReader.h"
#include "data/SimSlate.h"
#include "data/Slate.h"
#include "data/UniversalDataReader.h"
#include "data/UniversalDataWriter.h"
#include "units/Units.h"
#include "io/LPC3Reg.h"

BPC1* BPC1::Instance_( NULL );

BPC1::BPC1( const Module* module )
    : SyncSensorComponent( BPC1IF::NAME, module ),
      debug_( false ),
      battTimeout_( 300 ), // Accounts for a short IPBS message coming out first
      muxTimeout_( 1800 ), // 3x10 min mux cycles
      poTimeout_( 60 ),
      hibernationTime_( Timestamp::NOT_SET_TIME ),
      muxCycleTime_( 0 ), // Time it takes to complete one mux cycle.
      capacityFailed_( false ),
      voltageFailed_( false )
{

    // Data outputs
    reserveBatteryChargeWriter_  = newDataWriter( BPC1IF::RESERVE_BATTERY_CHARGE );
    reserveBatteryVoltageWriter_ = newDataWriter( BPC1IF::RESERVE_BATTERY_VOLTAGE );
    chargingSticksWriter_ = newDataWriter( BPC1IF::BATTERY_CHARGING_STICK_COUNT );

    // Universal outputs
    batteryChargeWriter_  = newUniversalWriter( UniversalURI::PLATFORM_BATTERY_CHARGE );
    batteryVoltageWriter_ = newUniversalWriter( UniversalURI::PLATFORM_BATTERY_VOLTAGE );
    dischargeStatusWriter_ = newUniversalWriter( UniversalURI::PLATFORM_BATTERY_DISCHARGING );

    // Set universal writers accuracy
    // NOTE: accuracies are expected to vary, consider using writeWithAccuracy/setAccuracy instead of using a fixed value
    batteryChargeWriter_->setAccuracy( Units::AMPERE_HOUR, BPC1IF::PLATFORM_BATTERY_CHARGE_ACCURACY );
    batteryVoltageWriter_->setAccuracy( Units::VOLT, BPC1IF::PLATFORM_BATTERY_VOLTAGE_ACCURACY );
    dischargeStatusWriter_->setAccuracy( Units::BOOL, BPC1IF::PLATFORM_BATTERY_DISCHARGING_ACCURACY );

    // IBIT Configuration Readers
    battCapacityThresholdCfgReader_ = newConfigReader( IBITIF::BATTERY_CAPACITY_THRESHOLD );
    battVoltageThresholdCfgReader_  = newConfigReader( IBITIF::BATTERY_VOLTAGE_THRESHOLD );

    // BPC Configuration Readers
    battMuxCycleTimeCfgReader_      = newConfigReader( BPC1IF::BATTERY_MUX_CYCLE_TIME );
    battSamplingIntervalCfgReader_  = newConfigReader( BPC1IF::BATTERY_SAMPLING_INTERVAL );
    battStickCommsTimoutCfgReader_  = newConfigReader( BPC1IF::BATTERY_STICK_COMMS_TIMEOUT );
    missingStickThresholdCfgReader_ = newConfigReader( BPC1IF::BATTERY_MISSING_STICK_THRESHOLD );

    // Create the battery bank objects
    createBatteryBanks();

    Instance_ = this;

    // This configures the advanced run modes.
    setRunState( START );
    startTime_ = Timestamp::Now();
}


BPC1::~BPC1()
{
    uninitialize();

    // Destroy bank objects
    for( bank_ = bankList_.begin(); bank_ != bankList_.end(); bank_++ )
    {
        delete( *bank_ );
    }
    Instance_ = NULL;
}

void BPC1::run()
{
}

void BPC1::createBatteryBanks()
{
    BattBank *battBank( NULL );
    BattBankID bankID;

    if( !readConfig() )
    {
        logger_.syslog( "Failed to load configuration parameters.", Syslog::CRITICAL );
        this->setFailure( FailureMode::DATA );
        return;
    }

    for( int i = 0; i < BPC1IF::NUMBANKS; i++ )
    {
        bankID = static_cast<BattBankID>( i );

        // NUMBANKS, NUMBATTS, STICKOFFSET, NUMRESERVE are currently all defined in the
        // BPC1IF file. If needed in the future (e.g., if we have some vehicles with 2 BP
        // and some w/ 4 BP), these variables can be read from a config file, say using
        // Slate::ReadOnce, and then passed to the BattBank constructors below.

        switch( bankID )
        {
        case BPC1A:
            battBank = new BattBank( BPC1AIF::UART, BPC1AIF::BAUD, logger_, 4095, 0.010, BPC1AIF::NUMBATTS, BPC1AIF::NUMRESERVE, BPC1AIF::STICKOFFSET, BPC1AIF::NAME );
            break;

        case BPC1B:
            battBank = new BattBank( BPC1BIF::UART, BPC1BIF::BAUD, logger_, 4095, 0.010, BPC1BIF::NUMBATTS, BPC1BIF::NUMRESERVE, BPC1BIF::STICKOFFSET, BPC1BIF::NAME );
            break;

        default:
            break;
        }

        // Add the new BattBank to the bank list
        bankList_.push_back( battBank );
        battBank = NULL;
    }

    // Initialize stick writers
    initStickWriters();
}

bool BPC1::readConfig()
{
    bool ok = true;

    ok &= battCapacityThresholdCfgReader_->read( Units::MILLIAMPERE_HOUR, battCapacityThreshold_ );
    ok &= battVoltageThresholdCfgReader_->read( Units::MILLIVOLT, battVoltageThreshold_ );

    ok &= battMuxCycleTimeCfgReader_->read( Units::SECOND, muxCycleTime_ );
    ok &= battSamplingIntervalCfgReader_->read( Units::SECOND, battSamplingInterval_ );
    ok &= battStickCommsTimoutCfgReader_->read( Units::SECOND, battStickCommsTimeoutCfg_ );
    ok &= missingStickThresholdCfgReader_->read( Units::COUNT, missingStickThreshold_ );

    return ok;
}

void BPC1::uninitialize()
{
    if( debug_ ) logger_.syslog( "uninitialize", Syslog::INFO );

    if( !simulateHardware() )
    {
        disableUARTs();

        // Deactivate the mux
        LPC3Reg::PowerOffBatteryMux();
    }
}

/// Do what needs to be done to run
/// Similar to initialize, in old init/run/uninit sequence
Component::RunState BPC1::start()
{
    if( debug_ ) logger_.syslog( "Start", Syslog::INFO );
    if( simulateHardware() )
    {
        return STARTING;
    }

    this->setAllowableFailures( 10 );
    this->setRetryTimeout( 300 );

    if( !readConfig() )
    {
        logger_.syslog( Str( "Failed to load configuration parameters." ), Syslog::CRITICAL );
        this->setFailure( FailureMode::DATA );
    }

    // Open the uarts
    if( !enableUARTs() )
    {
        this->setFailure( FailureMode::COMMUNICATIONS );
        return STOP;
    }

    // Flush uarts on both ports
    flushBankUarts();

    // Activate the mux
    LPC3Reg::PowerOnBatteryMux();

    // Send spaces to initialize menus on both ports
    sendIBPSBreaks();

    startTime_ = Timestamp::Now();
    return STARTING;
}

Component::RunState BPC1::starting()
{
    if( debug_ ) logger_.syslog( "Starting", Syslog::INFO );

    if( startTime_.elapsed() < poTimeout_ ) // Wait for the BPC to boot
    {
        return STARTING;
    }

    if( simulateHardware() )
    {
        return RUNNABLE;
    }

    // Send spaces to initialize menus on both ports, again
    sendIBPSBreaks();

    // Look for the menu on both ports
    if( menusInitialized() )
    {
        if( debug_ ) logger_.syslog( "Initialized.", Syslog::INFO );

        // Reset the internal battery bank state
        for( bank_ = bankList_.begin(); bank_ != bankList_.end(); bank_++ )
        {
            ( *bank_ )->initializeBankState();
        }

        startTime_ = Timestamp::Now();
        muxTime_ = Timestamp::Now();
        return RUNNABLE;
    }

    // Why the long timeout? If the battery pack is still spewing data, it can take awhile.
    // poTimeout is for when the enable lines are being used.
    if( startTime_.elapsed() > battTimeout_ )
    {
        logger_.syslog( Str( "Failed to initialize" ), Syslog::FAULT );
        this->setFailure( FailureMode::COMMUNICATIONS );
        return STOP;
    }
    return STARTING;
}

/// Pause for a short period (indicated by pauseTime)
Component::RunState BPC1::pause()
{
    if( debug_ ) logger_.syslog( "Pause", Syslog::INFO );
    return STOP; // State not used at this time
}

/// Wait to see if we're paused
Component::RunState BPC1::pausing()
{
    if( debug_ ) logger_.syslog( "Pausing", Syslog::INFO );
    return STOP; // State not used at this time
}

/// Should eventually follow a PAUSE request: should set continueTime
Component::RunState BPC1::paused()
{
    if( debug_ ) logger_.syslog( "Paused", Syslog::INFO );
    return STOP; // State not used at this time
}

Component::RunState BPC1::resume()
{
    if( debug_ ) logger_.syslog( "Resume", Syslog::INFO );
    return STOP; // State not used at this time
}

// This state is not currently in use
Component::RunState BPC1::resuming()
{
    if( debug_ ) logger_.syslog( "Resuming", Syslog::INFO );
    return STOP; // State not used at this time
}

Component::RunState BPC1::runnable()
{
    //if( debug_ ) logger_.syslog( "Runnable", Syslog::INFO );

    if( simulateHardware() )
    {
        // Get total battery capacity and voltage
        SimBatteryStruct simBatt;
        SimSlate::Read( simBatt );

        if( !isnan( simBatt.batteryCharge ) && !isnan( simBatt.batteryVoltage ) )
        {
            Timestamp dataTime = Timestamp::Now();

            // Handle reserve battery
            double reserveCapacityFull = 51.6; // Ah
            reserveCapacity_ = simBatt.batteryCharge > reserveCapacityFull ? reserveCapacityFull : simBatt.batteryCharge;
            reserveVoltage_  = simBatt.batteryVoltage;

            // Subtract reserve capcity from the total capacity
            totalCapacity_ = AuvMath::Max( 0.0, simBatt.batteryCharge - reserveCapacity_ );
            totalVoltage_  = simBatt.batteryVoltage;

            setWritersInvalid( false );
            dischargeStatusWriter_->setInvalid( false );
            batteryChargeWriter_->write( Units::AMPERE_HOUR, totalCapacity_, dataTime );
            batteryVoltageWriter_->write( Units::VOLT, totalVoltage_, dataTime );
            dischargeStatusWriter_->write( Units::BOOL, true, dataTime );
            chargingSticksWriter_->write( Units::COUNT, 0, dataTime );
            reserveBatteryChargeWriter_->write( Units::AMPERE_HOUR, reserveCapacity_, dataTime );
            reserveBatteryVoltageWriter_->write( Units::VOLT, reserveVoltage_, dataTime );

            hibernationTime_ = Timestamp::Now();
            return RUNNABLE;
        }
    }
    else
    {
        // Run the banks
        bool charging = false, ranAnyBank = false;
        for( bank_ = bankList_.begin(); bank_ != bankList_.end(); bank_++ )
        {
            if( ( *bank_ )->runBank() )
            {
                // If a bank parsed new data, it should have updated charge/discharge state.
                // We're charging if any bank is charging
                ranAnyBank = true;
                charging |= ( *bank_ )->isCharging();
                startTime_ = Timestamp::Now();
            }
        }

        // If any bank parsed new data, update charge/discharge state
        if( ranAnyBank )
        {
            dischargeStatusWriter_->setInvalid( false );
            dischargeStatusWriter_->write( Units::BOOL, !charging, startTime_ );
        }

        // Check to see if ALL banks had valid data, but failed to parse it
        if( allBanksHaveDataError() )
        {
            logger_.syslog( "Failed to parse data from all battery packs.", Syslog::FAULT );
            this->setFailure( FailureMode::DATA );
            dischargeStatusWriter_->setInvalid( true );
            return STOP;
        }

        // In between all that parsing, let's see if we have cycled through all the batteries
        if( voltagesPopulated() || ( muxTime_.elapsed() > muxTimeout_ ) )
        {
            Timestamp dataTime = Timestamp::Now();

            calculateTotals();
            // ...and write them
            setWritersInvalid( false );
            batteryChargeWriter_->write( Units::MILLIAMPERE_HOUR, totalCapacity_, dataTime );
            batteryVoltageWriter_->write( Units::MILLIVOLT, totalVoltage_, dataTime );
            chargingSticksWriter_->write( Units::COUNT, totalChargingSticks_, dataTime );
            reserveBatteryChargeWriter_->write( Units::MILLIAMPERE_HOUR, reserveCapacity_, dataTime );
            reserveBatteryVoltageWriter_->write( Units::MILLIVOLT, reserveVoltage_, dataTime );

            // Check to see that we're not running low
            checkBatt( totalCapacity_, totalVoltage_ );

            // Clear out for the next time
            setVoltagesNull();

            if( muxTime_.elapsed() > muxTimeout_ )
            {
                // We're here because we timed out... Report IDs of missing sticks if any.
                if( missingSticks_.size() > 0 )
                {
                    int numMissing = ( int )missingSticks_.size();
                    Str errStr = "Failed to receive data from " + Str( numMissing ) + " sticks prior to timeout. Missing stick IDs are: ";
                    for( int i = 0; i < numMissing; i++ )
                    {
                        errStr += Str( missingSticks_[i] );
                        if( i < numMissing - 1 ) errStr += ", ";
                    }
                    errStr += ".";

                    logger_.syslog( errStr, Syslog::FAULT );
                    this->setFailure( FailureMode::DATA );
                }
                else
                {
                    logger_.syslog( "Failed to receive data from all sticks prior to timeout. Will not retry this scan cycle.", Syslog::ERROR );
                }
            }
            else
            {
                // Got data from all sticks!
                logger_.syslog( "Received data from all battery sticks.", Syslog::INFO );
                this->resetFailCount();
            }

            hibernationTime_ = Timestamp::Now();
            return STOP;
        }

        // Finally, check the timeouts.
        if( startTime_.elapsed() > battTimeout_ )
        {
            logger_.syslog( Str( "Failed to receive data from all battery packs." ), Syslog::ERROR );
            this->setFailure( FailureMode::COMMUNICATIONS );
            return STOP;
        }
    }

    return RUNNABLE;
}


Component::RunState BPC1::stop()
{
    if( debug_ ) logger_.syslog( "Stop", Syslog::INFO );

    muxTime_ = Timestamp::Now();
    uninitialize(); // First power down then query for faults next cycle

    return STOPPING;
}


Component::RunState BPC1::stopping()
{
    if( debug_ ) logger_.syslog( "Stopping", Syslog::INFO );
    if( !simulateHardware() )
    {
        // Stoping the component doesn't actually recycle power. We'll need to wait for the mux cycle to time out.
        if( muxTime_.elapsed().asFloat() < muxCycleTime_ )
            return STOPPING;
    }
    return STOPPED;
}


Component::RunState BPC1::stopped()
{
    if( debug_ ) logger_.syslog( "Stopped", Syslog::INFO );

    if( isDataRequested() )
    {
        return START;
    }

    return STOPPED;
}


bool BPC1::isDataRequested()
{

    // Stay up to date with config changes. This allows the operator to "wake up" the comp.
    readConfig();

    if( ( hibernationTime_.elapsed().asFloat() >= battSamplingInterval_ ) || ( hibernationTime_ == Timestamp::NOT_SET_TIME ) )
    {
        if( debug_ ) logger_.syslog( "Data requested. Hibernation time: " + Str( ( hibernationTime_.elapsed().asFloat() / 3600 ) ) + " hours.", Syslog::INFO );
        return true;
    }

    return false;
}

// Checks all battery sticks to see if their voltage has been logged
// Returns true if the number of missing batt sticks does not exceed the allowed limit.
// Relies on setVoltagesNull to clear sticks again for further checking
bool BPC1::voltagesPopulated()
{
    BattStick *battStick;
    int nullStickCount = 0;

    missingSticks_.clear();
    for( bank_ = bankList_.begin(); bank_ != bankList_.end(); bank_++ )
    {
        for( int i = 0; i < ( *bank_ )->NumBattSticks(); i++ )
        {
            // Get a pointer to the batt stick
            battStick = ( *bank_ )->getStick( i );

            // Check to see if stick data hasn't been updated or has an active alarm flag
            if( isnan( battStick->voltage_ ) || ( battStick->statusAlarmCount_ > 0 ) )
            {
                nullStickCount++;
                // Add to list of missing sticks if active alarm flag is set or if stick hasn't reported in within the specified timeout
                if( battStick->statusAlarmFlag_ || battStick->lastCheckInTime_.elapsed().asFloat() > battStickCommsTimeoutCfg_ )
                {
                    if( debug_ ) logger_.syslog( "Missing data from stick: " + Str( battStick->stickID_ ) + " (s/n: " + battStick->serialNumberStr_ + ").", Syslog::ERROR );
                    missingSticks_.push_back( battStick->stickID_ );
                }
            }
        }
    }

    return( nullStickCount <= missingStickThreshold_ );
}


// Sets the writer's validity to invalid for true or valid for false
void BPC1::setWritersInvalid( bool invalid )
{
    // First all the stick's individual writes
    setStickWritersInvalid( invalid );

    // Next the other writers
    // Note: doesn't set discharge status writer, because its validity is determined
    // based on a single parse cycle, vs. the aggregate data all other writers use.
    batteryChargeWriter_->setInvalid( invalid );
    batteryVoltageWriter_->setInvalid( invalid );
    chargingSticksWriter_->setInvalid( invalid );
    reserveBatteryChargeWriter_->setInvalid( invalid );
    reserveBatteryVoltageWriter_->setInvalid( invalid );
}


// Once all battery sticks have been freshly populated with
// data, this function will add up all the grand totals
void BPC1::calculateTotals()
{

    // Start fresh
    reserveCapacity_ = 0;
    reserveVoltage_  = 0;
    totalCapacity_   = 0;
    totalVoltage_    = 0;
    totalChargingSticks_ = 0;
    float reserveSumCapacity( 0 );
    float reserveSumVoltage( 0 );
    float sumCapacity( 0 );
    float sumVoltage( 0 );
    short validReserveStickCount( 0 );
    short validStickCount( 0 );

    BattStick *battStick;

    // Add them up
    for( bank_ = bankList_.begin(); bank_ != bankList_.end(); bank_++ )
    {
        for( int i = 0; i < ( *bank_ )->NumBattSticks(); i++ )
        {
            // Get pointer to the batt stick
            battStick = ( *bank_ )->getStick( i );

            if( battStick->checkStatus( logger_ ) && !isnan( battStick->voltage_ ) )
            {
                if( battStick->isReserveStick_ )
                {
                    reserveSumCapacity += ( battStick->capacity_ );
                    reserveSumVoltage  += ( battStick->voltage_ );
                    ++validReserveStickCount;
                }
                else
                {
                    sumCapacity += ( battStick->capacity_ );
                    sumVoltage  += ( battStick->voltage_ );
                    ++validStickCount;
                }
                if( battStick->isCharging() ) ++totalChargingSticks_;
            }
            else if( debug_ )
            {
                logger_.syslog( "Missing data from stick: " + Str( battStick->stickID_ ) + " (s/n: " + battStick->serialNumberStr_ + ").", Syslog::ERROR );
            }
        }
    }

    logger_.syslog( "Calculating totals. Valid battery stick count: " + Str( validStickCount ) + ". Valid reserve battery stick count: " + Str( validReserveStickCount ) + ".", Syslog::INFO );

    // Update total capacity and average voltage
    totalCapacity_ = sumCapacity;
    totalVoltage_  = sumVoltage / validStickCount;

    reserveCapacity_ = reserveSumCapacity;
    reserveVoltage_  = reserveSumVoltage / validReserveStickCount;

}


void BPC1::checkBatt( const float capacity, const float voltage )
{
    if( capacityFailed_ == false && capacity <= battCapacityThreshold_ )
    {
        logger_.syslog( "Battery Capacity Below Threshold. " + Str( capacity * 0.001 ) + " Ah.", Syslog::CRITICAL );
        capacityFailed_ = true;
    }

    if( voltageFailed_ == false && voltage <= battVoltageThreshold_ )
    {
        logger_.syslog( "Battery Voltage Below Threshold. " + Str( voltage * 0.001 ) + " V.", Syslog::CRITICAL );
        voltageFailed_ = true;
    }
}


bool BPC1::enableUARTs()
{
    bool enabled = true;
    for( bank_ = bankList_.begin(); bank_ != bankList_.end(); bank_++ )
    {
        enabled &= ( *bank_ )->enableUART();
        // NOTE: should this be an or rather than and? That way we can continue to talk to
        // one of the banks if the other fails... Might make diagnosing stuff in the lab
        // harder though...
    }

    return( enabled );
}

void BPC1::disableUARTs()
{
    for( bank_ = bankList_.begin(); bank_ != bankList_.end(); bank_++ )
    {
        ( *bank_ )->closeUART();
    }
}

// Flush UART on both ports
void BPC1::flushBankUarts()
{
    for( bank_ = bankList_.begin(); bank_ != bankList_.end(); bank_++ )
    {
        ( *bank_ )->flushUART();
    }
}

// Initializes the writers for each stick regestered in a BattBank.
// Done from BPC rather than in BattBank to keep stick data under a single comp.
void BPC1::initStickWriters()
{
    BattStick *battStick;

    // Iterate over the list of banks
    for( bank_ = bankList_.begin(); bank_ != bankList_.end(); bank_++ )
    {
        for( int i = 0; i < ( *bank_ )->NumBattSticks(); i++ )
        {
            // Get a pointer to the i'th batt stick
            battStick = ( *bank_ )->getStick( i );

            // ...and init its writers
            battStick->battCapacityWriter_ = Slate::NewOutputWriter( BPC1IF::BATT_CAPACITY_BASENAME + battStick->stickID_, this, Units::AMPERE_HOUR() );
            battStick->battCurrentWriter_  = Slate::NewOutputWriter( BPC1IF::BATT_CURRENT_BASENAME + battStick->stickID_, this, Units::AMPERE() );
            battStick->battStatusWriter_   = Slate::NewOutputWriter( BPC1IF::BATT_STATUS_BASENAME + battStick->stickID_, this, Units::NONE_USHORT() );
            battStick->battTempWriter_     = Slate::NewOutputWriter( BPC1IF::BATT_TEMP_BASENAME + battStick->stickID_, this, Units::CELSIUS() );
            battStick->battVoltageWriter_  = Slate::NewOutputWriter( BPC1IF::BATT_VOLTAGE_BASENAME + battStick->stickID_, this, Units::MILLIVOLT() );
            // TODO: Change from deprecated Slate::NewOutputWriter to newDataWriter. Leaving
            // as-is for now because of the adaptive way it is initializing them.
        }
    }
}

// Sets the voltage for each battery stick to null as a
// flag to indicate that recent data has not been obtained
// for a given stick
void BPC1::setVoltagesNull()
{
    for( bank_ = bankList_.begin(); bank_ != bankList_.end(); bank_++ )
    {
        ( *bank_ )->setVoltagesNull();
    }
}

// Sends the break (space) character to all ports to
// request the IBPS banner menu offering hex output
void BPC1::sendIBPSBreaks()
{
    for( bank_ = bankList_.begin(); bank_ != bankList_.end(); bank_++ )
    {
        ( *bank_ )->sendIBPSBreak();
    }
}

// Returns true if any of the ports are initialized to
// the IBPS banner menu offering hex output
bool BPC1::menusInitialized()
{
    for( bank_ = bankList_.begin(); bank_ != bankList_.end(); bank_++ )
    {
        if( ( *bank_ )->menuInitialized() ) return true;
    }

    return false;
}


// Returns true if ALL banks had valid data, but failed to parse it
bool BPC1::allBanksHaveDataError()
{
    bool dataError = true;
    for( bank_ = bankList_.begin(); bank_ != bankList_.end(); bank_++ )
    {
        dataError &= ( *bank_ )->hasDataError();
    }

    return( dataError );
}

void BPC1::setStickWritersInvalid( bool invalid )
{
    for( bank_ = bankList_.begin(); bank_ != bankList_.end(); bank_++ )
    {
        ( *bank_ )->setWritersInvalid( invalid );
    }
}

int BPC1::numBattSticks()
{
    int numSticks = 0;
    for( bank_ = bankList_.begin(); bank_ != bankList_.end(); bank_++ )
    {
        numSticks += ( *bank_ )->NumBattSticks();
    }

    return numSticks;
}

int BPC1::numReserveSticks()
{
    int numReserve = 0;
    for( bank_ = bankList_.begin(); bank_ != bankList_.end(); bank_++ )
    {
        numReserve += ( *bank_ )->NumReserveSticks();
    }

    return numReserve;
}

// Should return [myNamespace]::SIMULATE_HARDWARE, or [myNamespace]::POWER, etc
ConfigURI BPC1::getConfigURI( ConfigOption configOption ) const
{
    switch( configOption )
    {
    case CONFIG_POWER:
        return BPC1IF::POWER;
    case CONFIG_SIMULATE_HARDWARE:
        return BPC1IF::SIMULATE_HARDWARE;
    default:
        return ConfigURI::NO_CONFIG_URI;
    }
}

