//------------------------------------------------------------------------------------------------
// <copyright file="SBGPCTDSensor.h" company="LiquidRobotics">
//
// Copyright (c) 2014 Liquid Robotics, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// This software is associated with a release.  Liquid Robotics makes no
// representation that the design, interfaces or component distribution will
// remain unchanged in future releases.
//
// </copyright>
//
// <summary>
// 	Declaration of SBGPCTDSensor, a boundary interface to a Seabird GPCTD.  The idea is to provide
//  interface functions to every valid command, but certain functions are omitted since the
//  class needs to be able to control some aspects in order to interact the sensor.  Some of these
//  are:
//
//		BaudRate (will change to the configured rate)
//		OutputExecutedTag (always false)
//		OutputFormat (always 1, decimal engineering units)
//		RS232ForceOn (always false since the sensor expects to be able to send commands)
//		TxRealTime (always true)
//
// </summary>
//
// <owner>Mike Cookson</owner>
//------------------------------------------------------------------------------------------------

#ifndef SBGPCTDSENSOR_H_
#define SBGPCTDSENSOR_H_

#include "LqrSMACByteArray.h"
#include "LqrSMACLogger.h"
#include "LqrSMACLockableConcept.h"
#include "LqrSMACScopedConnection.h"
#include "LqrSMACSemaphore.h"
#include "LqrSMACSharedLib.h"
#include "LqrSMACSignalSubscriber.h"
#include "LqrSMACTimeCounter.h"
#include <sstream>

namespace LiquidRobotics {

namespace SMACFramework {
	class ISerialPort;
	class SerialListener;
}

namespace SeabirdGPCTD {

class SBGPCTDSensorSettings;

/*! \brief Direct interface to Seabird GPCTD sensor
 *
 * This class provides a full interface to the sensor, with Boost signlas to
 * report asynchronous conditions.  The "controller" role class can implement
 * the boundary interface itself, but it's useful for clarity to break out
 * the interface functions in a separate class if the messaging is involved
 * enough.
 *
 * Consult the Seabird Glider Payload CTD Manual for complete information
 * about the message set (manual version #004, 09/26/13, for firmware version
 * 1.2.1 and later)
 */
class SHARED_LIB_PUBLIC SBGPCTDSensor
{
public:

	//! Smart pointer to this class
	typedef boost::shared_ptr<SBGPCTDSensor> Ptr;

	//! Weak reference to this class
	typedef boost::weak_ptr<SBGPCTDSensor> WPtr;

	//! Creates typedefs for the LockableConcept classes
	LOCKABLE_CONCEPT_TYPEDEFS();

	//! Creates typedefs for the ScopedConnection classes
	SCOPED_CONNECTION_TYPEDEFS();

	//! Creates typedefs for the Logger classes
	SMF_LOGGER_TYPEDEFS();

	//! Define a short-cut for string (don't want "using namespace" in header files)
	typedef std::string string;

	//! Define a type for the data receive signal, which is used internally to report received serial data blocks
	typedef boost::signals2::signal<void (string& input)> DataReceiveSignalType;

	/*! \brief Parsed form of the sensor's ConfigurationData block
	 *  \sa SBGPCTDSensor::GetConfigurationData()
	 *
	 *  ConfigurationData is returned from the sensor's "getcd" command, and
	 *  is returned from the GetConfigurationData() method.  This is a data
	 *  entity, so it's implemented as a structure.
	 */
	struct ConfigData {

		/*! \brief Class default constructor
		 *  \sa ConfigData(const string& _line)
		 */
		ConfigData();

		/*! \brief Class constructor with input from the sensor
		 *  \sa ConfigData()
		 *
		 *  Parses the input and fills out the member variables
		 */
		ConfigData(
				const string& _line		/**< [in] Input from the sensor to be parsed */
				);

		/*! \brief Produces a string equivalent of the class members
		 *  \returns A string with representations of the class members, useful for logging or debugging
		 */
		string ToString() const;

		string line;					//!< A copy of the constructor parameter
		string deviceType;				//!< DeviceType from XML (a readable string that indicates the model)
		string serialNumber;			//!< SerialNumber from XML (the serial number of the sensor)
		string outputFormat;			//!< SampleDataFormat from XML ("OutputFormat" setting)
		string rs232ForceOn;			//!< RS232ForceOn from XML ("RS232ForceOn" setting)
		string txRealTime;				//!< TxRealTime form XML ("TxRealTime" setting)
		string sampleInterval;			//!< SampleInterval from XML ("Interval" setting)
		string sampleMode;				//!< SampleMode from XML ("Interval" setting)
		string autoRun;					//!< AutoRun from XML ("AutoRun" setting)
		string sbe43;					//!< SBE43 from XML ("OxygenInstalled" setting)
		string minCondFreq;				//!< MinCondFreq from XML ("MinCondFreq" setting)
		string customPumpMode;			//!< CustomPumpMode from XML ("CustomPumpMode" setting)
	};

	/*! \brief Used by HardwareData to hold parsed ID, device type and serial number for a component
	 *  \sa HardwareData
	 */
	struct HardwareID {

		//! Defines a vector (array) of HardwareID
		typedef std::vector<HardwareID> Vector;

		/*! \brief Produces a string equivalent of the class members
		 *  \returns A string with representations of the class members, useful for logging or debugging
		 */
	    string ToString() const;

		string id;						//!< id from XML, like "Temperature" or "Conductivity" */
		string deviceType;				//!< type from XML, like "temperature-1" or "conductivity-1" */
		string serialNumber;			//!< serialNumber from XML */
	};

	/*! \brief Parsed form of the sensor's HardwareData block
	 *  \sa SBGPCTDSensor::GetHardwareData()
	 *
	 *  HardwareData is returned from the sensor's "gethd" command, and is
	 *  returned form the GetHardwareData() method.  This is a data entity,
	 *  so it's implemented as a structure.
	 */
	struct HardwareData {

