/** \file
 *
 *  Contains the WetLabsBB2FL class implementation.
 *
 *  Copyright (c) 2007,2008,2009 MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 */


/*
  This component assumes configuration of the instrument is done manually.
  The manual specifies a max sample rate of 8Hz, however lab tests have shown what
  appear to be higher rates.
  At the time of writing $ave 8 and $pkt 0 were stored ($sto) to provide
  continuous data streaming averaging every other sample. This results in
  an output stream of 2 Hz to this component.
  The bin size and component PERIOD are designed to ouput mean data
  every 10 seconds.
*/

#include "WetLabsBB2FL.h"
#include "WetLabsBB2FLIF.h"

#include "data/ConfigReader.h"
#include "data/SimSlate.h"
#include "data/Slate.h"
#include "data/StrValue.h"
#include "data/UniversalDataReader.h"
#include "data/UniversalDataWriter.h"
#include "units/Units.h"

#define OUTPUT_LEN (50)

////////////////////////////////////////////////////////
// Use WetLabsBB2FLIF namespace to decrease verbosity
using namespace WetLabsBB2FLIF;

const Timespan WetLabsBB2FL::PERIOD( 0.5 );

const unsigned short WetLabsBB2FL::MAX_DEVICE_MSG_QUEUE_SIZE( 5 );

WetLabsBB2FL::WetLabsBB2FL( const Module* module )
    : AsyncComponent( NAME, module, PERIOD ),
      uart_( UART,  BAUD, 0.25, logger_, 4095, true ),
      loadControl_( LOAD_CONTROL, !simulateHardware(), logger_, this ),
      startTime_( Timestamp::NOT_SET_TIME ),
      dataTime_( Timestamp::NOT_SET_TIME ),
      timeout_( 5.0 ),
      chlConcAccuracy_( 0.018 ), // ug/l
      cdomConcAccuracy_( 0.18 ), // ppb, from datasheet
      chlBin_( 10 ), // 5 second bin size assumes 2Hz data into buffer
      cdomBin_( 10 ),
      serial_( Str::EMPTY_STR ),
      output470_( -1 ),
      output650_( -1 ),
      outputChl_( -1 ),
      outputCdom_( -1 ),
      volScat470_( nanf( "" ) ),
      backScat470_( nanf( "" ) ),
      volScat650_( nanf( "" ) ),
      backScat650_( nanf( "" ) ),
      chlConc_( nanf( "" ) ),
      cdomConc_( nanf( "" ) ),
      debug_( false )
{

    serialCfgReader_ = newConfigReader( WetLabsBB2FLIF::SERIAL );
    scaleFactor470CfgReader_ = newConfigReader( WetLabsBB2FLIF::SCALE_FACTOR_470 );
    darkCounts470CfgReader_ = newConfigReader( WetLabsBB2FLIF::DARK_COUNTS_470 );
    scaleFactor650CfgReader_ = newConfigReader( WetLabsBB2FLIF::SCALE_FACTOR_650 );
    darkCounts650CfgReader_ = newConfigReader( WetLabsBB2FLIF::DARK_COUNTS_650 );
    scaleFactorChlCfgReader_ = newConfigReader( WetLabsBB2FLIF::SCALE_FACTOR_CHL );
    darkCountsChlCfgReader_ = newConfigReader( WetLabsBB2FLIF::DARK_COUNTS_CHL );
    chlAccuracyCfgReader_ = newConfigReader( WetLabsBB2FLIF::CHL_ACCURACY );
    scaleFactorCdomCfgReader_ = newConfigReader( WetLabsBB2FLIF::SCALE_FACTOR_CDOM );
    darkCountsCdomCfgReader_ = newConfigReader( WetLabsBB2FLIF::DARK_COUNTS_CDOM );
    cdomAccuracyCfgReader_ = newConfigReader( WetLabsBB2FLIF::CDOM_ACCURACY );

    salinityReader_ = newUniversalReader( UniversalURI::SEA_WATER_SALINITY );

    output470Writer_ = newDataWriter( WetLabsBB2FLIF::OUTPUT_470 );
    output650Writer_ = newDataWriter( WetLabsBB2FLIF::OUTPUT_650 );
    outputChlWriter_ = newDataWriter( WetLabsBB2FLIF::OUTPUT_CHL );
    outputCdomWriter_ = newDataWriter( WetLabsBB2FLIF::OUTPUT_CDOM );

    volScat470Writer_ = newDataWriter( WetLabsBB2FLIF::VOLUME_SCAT_COEFF_117DEG_470NM );
    volScat650Writer_ = newDataWriter( WetLabsBB2FLIF::VOLUME_SCAT_COEFF_117DEG_650NM );

    backScat470Writer_ = newDataWriter( WetLabsBB2FLIF::BACKSCATTERING_COEFF_470NM );
    backScat650Writer_ = newDataWriter( WetLabsBB2FLIF::BACKSCATTERING_COEFF_650NM );

    chlConcWriter_ = newUniversalWriter( UniversalURI::MASS_CONCENTRATION_OF_CHLOROPHYLL_IN_SEA_WATER,
                                         Units::MICROMOLE_PER_LITER, chlConcAccuracy_ );
    cdomConcWriter_ = newUniversalWriter( UniversalURI::CONCENTRATION_OF_COLORED_DISSOLVED_ORGANIC_MATTER_IN_SEA_WATER,
                                          Units::PART_PER_BILLION, cdomConcAccuracy_ );

    Str binChannelName = getName() + "_BINNED";

    binMedianChlWriter_ = newDataWriter( binChannelName, WetLabsBB2FLIF::BIN_MEDIAN_MASS_CONCENTRATION_OF_CHLOROPHYLL_IN_SEA_WATER );
    binMeanChlWriter_ = newDataWriter( binChannelName, WetLabsBB2FLIF::BIN_MEAN_MASS_CONCENTRATION_OF_CHLOROPHYLL_IN_SEA_WATER );
    binVarianceChlWriter_ = newDataWriter( binChannelName, WetLabsBB2FLIF::BIN_VARIANCE_MASS_CONCENTRATION_OF_CHLOROPHYLL_IN_SEA_WATER );
    binMedianCdomWriter_ = newDataWriter( binChannelName, WetLabsBB2FLIF::BIN_MEDIAN_CONCENTRATION_OF_CDOM_IN_SEA_WATER );
    binMeanCdomWriter_ = newDataWriter( binChannelName, WetLabsBB2FLIF::BIN_MEAN_CONCENTRATION_OF_CDOM_IN_SEA_WATER );
    binVarianceCdomWriter_ = newDataWriter( binChannelName, WetLabsBB2FLIF::BIN_VARIANCE_CONCENTRATION_OF_CDOM_IN_SEA_WATER );

    // Initialize the run mode
    setRunState( START );

    this->setAllowableFailures( 5 );
    this->setRetryTimeout( 150 );
    this->setFailureMissionCritical( false );
}

