/** \file
 *
 *  Contains the Rowe_600 class implementation.
 *
 *  Copyright (c) 2018, MBARI
 *  MBARI Proprietary Information.  All Rights Reserved
 *
 */

/*


The Rowe 600 needs to be configured prior to use. This component currently expects bottom track
capability output in PD13 message format as shown below. Also it's expected that the mounting
be with the notch 45 degrees aft of starboard.

:SA,   +0.00,   +0.00,  0.00
:TS,19022622091777,35.0, -0.1,   0.0,1448.9,  0
:RA,  0.00,   0.00,   0.00,   0.00,   0.00
:BI,-32768,-32768,-32768,-32768,V
:BS,-32768,-32768,-32768,V
:BE,-32768,-32768,-32768,V
:BD,        +0.00,        +0.00,        +0.00,   0.00, 54.00

Additionally CEAUTOSTART should be set to 2. At the time of writing the spec (version AK - March 2018)
as well as the wiki (http://rowetechinc.co/wiki/index.php?title=CEAUTOSTART) did not accurately reflect the functionality of the CEAUTOSTART command.
CEAUTOSTART was set to 2, followed by a CSAVE command, followed by a START command. Once pinging, power was removed and upon restoration, the unit
began pinging automatically as specified.

An example confiugration is shown below.
DP600 System Configuration:
  Mode DVL
  Sim 0
  CEPO l
  CEAUTOSTART 2
  CEI 00:00:01.00
  CERECORD 0,0,0
  C232OUT 113
  C485OUT 0
  C422OUT 0
  CMACOUT 0
  CUDPOUT 0
  CBTON[0] 1
  CBTBB[0] 7, 0.000, 50.00, 4
  CBTST[0] 0.900,10.000,10.000
  CBTBL[0] 0.10,10.00
  CBTMX[0] 180.00,1
  CBTTBP[0] 0.13
  CBTT[0] 30.0,50.0,10.0,4.0
  CBTFILT 0.000,0.000,10,1.000,0
  CWTON[0] 0
  CWTBB[0] 1
  CWTBL[0] 4.00
  CWTBS[0] 2.00
  CWTTBP[0] 0.00
  CWSSC 0,0,0,0
  CWS 35.00
  CWT 15.00
  CTD 0.00
  CWSS 1498.00
  CHO 0.00,90.00,0.00
  CHS 1
  CTS 1
  CVSF[0] 1.000,1.000
  C232B 115200,8,0,1
  C485B 115200,8,0,1
  C422B 115200,8,0,1
  C485G 0.05
  CTRIG 0,0
  COUTTRIG 0
  SN01l00000000000000000000000000464
  System Firmware Version: 00.02.129 Jun 28 2019 06:07:55
*/

#include "Rowe_600.h"
#include "Rowe_600IF.h"
#include "Power24vConverterIF.h"

#include <cstdlib>

#include "data/BlobWriter.h"
#include "data/ConfigReader.h"
#include "data/SimSlate.h"
#include "data/UniversalDataReader.h"
#include "data/UniversalDataWriter.h"
#include "units/Units.h"
#include "utils/AuvMath.h"

#define DVL_M_ACCURACY (0.001)

Rowe_600::Rowe_600( const Module* module )
    : SyncSensorComponent( Rowe_600IF::NAME, module ),
      hasPD13_( false ),
      dvlTimeout_( 10.0 ),
      dvlFailTimeout_( 180 ),
      powerOnTimeout_( 12 ),
      debug_( false ),
      dvlTimeoutArmed_( false ),
      uart_( Rowe_600IF::UART, Rowe_600IF::BAUD, 0.010, logger_, UART_BUFSIZE ),
      loadControl_( Rowe_600IF::LOAD_CONTROL, !simulateHardware(), logger_, this ),
      veloInst_( 0 ),
      veloInstX_( 0 ),
      veloInstY_( 0 ),
      veloInstZ_( 0 ),
      veloError_( -32000 ),
      veloInstFlag_( 0 ),
      altEstimate_( -1 ),
      beam1Range_( -1 ),
      beam2Range_( -1 ),
      beam3Range_( -1 ),
      beam4Range_( -1 )