		/*! \brief Class default constructor
		 *  \sa HardwareData(const string& _line)
		 */
	    HardwareData();

		/*! \brief Class constructor with input from the sensor
		 *  \sa HardwareData()
		 *
		 *  Parses the input and fills out the member variables
		 */
	    HardwareData(
	    		const string& line		/**< [in] Input from the sensor to be parsed */
	    		);

		/*! \brief Produces a string equivalent of the class members
		 *  \returns A string with representations of the class members, useful for logging or debugging
		 */
	    string ToString() const;

		string line;						//!< A copy of the constructor parameter
		string manufacturer;				//!< Manufacturer from XML
		string firmwareVersion;				//!< FirmwareVersion from XML
		string firmwareDate;				//!< FirmwareDate from XML
	    string commandSetVersion;			//!< CommandSetVersion from XML
		string mfgDate;						//!< MfgDate from XML
		HardwareID mainSensor;				//!< HardwareID picked up from DeviceType and SerialNumber in the XML
		HardwareID::Vector pcbs;			//!< HardwareID's for items in the XML with PCBAssembly as id and PCBSerialNum as serial number
		HardwareID::Vector internalSensors;	//!< HardwareID's for items under InternalSensors in the XML
		HardwareID::Vector externalSensors;	//!< HardwareID's for items under ExternalSensors in the XML
	};


	/*! \brief Parsed form of the sensor's output from UH (upload headers)
	 *  \sa SBGPCTDSensor::UploadHeaders()
	 *
	 *  A Header vector is output from the UploadHeaders() method, which
	 *  contains meta data about samples stored in the sensor.  This is a
	 *  data entity, so it's implemented as a structure.
	 */
	struct Header {

		//! Smart pointer to this class
		typedef boost::shared_ptr<Header> Ptr;

		//! Defines a vector (array) of headers
		typedef std::vector<Header> Vector;

		//! Smart pointer to a vector of headers
		typedef boost::shared_ptr<Vector> VectorPtr;


		/*! \brief Class default constructor
		 *  \sa Header(const string& _line)
		 */
	    Header();

		/*! \brief Class constructor with input from the sensor
		 *  \sa Header()
		 *
		 *  Parses the input and fills out the member variables
		 */
	    Header(
	    		const string& line	/**< [in] Input from the sensor to be parsed */
	    		);

		/*! \brief Produces a string equivalent of the class members
		 *  \returns A string with representations of the class members, useful for logging or debugging
		 */
	    string ToString() const;

		string line;				//!< A copy of the constructor parameter; for example: cast   1 20 May 2014 23:54:31 samples 1 to 1374, int = 1, stop = stop cmd
		unsigned castNumber;		//!< The cast number (sequence number)
		string timeStr;				//!< The time and date portion of the line
		time_t timestamp;			//!< timeStr converted to Unix timestamp (seconds since 1-Januar 1970, GMT)
		unsigned firstSample;		//!< Number of the first sample in the cast
		unsigned lastSample;		//!< Number of the last sample in the cast
		unsigned interval;			//!< Interval (seconds between samples)
		string stopState;			//!< Text from the stop portion of the string
	};

	/*! \brief Enumeration of the possible states of the pump
	 *  \sa SetPump()
	 */
	typedef enum {
		PUMP_OFF,					/**< Turn the pump off */
		PUMP_SLOW,					/**< Turn the pump on, slow speed */
		PUMP_FAST					/**< Turn the pump on, fast speed */
	} PumpStatus;

	/*! \brief Parsed form of the sensor sample with metadata
	 *  \sa SBGPCTDSensor::SampleNow(), SBGPCTDSensor::SampleNowNoPump(), SBGPCTDSensor::SamplePressureNow(), SBGPCTDSensor::asynchronousSample
	 *
	 *  Contains the raw CTD (and possibly O) components, with values that
	 *  cannot be converted or for which there is no data set to NaN.  Latitude
	 *  and longitude are set to 0; the timestamp is set to the system time
	 *  during construction.  This is a data entity, so it's implemented as
	 *  a structure.
	 */
	struct Sample {

		//! Smart pointer to this class
		typedef boost::shared_ptr<Sample> Ptr;

		//! Defines a vector (array) of samples
		typedef std::vector<Sample> Vector;

		//! Smart point to a vector of samples
		typedef boost::shared_ptr<Vector> VectorPtr;

		/*! \brief Class default constructor
		 *  \sa Sample(const string& _line)
		 */
		Sample();

		/*! \brief Class constructor with input from the sensor
		 *  \sa Sample()
		 *
		 *  Parses the input and fills out the member variables
		 */
		Sample(
				const string& rawLine	/**< [in] Sensor output to be parsed */
				);

		/*! \brief Produces a string equivalent of the class members
		 *  \returns A string with representations of the class members, useful for logging or debugging
		 */
		string ToString() const;

		/*! \brief Returns a timestamp for the current time with time_t and sub-second field (timeval)
		 *  \returns timeval set to the current time
		 */
		static timeval GetTimestamp();

		/*! \brief Checks a reading value for NaN to determine if there is a valid value
		 *  \returns true if the number (readingValue) is valid, false if it is NaN
		 */
		static bool HaveReading(
				float readingValue		/**< [in] The reading value to check for validity */
				);

		timeval tv;						//!< time when sample was created
		double latitude;				//!< latitude (to be filled externally)
		double longitude;				//!< longitude (to be filled externally)
		string line;					//!< raw line from the sensor
		string pressureStr;				//!< pressure in decibars (raw from censor)
		float pressure;					//!< pressure in decibars (initializes to NAN)
		string tempCStr;				//!< temperature in Celsius (raw from sensor)
		float tempC;					//!< temperature in Celsiu (initializes to NAN)s
		string condSmStr;				//!< conductivity in S/m (raw from sensor)
		float condSm;					//!< conductivity in S/m (initializes to NAN)
		string disOxyHzStr;				//!< dissolved oxygen frequency in Hz (raw from sensor)
		float disOxyHz;					//!< dissolved oxygen frequency in Hz (initializes to NAN)
	};