WetLabsBB2FL::~WetLabsBB2FL()
{
}

void WetLabsBB2FL::readConfig()
{
    float chlAccuracy = DataElement::NO_ACCURACY;
    float cdomAccuracy = DataElement::NO_ACCURACY;
    StrValue tempStr( Str::EMPTY_STR );
    serialCfgReader_->read( tempStr );
    serial_ = tempStr.asString();
    scaleFactor470CfgReader_->read( Units::RECIPROCAL_METER_PER_STERADIAN_PER_COUNT, scaleFactor470_ );
    darkCounts470CfgReader_->read( Units::COUNT, darkCounts470_ );
    scaleFactor650CfgReader_->read( Units::RECIPROCAL_METER_PER_STERADIAN_PER_COUNT, scaleFactor650_ );
    darkCounts650CfgReader_->read( Units::COUNT, darkCounts650_ );
    scaleFactorChlCfgReader_->read( Units::MICROGRAM_PER_LITER_PER_COUNT, scaleFactorChl_ );
    darkCountsChlCfgReader_->read( Units::COUNT, darkCountsChl_ );
    scaleFactorCdomCfgReader_->read( Units::PART_PER_BILLION_PER_COUNT, scaleFactorCdom_ );
    darkCountsCdomCfgReader_->read( Units::COUNT, darkCountsCdom_ );
    if( chlAccuracyCfgReader_->read( Units::MICROGRAM_PER_LITER, chlAccuracy ) )
    {
        chlConcWriter_->setAccuracy( Units::MICROGRAM_PER_LITER, chlAccuracy );
    }
    if( cdomAccuracyCfgReader_->read( Units::PART_PER_BILLION, cdomAccuracy ) )
    {
        cdomConcWriter_->setAccuracy( Units::PART_PER_BILLION, cdomAccuracy );
    }
    // TODO: consider adding a parameter for bin size
    // BinFilter1D has support for this
}