{
    power24vConverterDataReader_ = newDataReader( Power24vConverterIF::POWER_24V_CONVERTER );

    altitudeWriter_ = newUniversalWriter( UniversalURI::HEIGHT_ABOVE_SEA_FLOOR, Units::METER, DVL_M_ACCURACY );
    velocityWrtGroundWriter_ = newUniversalBlobWriter( UniversalURI::PLATFORM_VELOCITY_WRT_GROUND, Units::METER_PER_SECOND, DVL_M_ACCURACY );
    xVelWrtGroundWriter_ = newUniversalWriter( UniversalURI::PLATFORM_X_VELOCITY_WRT_GROUND, Units::METER_PER_SECOND, DVL_M_ACCURACY );
    yVelWrtGroundWriter_ = newUniversalWriter( UniversalURI::PLATFORM_Y_VELOCITY_WRT_GROUND, Units::METER_PER_SECOND, DVL_M_ACCURACY );
    zVelWrtGroundWriter_ = newUniversalWriter( UniversalURI::PLATFORM_Z_VELOCITY_WRT_GROUND, Units::METER_PER_SECOND, DVL_M_ACCURACY );
    veloInstFlagWriter_  = newDataWriter( Rowe_600IF::BOTTOM_VELOCITY_FLAG_READING );

    beam1RangeWriter_  = newDataWriter( Rowe_600IF::BEAM1RANGE );
    beam2RangeWriter_  = newDataWriter( Rowe_600IF::BEAM2RANGE );
    beam3RangeWriter_  = newDataWriter( Rowe_600IF::BEAM3RANGE );
    beam4RangeWriter_  = newDataWriter( Rowe_600IF::BEAM4RANGE );

    this->setAllowableFailures( 3 );
    this->setRetryTimeout( 180 );
    // This configures the advanced run modes.
    setRunState( START );
}

Rowe_600::~Rowe_600()
{
}

void Rowe_600::run()
{
}


Component::RunState Rowe_600::start()
{
    if( debug_ ) logger_.syslog( "Start", Syslog::INFO );

    // Start the clock for PO timeout
    startTime_ = Timestamp::Now();

    // Request 24V power
    power24vConverterDataReader_->requestData( true );

    if( simulateHardware() )
    {
        return STARTING;
    }

    deviceResponse_[0] = '\0';

    if( !loadControl_.powerUp() )
    {
        logger_.syslog( Str( "load controller failed to power up." ), Syslog::FAULT );
        this->setFailure( FailureMode::HARDWARE );
        return START;
    }

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

    if( !isDataRequested() )
    {
        return STOP;
    }

    return STARTING;
}


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

    // If a ping has come in we might as well get on with it
    if( uart_.canReadUntil( ":SA" ) )
    {
        startTime_ = Timestamp::Now();
        return RUNNABLE;
    }

    if( startTime_.elapsed() < powerOnTimeout_ )
    {
        return STARTING; // give it some more time to power up
    }
    else
    {
        // We're ready to go. Reset the clock and head for runnable.
        startTime_ = Timestamp::Now();
        return RUNNABLE;
    }
}


Component::RunState Rowe_600::pause()
{
    if( debug_ ) logger_.syslog( "Pause", Syslog::INFO );
    return PAUSED;
}

// Not using pause/paused
Component::RunState Rowe_600::paused()
{
    if( debug_ ) logger_.syslog( "Paused", Syslog::INFO );
    return STOP;
}


Component::RunState Rowe_600::resume()
{
    if( debug_ ) logger_.syslog( "Resume", Syslog::INFO );
    return STOP;
}


// Stop
Component::RunState Rowe_600::resuming()
{
    if( debug_ ) logger_.syslog( "Resuming", Syslog::INFO );
    return STOP;
}

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

    if( !simulateHardware() ) logVoltageAndCurrent();

    // Pause if we don't want data
    if( !isDataRequested() )
    {
        return STOP;
    }

    // Run checks on data timeouts, etc.
    if( !checkTimeouts() )
    {
        return STOP;
    }

    hasPD13_ = simPD13(); // Always attempt to read simulated values, for vehicle in the loop sims

    if( !simulateHardware() )
    {
        hasPD13_ = readPD13();
    }

    if( hasPD13_ )
    {
        processPD13();
        writeData();
        this->resetFailCount();
    }

    return RUNNABLE;
}

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