	/*! \brief Class constructor
	 *  \sa Initialize()
	 *
	 * Following construction, subscribe to any desired events and call
	 * Initailize() before calling other members.  For example:
	 *
	 * \code
	 * SBGPCTDSensor sensor(settings.GetSensorSettings(), logger));
	 * ScopedConnectionPtr connectionToAsynchronousSample = sensor.asynchronousSample.Subscribe(boost::bind(&MyClass::OnAsynchronousSample, this, _1, _2));
	 * try {
	 *   sensor.Initialize();
	 *   sensor.EstablishComms();
	 *   sensor.SetAutoSamplingInterval(15);
	 *   sensor.StartAutonomousSampling();
	 *   // ... accumulate samples ...
	 * }
	 * catch(exception& ex)
	 * {
	 *   // ... handle runtime errors
	 * }
	 * \endcode
	 */
	SBGPCTDSensor(const SBGPCTDSensorSettings& _settings, Logger& _logger);

	/*! \brief Class destructor
	 *  \sa Shutdown()
	 *
	 * If successfully initialized, release any subscription and call
	 * Shutdown() before deleting.
	 */
	virtual ~SBGPCTDSensor();

	/*! \brief Creates subscriptions and opens any initial resources.
	 *  \sa SBGPCTDSensor(), EstablishComms(), asynchronousSample, rawReceive, rawSend
	 *
	 * Call Initialize() after construction and subscribing to signals
	 * and before calling any other members.  Note that returning successfully
	 * from Initialize() does not imply that communications with the device are
	 * successful.  Call EstablishComms() to verify connection.
	 */
	void Initialize();

	/*! \brief Closes port and releases resources after previously successful Initialize()
	 *  \sa Initialize()
	 *
	 *  Call this member and release subscriptions before deleting this.
	 */
	void Shutdown();

	/*! \brief Enter quiescent (low power) mode
	 *
	 * Issues the "qs" command to the sensor, which causes it to enter a
	 * lower power quiescent mode.  In this state, the sensor serial port goes
	 * to sleep and drops some of the initial data while it wakes up again;
	 * subsequent command will take a little bit longer to wake up the sensor.
	 *
	 * Throws std::runtime_error to flag execution errors.
	 */
	void EnterQuiescentMode();

	/*! \brief Establishes communication with the sensor
	 *  \returns true if successful, false if an execution error occurred (only if throwOnFailure=true; by default, failure will throw and exception.)
	 *  \sa TryComms()
	 *
	 * Tries communication at various baud rates using TryComms(), which also
	 * powers up the sensor.  It is recommended to call EstablishComms() before
	 * other methods since TryComms() also sets the initial sensor configuration.
	 * If the sensor can communicate, but is not at the desired baud rate,
	 * ChangeBaudRates() is called to move to the desired rate.
	 *
	 * By default, EstablishComms() throws std::runtime_error to flag various
	 * execution errors; passing throwOnFailure=false will cause the status
	 * to be returned rather than throwing exceptions.
	 */
	bool EstablishComms(
			bool throwOnFailure = true			/**< [in] true (default) if throwing exceptions for errors, false if returning false in case of errors */
			);

	/*! \brief Read the ConfigurationData structure from the sensor
	 *  \returns ConfigData structure representing the parsed output
	 *
	 *  Issues the "getcd" command to the sensor, which returns the ConfigurationData
	 *  XML structure.  For example, from the manual:
	 *
	 *  \code
	 *  <ConfigurationData DeviceType = 'SBE Glider Payload CTD' SerialNumber = '70112345'>
	 *    <SampleDataFormat>raw Decimal</SampleDataFormat>
	 *    <RS232ForceOn>no</RS232ForceOn>
	 *    <TxRealTime>yes</TxRealTime>
	 *    <SampleInterval>1</SampleInterval>
	 *    <SampleMode>Continuous</SampleMode>
	 *    <AutoRun>no</AutoRun>
	 *    <SBE43>yes</SBE43>
	 *    <MinCondFreq>3011.0</MinCondFreq>
	 *    <CustomPumpMode>no</CustomPumpMode>
	 *  </ConfigurationData>
	 *  \endcode
	 *
	 *  That resulting output is parsed into a ConfigData structure.
	 *
	 * Throws std::runtime_error to flag execution errors.
	 */
	ConfigData GetConfigurationData();

	/*! \brief Display status
	 *  \returns The display status string from the sensor
	 *  \sa GetConfigurationData()
	 *
	 *  Issues the "ds" command to the sensor, which returns a human-readable
	 *  status string; the content is similar to what is returned in ConfigData.
	 *  For example, from the manual:
	 *
	 *  \code
	 *  SBE Glider Payload CTD 1.2.1 SERIAL NO. 12345 25 Sep 2013 09:38:22
	 *  vMain = 9.37, vLith = 3.04
	 *  autorun = no
	 *  samplenumber = 57, free = 559183, profiles = 3
	 *  not logging, never started
	 *  sample every 1 seconds
	 *  sample mode is continuous
	 *  data format = raw Decimal
	 *  do not force on RS232 transmitter
	 *  transmit real time data
	 *  acquire SBE 43 oxygen
	 *  minimum conductivity frequency = 3011.0
	 *  custom pump mode disabled
	 *  \endcode
	 *
	 * Throws std::runtime_error to flag execution errors.
	 */
	string GetDisplayStatus();