void WetLabsBB2FL::run()
{
    //logger_.syslog("Run", Syslog::INFO);
}

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

    readConfig();

    chlBin_.clear();
    cdomBin_.clear();

    backScat470_ = nanf( "" );
    backScat650_ = nanf( "" );
    volScat470_ = nanf( "" );
    volScat650_ = nanf( "" );
    chlConc_ = nanf( "" );
    cdomConc_ = nanf( "" );
    output470_ = -1;
    output650_ = -1;
    outputChl_ = -1;
    outputCdom_ = -1;

    if( simulateHardware() )
    {
        return STARTING;
    }

    // Close if the uart if it is open
    if( uart_.isReadable() )
    {
        uart_.close();
        return START;
    }

    // Open the uart
    uart_.open();
    if( uart_.hasError() )
    {
        logger_.syslog( "Error opening port: ", uart_.errorString(), Syslog::FAULT );
        this->setFailure( FailureMode::COMMUNICATIONS );
        return START;
    }

    logger_.syslog( "Powering up", Syslog::INFO );
    if( !loadControl_.powerUp() )
    {
        logger_.syslog( "Failed to power up", Syslog::FAULT );
        this->setFailure( FailureMode::HARDWARE );
    }

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

/// Might follow a STOP...START sequence
Component::RunState WetLabsBB2FL::starting()
{
    if( debug_ ) logger_.syslog( "Starting", Syslog::INFO );
    if( simulateHardware() )
    {
        return RUNNABLE;
    }

    // Look for output
    if( uart_.readUntil( deviceResponse_, MAX_DEVICE_RESPONSE_SIZE, "\n", 1 ).hasError() )
    {
        if( uart_.getError() != UartStream::TIMEOUT )
        {
            logger_.syslog( "Uart error: ", uart_.errorString(), Syslog::ERROR );
            this->setFailure( FailureMode::COMMUNICATIONS );
            return STOP;
        }
    }
    else
    {
        unsigned int bytesRead = ( unsigned )uart_.bytesRead();
        deviceResponse_[AuvMath::Min( ( unsigned )MAX_DEVICE_RESPONSE_SIZE, bytesRead )] = '\0';

        if( bytesRead == OUTPUT_LEN )
        {
            if( !parse() )
            {
                this->setFailure( FailureMode::DATA );
                return STOP;
            }
            startTime_ = Timestamp::Now();
            return RUNNABLE;
        }
    }

    // See if the timeout has expired
    if( startTime_.elapsed() > timeout_ )
    {
        this->setFailure( FailureMode::COMMUNICATIONS );
        return STOP;
    }

    // coverity[secure_coding] // indicate that the code below is indeed safe
    return STARTING;
}

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

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

}

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

Component::RunState WetLabsBB2FL::resuming()
{
    return STOPPED; // State not used at this time
}

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

    /// Stop if we don't want data
    if( !isDataRequested() )
    {
        return stop();
    }
    // If data is available, parse it
    if( !simulateHardware() )
    {
        // Log voltage and current and check for any faults
        loadControl_.requestVoltageAndCurrent();
        if( loadControl_.hasError() )
        {
            logger_.syslog( "LCB fault: " + loadControl_.errorString(), Syslog::FAULT );
            this->setFailure( FailureMode::HARDWARE );
            return STOP;
        }
    }

    // Get latest data from device or SimSlate
    if( readData() )
    {
        updateTemporalBin();
        writeData();

        startTime_ = Timestamp::Now();
        this->resetFailCount();
    }

    if( ( !simulateHardware() ) && ( startTime_.elapsed() > timeout_ ) ) // Re-init if we've expired
    {
        logger_.syslog( "Failed to acquire valid data within specified timeout.", Syslog::FAULT );
        this->setFailure( FailureMode::COMMUNICATIONS );
        return STOP;
    }

    return RUNNABLE;
}

Component::RunState WetLabsBB2FL::stop()
{
    if( debug_ ) logger_.syslog( "Stop", Syslog::INFO );
    if( !simulateHardware() )
    {
        uninitialize(); // First power down then query for faults next cycle

    }

    return STOPPING;
}