// Set the writers invalid
    setAcousticWritersInvalid( true );
    altitudeWriter_->setInvalid( true );
    uninitialize(); // First power down then query for faults next cycle
    return STOPPING;
}

Component::RunState Rowe_600::stopping()
{
    if( debug_ ) logger_.syslog( "Stopping", Syslog::INFO );

    if( !simulateHardware() )
    {
        loadControl_.readFaults(); // See if anything went wrong that may have caused this request for uninitialize
        if( loadControl_.hasError() )
        {
            // Put anything that isn't an actual fault first
            if( loadControl_.errorString().find( "Software" ) )
            {
                if( debug_ )logger_.syslog( "LCB error:" + loadControl_.errorString(), Syslog::ERROR );
            }
            // And things that set failures second
            else
            {
                logger_.syslog( "LCB fault: " + loadControl_.errorString(), Syslog::FAULT );
                this->setFailure( FailureMode::HARDWARE );
            }
        }

    }
    return STOPPED;
}


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

    if( isDataRequested() )
    {
        return START;
    }

    if( !simulateHardware() )
    {
        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;
}


void Rowe_600::uninitialize()
{
    if( !simulateHardware() )
    {
        uart_.close();
        uart_.disableUART();
    }

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

    // 24V power is no longer needed
    power24vConverterDataReader_->requestData( false );
}


// Log voltage and current and check for any faults
void Rowe_600::logVoltageAndCurrent() // TODO: Elevate to superclass
{
    loadControl_.requestVoltageAndCurrent();
    if( loadControl_.hasError() )
    {
        // Put anything that isn't an actual fault first
        if( loadControl_.errorString().find( "Software" ) )
        {
            if( debug_ )logger_.syslog( "LCB error:" + loadControl_.errorString(), Syslog::ERROR );
        }
        // And things that set failures second
        else
        {
            logger_.syslog( "LCB fault: " + loadControl_.errorString(), Syslog::FAULT );
            this->setFailure( FailureMode::HARDWARE );
            stop();
        }
    }
}


ConfigURI Rowe_600::getConfigURI( ConfigOption configOption ) const
{
    switch( configOption )
    {
    case CONFIG_POWER:
        return Rowe_600IF::POWER;
    case CONFIG_SIMULATE_HARDWARE:
        return Rowe_600IF::SIMULATE_HARDWARE;
    default:
        return ConfigURI::NO_CONFIG_URI;
    }
}

void Rowe_600::readConfig()
{

}

bool Rowe_600::checkTimeouts()
{
    // Is non-simulated data available in time since the last ping?
    if( !simulateHardware() &&  startTime_.elapsed() > dvlTimeout_ )
    {
        logger_.syslog( Str( "No DVL communication! Re-initializing" ), Syslog::ERROR );
        this->setFailure( FailureMode::COMMUNICATIONS );
        return false;
    }
    // Next, check overall valid data timeout
    if( !dvlTimeoutArmed_ )
    {
        dvlFailTime_ = Timestamp::Now();
        dvlTimeoutArmed_ = true;
    }

    if( dvlFailTime_.elapsed() > dvlFailTimeout_ )
    {
        logger_.syslog( "DVL failed to acquire valid data within timeout.", Syslog::FAULT );
        this->setFailure( FailureMode::DATA );
        dvlTimeoutArmed_ = false;
        return false;
    }
    return true;
}