	/*! \brief Get calibration coefficients
	 *  \returns The CalibrationCoefficients XML structure reported by the sensor.
	 *
	 *  Issues the "getcc" command to the sensor, which returns an XML-formatted
	 *  CalibrationCoefficients structure.  For example, from the manual:
	 *
	 *  \code
	 *  <CalibrationCoefficients DeviceType = 'SBE Glider Payload CTD' SerialNumber = '70112345'>
	 *    <Calibration format = 'TEMP1' id = 'Main Temperature'>
	 *      <SerialNum>01606001</SerialNum>
	 *      <CalDate>19-Jul-13</CalDate>
	 *      <TA0>1.155787e-03</TA0>
	 *      <TA1>2.725208e-04</TA1>
	 *      <TA2>-7.526811e-07</TA2>
	 *      <TA3>1.716270e-07</TA3>
	 *      <TOFFSET>0.000000e+00</TOFFSET>
	 *    </Calibration>
	 *    <Calibration format = 'WBCOND0' id = 'Main Conductivity'>
	 *      <SerialNum>01606001</SerialNum>
	 *      <CalDate>19-Jul-13</CalDate>
	 *      <G>-1.006192e+00</G>
	 *      <H>1.310565e-01</H>
	 *      <I>-2.437852e-04</I>
	 *      <J>3.490353e-05</J>
	 *      <PCOR>-9.570000e-08</PCOR>
	 *      <TCOR>3.250000e-06</TCOR>
	 *      <WBOTC> -1.351281e-07</WBOTC>
	 *    </Calibration>
	 *    <Calibration format = 'STRAIN0' id = 'Main Pressure'>
	 *      <SerialNum>01600003</SerialNum>
	 *      <CalDate>27-Jul-13</CalDate>
	 *      <PA0>-5.137085e-02</PA0>
	 *      <PA1>1.550601e-03</PA1>
	 *      <PA2>7.210415e-12</PA2>
	 *      <PTCA0>5.154159e+05</PTCA0>
	 *      <PTCA1>2.560262e-01</PTCA1>
	 *      <PTCA2>-8.533080e-02</PTCA2>
	 *      <PTCB0>2.426612e+01</PTCB0>
	 *      <PTCB1>-7.750000e-04</PTCB1>
	 *      <PTCB2>0.000000e+00</PTCB2>
	 *      <PTEMPA0>-7.667877e+01</PTEMPA0>
	 *      <PTEMPA1>4.880376e+01</PTEMPA1>
	 *      <PTEMPA2>-4.555938e-01</PTEMPA2>
	 *      <POFFSET>0.000000e+00</POFFSET>
	 *      <PRANGE>1.000000e+03</PRANGE>
	 *    </Calibration>
	 *    <Calibration format = 'SBE43F0' id = 'Oxygen'>
	 *      <SerialNum>sbe43F</SerialNum>
	 *      <CalDate>27-Aug-13</CalDate>
	 *      <FOffset>3.000000e+03</FOffset>
	 *      <SOC>1.000000e+00</SOC>
	 *      <A>2.000000e+00</A>
	 *      <B>3.000000e+00</B>
	 *      <C>4.000000e+00</C>
	 *      <E>5.000000e+00</E>
	 *      <Tau20>1.000000e+00</Tau20>
	 *      <D1>6.000000e+00</D1>
	 *      <D2>7.000000e+00</D2>
	 *      <H1>8.000000e+00</H1>
	 *      <H2>9.000000e+00</H2>
	 *      <H3>1.000000e+01</H3>
	 *    </Calibration></CalibrationCoefficients>
	 *  \endcode
	 *
	 * Throws std::runtime_error to flag execution errors.
	 */
	string GetDisplayCalibration();

	/*! \brief Get hardware configuration
	 *  \returns The parsed hardware configuration (HardwareData)
	 *  \sa HardwareData
	 *
	 *  Issues the "gethd" command to the sensor, which returns an XML-formatted
	 *  report as text.  For example, from the manual;
	 *
	 *  \code
	 *  <HardwareData DeviceType = 'SBE Glider Payload CTD' SerialNumber = '70112345'>
	 *    <Manufacturer>Sea-Bird Electronics, Inc.</Manufacturer>
	 *    <FirmwareVersion>1.2.1</FirmwareVersion>
	 *    <FirmwareDate>Sep 20 2013 13:17:19</FirmwareDate>
	 *    <CommandSetVersion>1.1</CommandSetVersion>
	 *    <PCBAssembly>41720A</PCBAssembly>
	 *    <PCBSerialNum>50763</PCBSerialNum>
	 *    <PCBAssembly>41660B</PCBAssembly>
	 *    <PCBSerialNum>50874</PCBSerialNum>
	 *    <PCBAssembly>41661B</PCBAssembly>
	 *    <PCBSerialNum>49205</PCBSerialNum>
	 *    <PCBAssembly>41662A</PCBAssembly>
	 *    <PCBSerialNum>48720</PCBSerialNum>
	 *    <MfgDate>01 Aug 2013</MfgDate>
	 *    <FirmwareLoader>SBE 37 FirmwareLoader V 1.0</FirmwareLoader>
	 *    <InternalSensors>
	 *      <Sensor id = 'Temperature'>
	 *        <type>temperature-1</type>
	 *        <SerialNumber>12345</SerialNumber>
	 *      </Sensor>
	 *      <Sensor id = 'Conductivity'>
	 *        <type>conductivity-1</type>
	 *        <SerialNumber>12345</SerialNumber>
	 *      </Sensor>
	 *      <Sensor id = 'Pressure'>
	 *        <type>strain-0</type>
	 *        <SerialNumber>psn</SerialNumber>
	 *      </Sensor>
	 *    </InternalSensors>
	 *    <ExternalSensors>
	 *      <Sensor id = 'Oxygen'>
	 *       <type>SBE43F</type>
	 *       <SerialNumber>12345</SerialNumber>
	 *      </Sensor>
	 *    </ExternalSensors>
	 *  </HardwareData>
	 *  \endcode
	 *
	 *  The structure is parsed and returned as the HardwareData structure.
	 *
	 * Throws std::runtime_error to flag execution errors.
	 */
	HardwareData GetHarwareData();