Component::RunState WetLabsBB2FL::stopping()
{
    if( debug_ ) logger_.syslog( "Stopping", Syslog::INFO );
    if( !simulateHardware() )
    {
        // See if anything went wrong that may have caused this request for uninitialize
        loadControl_.readFaults();
        if( loadControl_.hasError() )
        {
            logger_.syslog( "LCB fault: " + loadControl_.errorString(), Syslog::FAULT );
            this->setFailure( FailureMode::HARDWARE );
        }

        // Confirm device is powered off
        if( ( loadControl_.getPowerState() != LoadControl::OFF ) && ( loadControl_.getPowerState() != LoadControl::POWER_DOWN ) )
        {
            return STOP;
        }

        // Close if the uart if it is open
        if( uart_.isReadable() )
        {
            uart_.close();
        }
    }

    return STOPPED;
}

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

    if( isDataRequested() )
    {
        return start();
    }

    return STOPPED;
}

bool WetLabsBB2FL::readData()
{
    if( simulateHardware() )
    {
        SimSlate::Read( SimSlate::MASS_CONCENTRATION_OF_CHLOROPHYLL_UG_PER_L, chlConc_ );
        SimSlate::Read( SimSlate::MASS_CONCENTRATION_OF_CHLOROPHYLL_UG_PER_L, volScat650_ ); // TODO: Use the correct SimSlate entry for volScat650_
        SimSlate::Read( SimSlate::MASS_CONCENTRATION_OF_CHLOROPHYLL_UG_PER_L, volScat470_ ); // TODO: Use the correct SimSlate entry for volScat470_
        volScat650Writer_->setInvalid( false );
        volScat470Writer_->setInvalid( false );
        chlConcWriter_->setInvalid( false );
        dataTime_ = Timestamp::Now();

        // Assume the sim always has data to write
        return true;
    }

    bool processPacket( false );
    unsigned short readCount( 0 );

    // Read most recent device data
    while( uart_.dataAvailable() && ( readCount < MAX_DEVICE_MSG_QUEUE_SIZE ) )
    {
        // The uart explicitly blocks here so we don't run in a fast loop
        if( uart_.readUntil( deviceResponse_, MAX_DEVICE_RESPONSE_SIZE, "\n", 1 ).hasError() )
        {
            if( uart_.getError() != UartStream::TIMEOUT )
            {
                logger_.syslog( "Uart error: ", uart_.errorString(), Syslog::ERROR );
                this->setFailure( FailureMode::COMMUNICATIONS );
                setRunState( STOP );
            }
            processPacket = false;
            break;
        }
        else
        {
            // Valid device response
            if( debug_ ) logger_.syslog( "Got valid response. Skip count: " + Str( readCount ) + ".", Syslog::INFO );
            processPacket = readCount < MAX_DEVICE_MSG_QUEUE_SIZE;
            dataTime_ = Timestamp::Now();
        }

        ++readCount;
    }

    // Process device data
    if( processPacket )
    {
        unsigned bytesRead = ( unsigned )uart_.bytesRead();
        deviceResponse_[AuvMath::Min( ( unsigned )MAX_DEVICE_RESPONSE_SIZE, bytesRead )] = '\0';

        if( bytesRead == OUTPUT_LEN )
        {
            return parse();
        }
    }
    else if( readCount >= MAX_DEVICE_MSG_QUEUE_SIZE )
    {
        // Flush the buffer in case we're out of step
        logger_.syslog( "Faild to get valid response or reached max message skip count.", Syslog::ERROR );
        uart_.flush();
    }

    return false;
}

bool WetLabsBB2FL::parseHeader()
{
    char serial[50];

    char* trimmedDeviceResponse = strstr( deviceResponse_, "Ser" );

    // coverity[secure_coding] // indicate that the code below is indeed safe
    if( NULL != trimmedDeviceResponse
            && 2 == sscanf( trimmedDeviceResponse, "%*s %s  %*s %*s %*s %*s %d",
                            serial, &currentSamplesToAverage_ ) )
    {
        if( serial_ != serial )
        {
            logger_.syslog( "Serial # mismatch, got ", serial, Syslog::ERROR );
        }
        return true;
    }
    if( debug_ ) logger_.syslog( "Error parsing header: ", deviceResponse_, Syslog::ERROR );
    return false;
}