bool Rowe_600::simPD13()
{
    bool goodSim = false;

    SimDvlStruct dvlSimData;

    // Get the latests simulated DVL data
    SimSlate::Read( dvlSimData );

    // WRT Ground including altitude
    if( !isnan( dvlSimData.bottomRange ) )
    {
        // Convert values from meters/s to mm/s (and flip sign so it
        // is the velocity of the ground relative to the instrument)
        veloInstX_ = -dvlSimData.velocityWrtBottom.getU() * 1000.0;
        veloInstY_ = -dvlSimData.velocityWrtBottom.getV() * 1000.0;
        veloInstZ_ = -dvlSimData.velocityWrtBottom.getW() * 1000.0;

        beam1Range_ =
            !isnan( dvlSimData.beamRanges[0] ) ? dvlSimData.beamRanges[0] : -1;
        beam2Range_ =
            !isnan( dvlSimData.beamRanges[1] ) ? dvlSimData.beamRanges[1] : -1;
        beam3Range_ =
            !isnan( dvlSimData.beamRanges[2] ) ? dvlSimData.beamRanges[2] : -1;
        beam4Range_ =
            !isnan( dvlSimData.beamRanges[3] ) ? dvlSimData.beamRanges[3] : -1;
        altEstimate_ = dvlSimData.bottomRange;

        // We have valid simulated data
        goodSim = true;
        setAcousticWritersInvalid( false );
        startTime_ = Timestamp::Now();  // roughly set the time for the incoming data here
        dvlTimeoutArmed_ = false;
    }
    else
    {
        goodSim = false;
    }

    return goodSim;
}


bool Rowe_600::readPD13()
{
    // See if an entire message has made it in
    // If so, we want to parse the lines: :TS, :RA, :BI, and :BD
    if( uart_.canReadUntil( ":SA" ) && uart_.canReadUntil( ":BD" ) )
    {
        if( debug_ ) logger_.syslog( "Message in queue", Syslog::INFO );
        // Parse the response
        if( !parsePD13() )
        {
            logger_.syslog( "Failed to parse:", deviceResponse_, Syslog::ERROR );
            setAcousticWritersInvalid( true );
            uart_.flush();
            return false;
        }
        else // Parsing was sucessfull
        {
            setAcousticWritersInvalid( false );
            startTime_ = Timestamp::Now();  // roughly set the time for the incoming data here
            return true;
        }
    }
    else // There isn't a PD13 string in the UART yet.
    {
        return false;
    }
}