	/*! \brief Read the last sample that was taken
	 *  \returns The parsed output from the sensor (Sample)
	 *  \sa GetLastPressureSample(), SampleNow(), SampleNowNopump(), SamplePressureNow(), Sample, asynchronousSample
	 *
	 *  Sends the "sl" command to the sensor and returns the parsed output
	 *  as a Sample.
	 *
	 * Throws std::runtime_error to flag execution errors.
	 */
	Sample GetLastSample();

	/*! \brief Read the last pressure sample that was taken
	 *  \returns The parsed output from the sensor (Sample); note that other components of the last reading are not included.
	 *  \sa GetLastSample(), SampleNow(), SampleNowNopump(), SamplePressureNow(), Sample, asynchronousSample
	 *
	 *  Sends the "slp" command to the sensor and returns the parsed output
	 *  as a Sample.
	 *
	 * Throws std::runtime_error to flag execution errors.
	 */
	Sample GetLastPressureSample();

	/*! \brief Read status data
	 *  \returns XML output from the sensor as a string
	 *
	 *  Sends the "getsd" command to the sensor and returns the raw output.
	 *  For example, from the manual:
	 *
	 *  \code
	 *  <StatusData DeviceType = 'SBE Glider Payload CTD' SerialNumber = '70112345'>
	 *    <DateTime>2014-07-17T09:38:36</DateTime>
	 *    <EventSummary numEvents = '65'/>
	 *    <Power>
	 *      <vMain> 9.37</vMain>
	 *      <vLith> 3.04</vLith>
	 *    </Power>
	 *    <MemorySummary>
	 *      <Bytes>855</Bytes>
	 *      <Samples>57</Samples>
	 *      <SamplesFree>559183</SamplesFree>
	 *      <SampleLength>15</SampleLength>
	 *      <Profiles>3</Profiles>
	 *    </MemorySummary>
	 *    <AutonomousSampling>no, never started</AutonomousSampling>
	 *  </StatusData>
	 *  \endcode
	 *
	 * Throws std::runtime_error to flag execution errors.
	 */
	string GetStatusData();

	/*! \brief Check if Initialize() member has run successfully.
	 *  \returns true if Initialize() was called, false if it was not
	 *  \sa Initialize()
	 */
	bool IsInitialized() const;

	/*! \brief Check if the sensor is powered on
	 *  \return true if the sensor is powered, false if it has not been powered on (assumed off)
	 *  \sa IsPowerStateKnown(), SetPower()
	 */
	bool IsPowered() const;

	/*! \brief Check if sensor power has been set on or off
	 *  \return true if sensor power has been set on or off, false if not
	 *  \sa IsPowered(), SetPower()
	 */
	bool IsPowerStateKnown() const;

	/*! \brief Reset logging (clear sample storage on sensor)
	 *
	 *  Sends the "ResetLogging" comamnd to the sensor, which clears sample
	 *  storage on the sensor.
	 *
	 * Throws std::runtime_error to flag execution errors.
	 */
	void ResetLogging();

	/*! \brief Immediately takes a sample with optional pump
	 *  \returns Parsed output from the sensor (Sample)
	 *  \sa GetLastSample(), GetLastPressureSample(), SampleNowNoPump(), SamplePressureNow(), Sample, asynchronousSample
	 *  \sa GetLastSample(), GetLastPressureSample(), SampleNow(), SampleNowNoPump(), SamplePressureNow(), Sample, asynchronousSample
	 *
	 *  Sends the "pts" (with pump) or "ts" (without pump) command to the
	 *  sensor and returns the parsed output.
	 *
	 * Throws std::runtime_error to flag execution errors.
	 */
	Sample SampleNow(
			bool withPump = true		/**< [in] true if using pump (default), false if not using pump */
			);

	/*! \brief Take a set of samples without pump
	 *  \returns Parsed output from the sensor (Sample)
	 *  \sa GetLastSample(), GetLastPressureSample(), SampleNow(), SamplePressureNow(), Sample, asynchronousSample
	 *
	 *  Sends the "tsn" command to the sensor and returns the parsed output
	 *  for an array of results.
	 *
	 * Throws std::runtime_error to flag execution errors or std::invalid_argument
	 * for parameter errors.
	 */
	Sample::VectorPtr SampleNowNoPump(
			unsigned sampleCount			/**< [in] Number of samples to be taken */
			);

	/*! \brief Immediately takes a pressure sample (does not run the pump)
	 *  \returns Parsed output from the sensor (Sample); note that other components are not available in the sample
	 *  \sa GetLastSample(), GetLastPressureSample(), SampleNow(), SampleNowNoPump(), Sample, asynchronousSample
	 *
	 *  Sends the "tp" command to the sensor and returns the parsed output.
	 *
	 * Throws std::runtime_error to flag execution errors.
	 */
	Sample SamplePressureNow();

	/*! \brief Run one of a selected set of sensor commands and return literal response
	 *  \returns The literal output from the specified command
	 *
	 *  The following commands are supported:
	 *
	 *  dc (display calibration coefficients)
	 *  ds (display status)
	 *  getcc (get calibration coefficients)
	 *  getcd (get configuration data: XML)
	 *  getec (get event counters: XML)
	 *  gethd (get hardware configuration: XML)
	 *  getsd (get status data: XML)
	 *  sl (send last sample)
	 *  slp  (send last pressure sample)
	 *
	 * Throws std::runtime_error to flag execution errors.
	 */
	string SendLowLevelCommand(
			const string& command		/**< [in] The low-level command to be sent (dc, ds, getcc, getcd, getec, gethd, getsd, sl, slp) */
			);

	/*! \brief Configure auto-run
	 *
	 * Sends AutoRun=Y or AutoRun=N to the sensor, which enables or disables
	 * automatic start of sampling when power is applied.
	 *
	 * Note: although the GPCTD attempts to ascertain whether it is in water
	 * and will not turn on the pump unless it thinks it is in water, this test
	 * is not always workable.  If the sensor powers up and automatically
	 * runs the pump dry, the pump may be damaged.
	 *
	 * Throws std::runtime_error to flag execution errors.
	 */
	void SetAutoRun(
			bool autoRun = true		/**< [in] true to enable start of sampling on power up, false to disable it */
			);