bool WetLabsBB2FL::parse()
{
    char* trimmedDeviceResponse = strstr( deviceResponse_, "99/99/99" );
    // coverity[secure_coding] // indicate that the code below is indeed safe
    int wavelengths[3];
    int outputs[3];
    if( NULL != trimmedDeviceResponse
            && 6 == sscanf( trimmedDeviceResponse, "%*d/%*d/%*d %*d:%*d:%*d %d %d %d %d %d %d",
                            &wavelengths[0], &outputs[0], &wavelengths[1], &outputs[1], &wavelengths[2], &outputs[2] ) )
    {
        for( int i = 0; i < 3; i++ )
        {
            switch( wavelengths[i] )
            {
            case 460: // CDOM
                outputCdom_ = outputs[i];
                if( outputCdom_ < 4096 && outputCdom_ >= darkCountsCdom_ )
                {
                    cdomConc_ = scaleFactorCdom_ * ( outputCdom_ - darkCountsCdom_ );
                    outputCdomWriter_->setInvalid( false );
                    cdomConcWriter_->setInvalid( false );
                }
                else
                {
                    outputCdomWriter_->setInvalid( true );
                    cdomConcWriter_->setInvalid( true );
                }
                break;
            case 470:
                output470_ = outputs[i];
                if( output470_ < 4096 && output470_ >= darkCounts470_ )
                {
                    volScat470_ = scaleFactor470_ * ( output470_ - darkCounts470_ );
                    output470Writer_->setInvalid( false );
                    volScat470Writer_->setInvalid( false );
                }
                else
                {
                    output470Writer_->setInvalid( true );
                    volScat470Writer_->setInvalid( true );
                }
                break;
            case 650:
                output650_ = outputs[i];
                if( output650_ < 4096 && output650_ >= darkCounts650_ )
                {
                    volScat650_ = scaleFactor650_ * ( output650_ - darkCounts650_ );
                    output650Writer_->setInvalid( false );
                    volScat650Writer_->setInvalid( false );
                }
                else
                {
                    output650Writer_->setInvalid( true );
                    volScat650Writer_->setInvalid( true );
                }
                break;
            case 695: //Chl
                outputChl_ = outputs[i];
                if( outputChl_ < 4096 && outputChl_ >= darkCountsChl_ )
                {
                    chlConc_ = scaleFactorChl_ * ( outputChl_ - darkCountsChl_ );
                    outputChlWriter_->setInvalid( false );
                    chlConcWriter_->setInvalid( false );
                }
                else
                {
                    outputChlWriter_->setInvalid( true );
                    chlConcWriter_->setInvalid( true );
                }
                break;
            default:
                logger_.syslog( "Error parsing data, unexpected wavelength found:", wavelengths[i], Syslog::ERROR );
            }
        }
        return true;
    }
    if( debug_ ) logger_.syslog( "Error parsing data: ", deviceResponse_, Syslog::ERROR );
    return false;
}

void WetLabsBB2FL::updateTemporalBin()
{
    chlBin_.add( chlConc_, dataTime_ );
    cdomBin_.add( cdomConc_, dataTime_ );
    Timestamp binTime;
    // Downsample data, only write mean when all data within window is new
    if( chlBin_.window_refreshed() )
    {
        binTime = chlBin_.median_time();
        binMedianChlWriter_->write( Units::MICROGRAM_PER_LITER, chlBin_.median(), binTime );
        binMeanChlWriter_->write( Units::MICROGRAM_PER_LITER, chlBin_.mean(), binTime );
        binVarianceChlWriter_->write( Units::MICROGRAM_PER_LITER, chlBin_.variance(), binTime );
    }
    if( cdomBin_.window_refreshed() )
    {
        binTime = cdomBin_.median_time();
        binMedianCdomWriter_->write( Units::MICROGRAM_PER_LITER, cdomBin_.median(), binTime );
        binMeanCdomWriter_->write( Units::MICROGRAM_PER_LITER, cdomBin_.mean(), binTime );
        binVarianceCdomWriter_->write( Units::MICROGRAM_PER_LITER, cdomBin_.variance(), binTime );
    }
}