bool Rowe_600::parsePD13()
{
    char *ptr;
    int scanRes = 0;
    // Line :SA is a throwaway
    uart_.readLine( deviceResponse_, sizeof( deviceResponse_ ) );

    // Line :TS is also a throwaway since this version of firmware outputs either 6 or 9 fields and does not specify actual values for BIT error.
    uart_.readLine( deviceResponse_, sizeof( deviceResponse_ ) );
    if( uart_.hasError() )
    {
        deviceResponse_[0] = '\0';
        logger_.syslog( "DVL uart error: ", uart_.errorString(), Syslog::ERROR );
        this->setFailure( FailureMode::COMMUNICATIONS );
        setRunState( STOP );
        return false;
    }
    // Here we just want the BIT error if any is present
    if( ( ptr = strstr( deviceResponse_, ":TS" ) ) )
    {
        bitError_ = 1; // reinitialize value
        scanRes = sscanf( ptr, ":TS,%*d,%*f,%*f,%*f,%*f,%d", &bitError_ );
        if( debug_ ) logger_.syslog( "BIT Error is:", bitError_, Syslog::INFO );
        if( scanRes != 1 )
        {
            logger_.syslog( "only read " + Str( scanRes ) + " of 1 data item for BIT error", Syslog::ERROR );
            return false;
        }
        if( bitError_ != 0 )
        {
            logger_.syslog( "DVL BIT error. See manual. Result code: ", bitError_, Syslog::ERROR );
        }
    }
    else
    {
        return false; // Expecting to see :TS and didn't
    }


    // Line :RA
    uart_.readLine( deviceResponse_, sizeof( deviceResponse_ ) );
    if( uart_.hasError() )
    {
        deviceResponse_[0] = '\0';
        logger_.syslog( "DVL uart error: ", uart_.errorString(), Syslog::ERROR );
        this->setFailure( FailureMode::COMMUNICATIONS );
        setRunState( STOP );
        return false;
    }
    // Here we want the range to bottom data
    if( ( ptr = strstr( deviceResponse_, ":RA" ) ) )
    {
        beam1Range_ = beam2Range_ = beam3Range_ = beam4Range_ = -1; // Initialize for now
        scanRes = sscanf( ptr, ":RA,%*f,%lf,%lf,%lf,%lf", &beam1Range_, &beam2Range_, &beam3Range_, &beam4Range_ );

        if( debug_ ) logger_.syslog( "Beam 1 Range:", beam1Range_, Syslog::INFO );
        if( debug_ ) logger_.syslog( "Beam 2 Range:", beam2Range_, Syslog::INFO );
        if( debug_ ) logger_.syslog( "Beam 3 Range:", beam3Range_, Syslog::INFO );
        if( debug_ ) logger_.syslog( "Beam 4 Range:", beam4Range_, Syslog::INFO );

        if( scanRes != 4 )
        {
            logger_.syslog( "only read " + Str( scanRes ) + " of 4 data items", Syslog::ERROR );
            return false;
        }
    }
    else
    {
        return false; // Expecting to see :RA and didn't
    }


    // Line :BI for instrument referenced data
    uart_.readLine( deviceResponse_, sizeof( deviceResponse_ ) );
    if( uart_.hasError() )
    {
        deviceResponse_[0] = '\0';
        logger_.syslog( "DVL uart error: ", uart_.errorString(), Syslog::ERROR );
        this->setFailure( FailureMode::COMMUNICATIONS );
        setRunState( STOP );
        return false;
    }
    // Here we want the bottom track data
    if( ( ptr = strstr( deviceResponse_, ":BI" ) ) )
    {
        veloInstX_ = veloInstY_ = veloInstZ_ = 0; // Initialize for now
        char flag = 'F';
        // forward is +X and -Y is to port.
        scanRes = sscanf( ptr, ":BI,%lf,%lf,%lf,%*f,%c", &veloInstX_, &veloInstY_, &veloInstZ_, &flag );
        if( flag == 'A' )
        {
            veloInstFlag_ = 1;
        }
        else
        {
            veloInstFlag_ = 0;
        }

        // Y direction is inverse for this unit so we'll flip it here.
        veloInstY_ *= -1.0;


        if( debug_ ) logger_.syslog( "Ship Velocity X:", veloInstX_, Syslog::INFO );
        if( debug_ ) logger_.syslog( "Ship Velocity Y:", veloInstY_, Syslog::INFO );
        if( debug_ ) logger_.syslog( "Ship Velocity Z:", veloInstZ_, Syslog::INFO );
        if( debug_ ) logger_.syslog( "Flag:" + Str( flag ), Syslog::INFO );
        if( scanRes != 4 )
        {
            logger_.syslog( "only read " + Str( scanRes ) + " of 4 data items", Syslog::ERROR );
            return false;
        }
    }
    else
    {
        return false; // Expecting to see :BI and didn't
    }

    // Line :BS and :BE are throwaway
    uart_.readLines( deviceResponse_, sizeof( deviceResponse_ ), 2 );

    // Line :BD
    uart_.readLine( deviceResponse_, sizeof( deviceResponse_ ) );
    if( uart_.hasError() )
    {
        deviceResponse_[0] = '\0';
        logger_.syslog( "DVL uart error: ", uart_.errorString(), Syslog::ERROR );
        this->setFailure( FailureMode::COMMUNICATIONS );
        setRunState( STOP );
        return false;
    }
    // Here we want the bottom track data
    if( ( ptr = strstr( deviceResponse_, ":BD" ) ) )
    {
        altEstimate_ = -1;
        scanRes = sscanf( ptr, ":BD,%*f,%*f,%*f,%lf,%*f", &altEstimate_ );

        if( debug_ ) logger_.syslog( "Altitude:", altEstimate_, Syslog::INFO );
        if( scanRes != 1 )
        {
            logger_.syslog( "only read " + Str( scanRes ) + " of 1 data item for altitude", Syslog::ERROR );
            return false;
        }
    }
    else
    {
        return false; // Expecting to see :BD and didn't
    }

    // The data is valid. Reset the clock next time through
    dvlTimeoutArmed_ = false;

    uart_.flush();
    return true;
}