	/*! \brief Configure the automatic sampling interval
	 *  \sa StartAutonomousSampling()
	 *
	 * Sends the "interval" configuration as specified.  In automated sampling
	 * mode, the sensor will returns samples at the given interval in seconds.
	 *
	 * Throws std::runtime_error to flag execution errors or std::invalid_argument
	 * for parameter errors.
	 */
	void SetAutoSamplingInterval(
			unsigned seconds			/**< [in] The time interval at which to take samples in automated sampling mode (seconds) */
			);

	/*! \brief Set auto-sampling minimum conductivity frequency
	 *  \sa StartAutonomousSampling()
	 *
	 *  Sends the "mincondfreq" parameter, setting it according to the provided
	 *  frequency, which configures the minimum conductivity frequency at which
	 *  the pump will turn on.  This is a measure to reduce the likelihood
	 *  of damaging the pump if the sensor is dry.
	 *
	 * Throws std::runtime_error to flag execution errors or std::invalid_argument
	 * for parameter errors.
	 */
	void SetAutoSamplingMinConductivity(
			float frequency			/**< [in] The frequency to be specified */
			);

	/*! \brief Configure realtime transmit of data when sampling
	 *  \sa StartAutonomousSampling()
	 *
	 *  Sends the "txrealtime" parameter, enabling (true) or disabling (false)
	 *  real time output of samples during sampling.  For example, if samples are
	 *  being stored in the sensor and will be uploaded later, there is no need
	 *  to output them in real time; but if they are being processed dynamically,
	 *  they should be output immediately.
	 *
	 * Throws std::runtime_error to flag execution errors or std::invalid_argument
	 * for parameter errors.
	 */
	void SetAutoSamplingRealtimeTransmit(
			bool transmitRealtime = true	/**< [in] true (default) to enable real time output during sampling, false if it is disabled */
			);

	/*! \brief Configure custom pump mode
	 *  \sa StartAutonomousSampling()
	 *
	 *  Sends the "custompumpmode" parameter, enabling the pump to run at fast
	 *  speed even if an oxygen sensor is not installed (true) or running in
	 *  fast mode only if an oxygen sensor is installed (false)
	 *
	 * Throws std::runtime_error to flag execution errors or std::invalid_argument
	 * for parameter errors.
	 */
	void SetCustomPumpMode(
			bool customMode = true		/**< [in] true if running in custom pump mode (fast even if no oxygen sensor), false if running fast only if oxygen sensor is installed */
			);

	/*! \brief Configure presence of dissolved oxygen sensor
	 *
	 *  Sends the "oxgyeninstalled" parameter, configuring the dissolved oxygen
	 *  sensor as present (true) or absent (false.)
	 *
	 * Throws std::runtime_error to flag execution errors or std::invalid_argument
	 * for parameter errors.
	 */
	void SetOxygenInstalled(
			bool installed = true		/**< [in] true (default) if dissolved oxygen sensor is installed, false if it is absent */
			);

	/*! \brief Set pump state
	 *
	 *  Set the state of the pump to off ("pumpoff"), slow ("pumpslow") or
	 *  fast ("pumpfast") mode
	 *
	 * Throws std::runtime_error to flag execution errors.
	 */
	void SetPump(
			PumpStatus newStatus		/**< [in] The new pump state (PUMP_OFF, PUMP_SLOW, PUMP_FAST) */
			);

	/*! \brief Set the sample number
	 *
	 *  Sends "setsamplenum" parameter, which configures the next slot for a
	 *  sample in internal memory.
	 *
	 * Throws std::runtime_error to flag execution errors or std::invalid_argument
	 * for parameter errors.
	 */
	void SetSampleNumber(
			unsigned sampleNumber		/**< [in] The new "next sample" number; setting 0 is equivalent to ResetLogging */
			);

	/*! \brief Set the sensor time using system time
	 *  \sa SetTime(unsigned year, unsigned month, unsigned day, unsigned hour, unsigned minute, unsigned second)
	 *
	 *  Takes the system local time and sets it using the second SetTime()
	 *  method with enumerated parameters.
	 *
	 * Throws std::runtime_error to flag execution errors or std::invalid_argument
	 * for parameter errors.
	 */
	void SetTime();

	/*! \brief Set the sensor time using specified components
	 *  \sa SetTime()
	 *
	 *  Sends the "datetime" parameter using the specified parameters.
	 *
	 * Throws std::runtime_error to flag execution errors or std::invalid_argument
	 * for parameter errors.
	 */
	void SetTime(
			unsigned year,			/**< [in] year to be set (for example, 2014) */
			unsigned month,		/**< [in] month to be set (1-12) */
			unsigned day,			/**< [in] day of month to be set (1-31) */
			unsigned hour,			/**< [in] hour to be set (0-23) */
			unsigned minute,		/**< [in] minute to be set (0-59) */
			unsigned second		/**< [in] second to be set (0-59) */
			);

	/*! \brief Turns power on or off to the sensor
	 *
	 *  Runs the configured shell command to provide or remove power to the sensor.
	 */
	void SetPower(
			bool on = true			/**< [in] true (default) to turn power on, false to turn power off */
			);

	/*! \brief Put sensor in autonomous sampling mode
	 *  \sa StopAutonomousSampling(), asynchronousSample
	 *
	 *  Sends the the "start" command to the sensor.  Autonomously-output
	 *  readings will be parsed and sent as samples to subscribers
	 *  to asynchronousSample
	 *
	 * Throws std::runtime_error to flag execution errors.
	 */
	void StartAutonomousSampling(
			bool ignoreNotWhileLogging = true	/** [in] true (default) if allowing the command even if already sampling, false if an exception should be thrown if already sampling */
			);