void WetLabsBB2FL::writeData()
{
    output470Writer_->write( Units::COUNT, output470_, dataTime_ );
    output650Writer_->write( Units::COUNT, output650_, dataTime_ );
    outputChlWriter_->write( Units::COUNT, outputChl_, dataTime_ );
    outputCdomWriter_->write( Units::COUNT, outputCdom_, dataTime_ );
    volScat470Writer_->write( Units::RECIPROCAL_METER_PER_STERADIAN, volScat470_, dataTime_ );
    volScat650Writer_->write( Units::RECIPROCAL_METER_PER_STERADIAN, volScat650_, dataTime_ );
    chlConcWriter_->write( Units::MICROGRAM_PER_LITER, chlConc_, dataTime_ );
    cdomConcWriter_->write( Units::PART_PER_BILLION, cdomConc_, dataTime_ );

    // TODO: this section below seems more like data preprocessing. It shouldn't be in the writeData method
    if( !volScat470Writer_->isInvalid() || !volScat650Writer_->isInvalid() )
    {
        float salinity( nanf( "" ) );
        float theta = D2R( 117.0 );
        float d = 0.09;
        bool haveSalinity = salinityReader_->wasTouchedSinceLastRun( this )
                            && salinityReader_->read( Units::PRACTICAL_SALINITY_UNIT, salinity )
                            && !isnan( salinity );
        if( haveSalinity && !volScat470Writer_->isInvalid() )
        {
            // These calculations are from the Triplet Puck User’s Guide, Revision H
            float Bw_117_470 = 1.38 * pow( 470.0 / 500.0, -4.32 ) * ( 1 + 0.3 * salinity / 37 ) * 1e-4 * ( 1 + pow( cos( theta ), 2 ) * ( 1 - d ) / ( 1 + d ) );
            float Bp_117_470 = volScat470_ - Bw_117_470;
            float bw_470;
            if( salinity < 35 )
            {
                bw_470 = 0.0022533 * pow( 470.0 / 500.0, -4.23 ) * 1e-4;
            }
            else
            {
                bw_470 = 0.0029308 * pow( 470.0 / 500.0, -4.24 ) * 1e-4;
            }
            float bbw_470 = bw_470 / 2;
            float bbp_470 = M_2PI * 1.1 * Bp_117_470;
            backScat470_ = bbp_470 + bbw_470;
            backScat470Writer_->write( Units::RECIPROCAL_METER, backScat470_, dataTime_ );
            backScat470Writer_->setInvalid( false );
        }
        else
        {
            backScat470Writer_->setInvalid( true );
        }
        if( haveSalinity && !volScat650Writer_->isInvalid() )
        {
            // These calculations are from the Triplet Puck User’s Guide, Revision H
            float Bw_117_650 = 1.38 * pow( 650.0 / 500.0, -4.32 ) * ( 1 + 0.3 * salinity / 37 ) * 1e-4 * ( 1 + pow( cos( theta ), 2 ) * ( 1 - d ) / ( 1 + d ) );
            float Bp_117_650 = volScat650_ - Bw_117_650;
            float bw_650;
            if( salinity < 35 )
            {
                bw_650 = 0.0022533 * pow( 650.0 / 500.0, -4.23 ) * 1e-4;
            }
            else
            {
                bw_650 = 0.0029308 * pow( 650.0 / 500.0, -4.24 ) * 1e-4;
            }
            float bbw_650 = bw_650 / 2;
            float bbp_650 = M_2PI * 1.1 * Bp_117_650;
            backScat650_ = bbp_650 + bbw_650;
            backScat650Writer_->write( Units::RECIPROCAL_METER, backScat650_, dataTime_ );
            backScat650Writer_->setInvalid( false );
        }
        else
        {
            backScat650Writer_->setInvalid( true );
        }
    }
    else
    {
        backScat470Writer_->setInvalid( true );
        backScat650Writer_->setInvalid( true );
    }

}

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

bool WetLabsBB2FL::isDataRequested()
{
    return chlConcWriter_->isDataRequested()
           || volScat470Writer_->isDataRequested()
           || volScat650Writer_->isDataRequested()
           || cdomConcWriter_->isDataRequested();
}

void WetLabsBB2FL::uninitialize()
{
    if( !simulateHardware() )
    {
        uart_.close();

        logger_.syslog( "Powering down", Syslog::INFO );
        if( !loadControl_.powerDown() )
        {
            logger_.syslog( "Failed to power down", Syslog::FAULT );
            this->setFailure( FailureMode::HARDWARE );
        }
    }
}