void Rowe_600::processPD13()
{

    // populate the vectors for writers using ship referenced data (navigation frame)
    veloInst_.setU( veloInstX_ );
    veloInst_.setV( veloInstY_ );
    veloInst_.setW( veloInstZ_ );

    // finally, perform validity checks for quality control
    if( veloInstFlag_ == 1 ) // Ground speed is valid
    {
        xVelWrtGroundWriter_->setInvalid( false );
        yVelWrtGroundWriter_->setInvalid( false );
        zVelWrtGroundWriter_->setInvalid( false );
        velocityWrtGroundWriter_->setInvalid( false );
    }
    else // ground speed is not valid
    {
        xVelWrtGroundWriter_->setInvalid( true );
        yVelWrtGroundWriter_->setInvalid( true );
        zVelWrtGroundWriter_->setInvalid( true );
        velocityWrtGroundWriter_->setInvalid( true );
    }

    // finally, perform validity checks for quality control
    if( veloInstFlag_ == 1 ) // Ground speed is valid
    {
        xVelWrtGroundWriter_->setInvalid( false );
        yVelWrtGroundWriter_->setInvalid( false );
        zVelWrtGroundWriter_->setInvalid( false );
        velocityWrtGroundWriter_->setInvalid( false );
    }
    else // ground speed is not valid
    {
        xVelWrtGroundWriter_->setInvalid( true );
        yVelWrtGroundWriter_->setInvalid( true );
        zVelWrtGroundWriter_->setInvalid( true );
        velocityWrtGroundWriter_->setInvalid( true ); // Keep this writer invalid here since we'll be using ship referenced data
    }

    if( altEstimate_ > 0 ) // Altitude has to be greater than 0 (at least by a little) to be valid
    {
        altitudeWriter_->setInvalid( false );
    }
    else
    {
        altitudeWriter_->setInvalid( true );
    }
}


void Rowe_600::writeData()
{
    xVelWrtGroundWriter_->write( Units::MILLIMETER_PER_SECOND, veloInstX_, startTime_ );
    yVelWrtGroundWriter_->write( Units::MILLIMETER_PER_SECOND, veloInstY_, startTime_ );
    zVelWrtGroundWriter_->write( Units::MILLIMETER_PER_SECOND, veloInstZ_, startTime_ );

    velocityWrtGroundWriter_->setAccuracy( Units::METER_PER_SECOND, DVL_M_ACCURACY ); // since writeWithAccuracy does not exist for blob writers
    velocityWrtGroundWriter_->write1DClass( Units::MILLIMETER_PER_SECOND, veloInst_, startTime_ );

    altitudeWriter_->write( Units::METER, altEstimate_, startTime_ );

    veloInstFlagWriter_->write( Units::COUNT, veloInstFlag_, startTime_ );

    beam1RangeWriter_->write( Units::METER, beam1Range_, startTime_ );
    beam2RangeWriter_->write( Units::METER, beam2Range_, startTime_ );
    beam3RangeWriter_->write( Units::METER, beam3Range_, startTime_ );
    beam4RangeWriter_->write( Units::METER, beam4Range_, startTime_ );

    xVelWrtGroundWriter_->write( Units::MILLIMETER_PER_SECOND, veloInstX_, startTime_ );
    yVelWrtGroundWriter_->write( Units::MILLIMETER_PER_SECOND, veloInstY_, startTime_ );
    zVelWrtGroundWriter_->write( Units::MILLIMETER_PER_SECOND, veloInstZ_, startTime_ );
}

void Rowe_600::setAcousticWritersInvalid( bool invalid )
{
    xVelWrtGroundWriter_->setInvalid( invalid );
    yVelWrtGroundWriter_->setInvalid( invalid );
    zVelWrtGroundWriter_->setInvalid( invalid );
    velocityWrtGroundWriter_->setInvalid( invalid );
}

bool Rowe_600::isDataRequested()
{
    return altitudeWriter_->isDataRequested()
           || velocityWrtGroundWriter_->isDataRequested()
           || xVelWrtGroundWriter_->isDataRequested()
           || yVelWrtGroundWriter_->isDataRequested()
           || zVelWrtGroundWriter_->isDataRequested();
}