	/*! \brief Take sensor out of autonomous sampling mode
	 *  \sa StartAutonomousSampling()
	 *
	 *  Sends the "stop" command to the sensor.
	 *
	 * Throws std::runtime_error to flag execution errors.
	 */
	void StopAutonomousSampling(
			bool ignoreOnlyWhileLoggingMessage = true	/* [in] true (default) if allowing the command even if not sampling, false if an exception should be thrown if not sampling */
			);


	/*! \brief Upload meta data about stored samples (headers)
	 *  \returns Smart pointer to an array of Header structures, one for each cast, describing sample ranges and dates for each stored cast
	 *  \sa UploadSamples()
	 *
	 *  Sends the "uh" command to the sensor and collects the output data.
	 *  For example, from the manual:
	 *
	 *  \code
	 *  <Headers>
	 *  cast 1 17 Jul 2014 15:41:26 samples 1 to 14, int = 1, stop = stop cmd
	 *  cast 2 17 Jul 2014 16:34:09 samples 39 to 43, int = 1, stop = stop cmd
	 *  </Headers>
	 *  \endcode
	 *
	 *  The output is parsed into Header structures and returned as a smart
	 *  to an array of Header structures.
	 *
	 * Throws std::runtime_error to flag execution errors.
	 */
	Header::VectorPtr UploadHeaders();

	/*! \brief Upload samples from sensor memory relative to a cast
	 *  \returns Smart pointer to an array of samples
	 *  \sa UploadHeaders()
	 *
	 *  Sends the "uc" command to the sensor, which uploads samples relative
	 *  to a cast.  These are converted to Sample structures and returned as
	 *  an array.
	 *
	 * Throws std::runtime_error to flag execution errors or std::invalid_argument
	 * for parameter errors.
	 */
	Sample::VectorPtr UploadSamples(unsigned castNumber);

	//! The type of Boost signal that returns asynchronously reported samples
	typedef boost::signals2::signal<void (SBGPCTDSensor& sender, const Sample& sample)> AsynchronousSampleSignalType;
private:
	LockableConceptMutex firingMutex;							//!< A mutex that is held when firing signals or deallocating subscriptions
	AsynchronousSampleSignalType asynchronousSampleSignal;		//!< The signal that tracks subscribers and fires events about asynchronously reported samples
public:
	LiquidRobotics::SMACFramework::SignalSubscriber<AsynchronousSampleSignalType> asynchronousSample;	//!< The subscriber that allows clients to subscribe or release subscriptions to asynchronously-reported samples

	//! The type of Boost signal that returns messages sent to the sensor
	typedef boost::signals2::signal<void (SBGPCTDSensor& sender, const string& message)> RawSendSignalType;
private:
	RawSendSignalType rawSendSignal;							//!< The signal that tracks subscribers and fires events about messages sent to the sensor
public:
	LiquidRobotics::SMACFramework::SignalSubscriber<RawSendSignalType> rawSend;		//!< The subscriber that allows clients to subscribe or release subscriptions to messages sent to the sensor

	//! The type of Boost signal that returns messages received from the sensor
	typedef boost::signals2::signal<void (SBGPCTDSensor& sender, const string& message)> RawReceiveSignalType;
private:
	RawSendSignalType rawReceiveSignal;							//!< The signal that tracks subscribers and fires events about messages received from the sensor
public:
	LiquidRobotics::SMACFramework::SignalSubscriber<RawReceiveSignalType> rawReceive;  //!< The subscriber that allows clients to subscribe or release subscriptions to messages received from the sensor

private:

	//! Define a short-cut for SMACFramework's ByteArray (don't want "using namespace" in header files)
	typedef LiquidRobotics::SMACFramework::ByteArray ByteArray;

	//! Define a smart pointer to a SMACFramework abstract serial port interface
	typedef boost::shared_ptr<LiquidRobotics::SMACFramework::ISerialPort> ISerialPortPtr;

	//! Define a short-cut for SMACFramework's Semaphore (don't want "using namespace" in header files)
	typedef LiquidRobotics::SMACFramework::Semaphore Semaphore;

	//! Define a short-cut for SMACFramework's SerialListener (don't want "using namespace" in header files)
	typedef LiquidRobotics::SMACFramework::SerialListener SerialListener;

	//! Define a smart pointer to a SMACFramework SerialListener
	typedef boost::shared_ptr<SerialListener> SerialListenerPtr;

	//! Define a short-cut for boost thread (don't want "using namespace" in header files)
	typedef boost::thread Thread;

	//! Define a smart pointer to a boost thread
	typedef boost::shared_ptr<Thread> ThreadPtr;

	//! Define a short-cut for SMACFramework's TimeCounter (don't want "using namespace" in header files)
	typedef LiquidRobotics::SMACFramework::TimeCounter TimeCounter;

	/*! \brief Handler for data that might be an asynchronous sample
	 *
	 *  This method only receives control if other handlers that are expecting
	 *  a result don't handle the data.  It attempts to convert the recieved
	 *  data to an asynchronous sample and fires the Boost event if one is
	 *  found.
	 */
	void OnAsynchronousData(
			string& data						/**< [in] Data received from the serial port */
			);

	/*! \brief Handler for serial data from SerialListener
	 *
	 *  The handler aggregates received data into receivedDataBuffer until
	 *  the end of the data receive is noted (receivedData.size()==0)  It then
	 *  fire the rawReceive signal and the internal commandData signal to look for
	 *  responses to commands.  If any data is left over after commandData, it
	 *  is forward to the asynchonrousData signal.
	 */
	void OnSerialReceive(
			SerialListener& sender,				/**< [in] Reference to signal originator */
			const ByteArray& receivedData		/**< [in] Received data from SerialListener; if invoked with size=0, this indicates the end of received data */
			);

	/*! \brief Sends a command to the sensor and looks for a result
	 *  \returns The response to the command, stripping off the prompt
	 *
	 * Throws std::runtime_error to flag execution errors.
	 */
	string SendCommand(
			const string& command,				/**< [in] The command to be sent to the sensor (without "\r" on the end */
			bool expectPrompt = true			/**< [in] true (default) if expecting a completion prompt after the command, false if not expecting one */
			);

	/*! \brief Sends a command to the sensor and looks for a result, with known first and last lines in the response
	 *  \returns The response to the command, stripping off the prompt
	 *
	 * Throws std::runtime_error to flag execution errors.
	 */
	string SendCommand(
			const string& command,				/**< [in] The command to be sent to the sensor (without "\r" on the end */
			const string& startLine,			/**< [in] The first line expected in the response or blank if not expected */
			const string& endLine,				/**< [in] The last line expected in the respones or blank if not expected */
			bool expectPrompt = true			/**< [in] true (default) if expecting a completion prompt after the command, false if not expecting one */
			);

	/*! \brief Attempts to establish communication with the sensor at a given baud rate
	 *  \returns true if communicating, false if failed to communicate (does not throw)
	 *  \sa EstablishComms()
	 *
	 *  Powers up the sensor and sends <backspace><backspace><cr> at the
	 *  given baud rate a few times, looking for a response prompt.  If
	 *  a response is seen, the logging state is read (CheckLogging()) and
	 *  sampling is stopped if it is running.  Then the interface is
	 *  configured:
	 *
	 *     autorun=n
	 *     outputexecutedtag=n
	 *     outputformat=1
	 *     rs232forceon=n
	 *     txrealtime=y
	 */
	bool TryComms(
			unsigned baudRate			/**< [in] The baud rate to try communicating at; for example, 9600 */
			);

	/*! \brief Determines if the sensor is currently in autonomous sampling mode
	 *  \returns true if sampling, false if not sampling
	 *
	 *  Sends the "ds" command an interprets the response to determine if the
	 *  sensor is currently sampling or idle.
	 *
	 * Throws std::runtime_error to flag execution errors.
	 */
	bool CheckLogging();

	/*! \brief Reconfigures the sensor to use the target baud rate
	 *
	 *  Sends the "baudrate=" command to change from whatever the baud rate
	 *  is currently to the given baud rate.
	 *
	 * Throws std::runtime_error to flag execution errors.
	 */
	void ChangeBaudRates(
			unsigned baudRate			/**< [in] The baud rate to change to; for example, 9600 */
			);

	/*! \brief Verifies that the sensor is reacting normally (not quiescent)
	 *
	 *  If it has been at least 110 seconds since the last receive, sends
	 *  a carriage return and looks for a response prompt.  This is called to
	 *  be sure that the sensor is acting normally before attempting other
	 *  communications.
	 *
	 * Throws std::runtime_error to flag execution errors.
	 */
	void VerifyReacting();

	/*! \brief Writes a string to the serial port and fires the rawSend signal
	 *
	 *  This method is used for all output to the sensor to ensure that
	 *  the expected signals are fired.
	 *
	 * Throws std::runtime_error to flag execution errors.
	 */
	void DoWrite(
			const string& msg			/**< [in] The literal string to be sent to the port */
			);

	/*! \brief Throws an exception with text
	 *
	 * This method is used to ensure that the appropriate exception types
	 * is used.
	 */
	void DoThrow(
			const string& msg			/**< [in] Text of exception to throw */
			);

	/*! \brief Close the port and release resources
	 *
	 *  This method is called internally when the port should be closed
	 *  (such as when an error occurs or when changing baud rates), but
	 *  when the interface is not being shut down.
	 */
	void ClosePort();

	/*! \brief Open the port with a given baud rate
	 *
	 *  Opens the port and creates a new SerialListener instance and
	 *  subscription.
	 *
	 * Throws std::runtime_error to flag execution errors.
	 */
	void OpenPort(
			unsigned baudRate			/**< [in] The baud rate to be used; for example, 9600 baud */
			);

	/*! \brief Calls EstablishComm() only if the port is closed
	 *
	 * Throws std::runtime_error to flag execution errors.
	 */
	void EstablishCommsIfPortClosed();

	const SBGPCTDSensorSettings& settings;					//!< Read-only reference to static sensor settings
	Logger& logger;											//!< Reference on logger to be used
	LockableConceptMutex commandMutex;						//!< Mutex to hold while executing a command
	bool initialized;										//!< true if Initialize() was successfully called, false if not
	bool powered;											//!< true if last power operation turned sensor on, false if it turned it off
	bool powerStateKnown;									//!< true if at least one power operation completed, false if not
	bool sampling;											//!< true if currently sampling (the sensor calls it logging), false if not
	ISerialPortPtr serialPort;								//!< Smart pointer to the serial port (can be null if not communicating)
	SerialListenerPtr serialListener;						//!< Smart pointer to serial listener (can be null if not communicating)
	ScopedConnectionPtr connectionToSerialListener;			//!< Subscription to serial listener callbacks (null if serial listener is null)
	DataReceiveSignalType asynchronousDataSignal;			//!< Signal used internally to subscribe to data that may be asynchronous samples
	LiquidRobotics::SMACFramework::SignalSubscriber<DataReceiveSignalType> asynchronousData; //!< Subscriber for asynchronousDataSignal clients
	DataReceiveSignalType commandDataSignal;				//!< Signal used internally to allow running commands to look for expected output before asynchronous sample processing is executed
	LiquidRobotics::SMACFramework::SignalSubscriber<DataReceiveSignalType> commandData; //!< Subscriber for commandDataSignal clients
	ScopedConnectionPtr connectionToAsynchronousData;		//!< Subscription to asynchronousData
	std::stringstream receivedDataBuffer;					//!< Buffer for serial data from serial listener (waiting for end of receive)
	const string expectedEnd0;								//!< Prompt string
	const string expectedEnd;								//!< Prompt string prefixed with CR/LF
	LockableConceptMutex qsMutex;							//!< Mutex that protects qsCounter
	TimeCounter qsCounter;									//!< Time counter that tracts time since last receive
};

}
}

#endif /* SBGPCTDSENSOR_H_ */
