// **************************************************************************
// File: gqgmc.hh
//
// Author:    Phil Gillaspy
//
// Last Modified: 04/26/2012
//
// Description:
//    Declare the class and its methods describing the capabilities
//    of the GQ Electronics LLC Geiger-Muller Counter (GQ GMC).
//    This code covers the GMC-300 and later models.
//
// INTRODUCTION:
//
// Detailed design documentation is embedded in the source code files.
// The intent is to combine source code and documentation such that a
// separate design document is unnecessary. The author's philosophy is that
// the source code should be considered as part of the design documentation.
// This philosophy was developed over many years of experience wherein the
// design document replicated so much information that was in the source
// code, that doing so generated needless opportunity for inconsistency.
// It is widely recognized that replicating information only increases
// the chances for human error and undue maintenance hardship.
//
// Since the design documentation will be embedded in the source code,
// the organization is constrained to the C/C++ compilation requirements
// that there exist a separate include and source body files. However,
// the author's philosophy is that sofware engineers find the separation
// of information between two files to be an annoyance. It would be much
// preferred that all information were located at the point where the
// software engineer needs to read the code to understand its operation,
// in other words, the source body file (.c,.cc). However, the C/C++
// convention of separate header and body files precludes that idealism.
// It will be realized that various C/C++ constructs and objects appear
// in both the header and source body files. So the following convention
// is followed as to where to embed the documentation: the documentation
// should appear where the object is defined, not where it declared.
// For example, an enumeration which must be defined as publicly
// visible, will be defined only in the header file and consequently
// should be documented in the header file. As another example, a
// class method is declared in the header file, but defined in the source
// file. Therefore the bulk of the documentation for the method should
// be placed in the source file.
//
// The documentation presented here will not cover any nuclear physics
// technical aspects of the GQ Geiger-Muller counter.
//
// GQ GEIGER-MULLER FEATURES
//
// The discussion of the GQ LLC geiger counter is drawn principally
// from the the GMC-300 model with firmware revision 2.15 and later.
// The GMC-300 records high energy particles which when passing through
// geiger-muller tube ionize the gas and create a transient current.
// Each transient pulsed current is considered a 'count'.
// The basic measured form of the radiation is reported in terms of
// counts per second, counts per minute, or the counts per minute
// averaged over an hour. The GMC-300 is also capable of recording
// the aforementioned measures in a history buffer of 64K bytes.
// The GMC-300 provides a USB interface which, however, is physically
// and logically implemented as a traditional RS-232 serial interface.
// The GMC-300 provides a variety of commands to collect data and
// set operational parameters. The reader is referred to the GQ
// Electronics LLC GMC-200 User Manual for a more thorough
// discussion of the its features.
//
//
// SOFTWARE LICENSE STATEMENT
//
// This software contained in the gqgmc.hh and gqgmc.cc files are
// licensed to the public domain under terms of the GNU GPL
// reproduced here.
// Copyright (C) 2012  Phil Gillaspy
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// Additionally, the author, Phil Gillaspy, requires that no copyright
// notice nor acknowledgement to the author is necessary for any
// derivative software using any or all part of this software.
//
// DEVELOPMENT AND BUILD OVERVIEW
//
// The code as written is Linux dependent, however, it should not
// be word-size dependent. So far it has only been compiled for
// 64-bit word size on Linux using GNU g++ 4.6.1. The Eclipse
// IDE was used for software development but this is not a
// necessity. A Makefile is provided so that it is perfectly
// acceptable to compile from the command line using
// "make all", "make bin", or "make lib".  The makefile creates
// a library containing the gqgmc class and its methods. This was
// done so third party usage could simply link to the library.
// Along with the makefile there are three associated files,
// Defines.mk, Targets.mk, and Patterns.mk. Defines.mk defines
// the root path of the development directory and various
// compile macros. Targets.mk defines the makefile targets and
// Patterns.mk defines makefile compilation dependencies.
// The makefile expects a specific directory structure. Given the
// the root directory of the source, there should be the following
// subdirectories existing; libs, bin, obj. The libGQGMC.a is
// located in the libs directory. Object files are placed in the
// obj directory. A sample main.c is compiled into a executable,
// gqgmc, and placed in the bin directory. The main.c contains
// sample test code to excercise the various capabilities of
// the GQ GMC geiger counter.
//
//
// DESIGN OVERVIEW
//
// This code provides an interface to GQ Electronics LLC GMC-300
// geiger counter. Therefore the functionality implemented by this
// software consists of providing access to the capabilities
// provided by the geiger counter. These capabilities are
// elucidated in the interface document titled GQ-GMC-ICD.odt.
// Generally, the public methods defined for the gqgmc class
// are matched one for one with the capabilities of the
// GMC-300. Therefore, the user is referred to the interface
// document to ascertain what functionality can be expected of
// this software. The software does make an attempt to abstract
// or otherwise mask the details of the communications mechanism.
// Some capabilities of the GMC-300 are not implemented for the
// reason that they are not meaningful to accessing the radiation
// measurements through a remote computer, or in other words,
// the capability is only useful if the user carries the geiger
// counter in hand. For example, only two of the approximately
// 50 configuration parameters are provided with get and set methods.
//
//
// INCLUDE FILE DOCUMENTATION
//
// This include for C++ string handling
#include <string>

// This include allows use of Linux predefined types
#include <stdint.h>

#ifndef gqgmc_hh_
#define gqgmc_hh_

// Define a name space for the GQ LLC geiger counter product line
namespace GQLLC
{

  // CONFIGURATION PARAMETER ENUMERATION
  //
  // Declare a globally visible enumeration to be used for calling the
  // writeConfigurationData() method. The value of the enumeration is
  // equal to the offset of the parameter in the configuration data
  // structure. This is then used as the first parameter in the call to
  // writeConfigurationData(). See writeConfigurationData() method in
  // gqgmc.cc.
  enum cfg_param_t
  {
    ePowerOnOff                    = 0,
    eAlarmOnOff                    = 1,
    eSpeakerOnOff                  = 2,
    eGraphicModeOnOff              = 3,
    eBacklightTimeoutSeconds       = 4,
    eIdleTitleDisplayMode          = 5,
    eAlarmCPMValue                 = 6,
    eCalibrationCPM0               = 8,
    eCalibrationSvUc0              = 10,
    eCalibrationCPM1               = 14,
    eCalibrationSvUc1              = 16,
    eCalibrationCPM2               = 20,
    eCalibrationSvUc2              = 22,
    eIdleDisplayMode               = 26,
    eAlarmValueuSvUc               = 27,
    eAlarmType                     = 31,
    eSaveDataType                  = 32,
    eSwivelDisplay                 = 33,
    eZoom                          = 34,
    eDataSaveAddress               = 38,
    eDataReadAddress               = 41,
    eNPowerSavingMode              = 44,
    eNSensitivityMode              = 45,
    eNCounterDelay                 = 46,
    eNVoltageOffset                = 48,
    eMaxCPM                        = 49,
    eNSensitivityAutoModeThreshold = 51,
    eSaveDate                      = 52,
    eSaveTime                      = 55,
    eMaxBytes                      = 58
  }; // end enum cfg_param_t

  // CONFIGURATION PARAMETER DATA BYTE COUNT
  //
  // Declare an globally visible enumeration to be used for calling the
  // writeConfigurationData() method. The value of the enumeration is equal
  // to the number of bytes of data passed to the writeConfigurationData()
  // method. For each enumeration element, enum cfg_param_t, there is a
  // matching enum cfg_bytecnt_t entry.
  enum cfg_bytecnt_t
  {
    ePowerOnOff_bytecnt                    = 1,
    eAlarmOnOff_bytecnt                    = 1,
    eSpeakerOnOff_bytecnt                  = 1,
    eGraphicModeOnOff_bytecnt              = 1,
    eBacklightTimeoutSeconds_bytecnt       = 1,
    eIdleTitleDisplayMode_bytecnt          = 1,
    eAlarmCPMValue_bytecnt                 = 2,
    eCalibrationCPM0_bytecnt               = 2,
    eCalibrationSvUc0_bytecnt              = 4,
    eCalibrationCPM1_bytecnt               = 2,
    eCalibrationSvUc1_bytecnt              = 4,
    eCalibrationCPM2_bytecnt               = 2,
    eCalibrationSvUc2_bytecnt              = 4,
    eIdleDisplayMode_bytecnt               = 1,
    eAlarmValueuSvUc_bytecnt               = 4,
    eAlarmType_bytecnt                     = 1,
    eSaveDataType_bytecnt                  = 1,
    eSwivelDisplay_bytecnt                 = 1,
    eZoom_bytecnt                          = 4,
    eDataSaveAddress_bytecnt               = 3,
    eDataReadAddress_bytecnt               = 3,
    eNPowerSavingMode_bytecnt              = 1,
    eNSensitivityMode_bytecnt              = 1,
    eNCounterDelay_bytecnt                 = 2,
    eNVoltageOffset_bytecnt                = 1,
    eMaxCPM_bytecnt                        = 2,
    eNSensitivityAutoModeThreshold_bytecnt = 1,
    eSaveDate_bytecnt                      = 3,
    eSaveTime_bytecnt                      = 3,
    eMaxBytes_bytecnt                      = 1
  }; // end enum cfg_bytecnt_t

  // SAVE DATA TYPE ENUMERATON
  // Define a globally visible enumeration which defines the
  // type of data that will be logged in the history buffer.
  // This enumeration is to be used in the get and set method
  // for the eSaveDataType configuration parameter. See
  // getSaveDataType() and setSaveDataType() methods in gqgmc.cc.
  enum saveDataType_t
  {
      eSaveOff = 0, // data logging is off
      eCPS     = 1, // counts per second
      eCPM     = 2, // counts per minute
      eCPH     = 3, // CPM averaged per hour
      eMaxSaveDataType = 4
  };

  // SOFTWARE KEY ENUMERATION
  //
  // Define a globally visible enumeration for use in calling the
  // sendKey (send software key) command. The software key command
  // emulates the same menu keys available on the front panel of
  // the GMC-300. The user uses this enumeration as the parameter
  // to the sendKey() call. See sendKey() method in gqgmc.cc.
  enum softkey_t
  {
    // The GQGMC User Manual numbers the software keys 1 through 4,
    // but the actual value is ASCII 0 through 3.
    // We provide one enum named following the User Manual convention.
    eKey1 = '0', eKey2 = '1', eKey3 = '2', eKey4 = '3',
    // The user can use a second, more intuitive, enumeration:
    // key 1 is the left arrow
    // key 2 is the up arrow
    // key 3 is the down arrow
    // key 4 is the enter/menu key
    eLeftArrow = '0', eUpArrow = '1', eDownArrow = '2', eEnter = '3'
  };

  // GMC ERROR CODES
  //
  // Define an enumeration of all possible error conditions. This needs
  // to be globally visible so external routines can use the error code
  // to access error text with the idea that the display of the error
  // condition and its associated text message to the end user can
  // be separated from this class. The idea is that this is a low level
  // driver which should be not contain high level GUI functionality.
  enum gmc_error_t
  {
    eNoProblem, eUSB_open_failed, eOlder_firmware, eGet_version,
    eGet_serial_number, eGet_CPM, eGet_CPS, eGet_AutoCPS, eGet_CFG,
    eErase_CFG, eUpdate_CFG, eWrite_CFG, eClear_USB,
    eGet_battery_voltage, eGet_history_data,
    eGet_history_data_length, eGet_history_data_address,
    eGet_history_data_overrun, eSet_Year, eSet_Month, eSet_Day,
    eSet_Hour, eSet_Minute, eSet_Second,
    eLast_error_code
  };

  // PUBLIC CONSTANTS
  //
  // Publicly available constants which specify the maximum allowed
  // requested length of history data and the maximum address in history
  // buffer, respectively. External users can use this to guard against
  // erroneous requests for history data. Actually, getHistoryData()
  // method has guards also.
  uint32_t const kHistory_Data_Maxsize = 0x1000;  //  4k bytes
  uint32_t const kHistory_Addr_Maxsize = 0x10000; // 64k bytes

  // CLASS DECLARATION
  //
  // The Class declaration - see gqgmc.cc for documentation
  class GQGMC
  {
    // PUBLIC METHODS
    public:

    // Constructor
    GQGMC();

    // Destructor is trivial, so coded inline
    virtual
    ~GQGMC()
    {
      delete[] mHistory_data;
    };

    // SUPPORTING PUBLIC METHODS

    // Method to call to open USB port.
    virtual
    void
    openUSB(string usb_device_name);

    // Method to close USB port
    virtual
    void
    closeUSB(void);

    // Method to clear the read (input) buffer of the USB-serial port.
    // This arguably should be a private method, but I can see the
    // outside possibility that a third party might need this.
    virtual
    void
    clearUSB();

    // Method to call to check any and all error conditions exihibited
    // by the GQGMC class, implementation is trivial so coded inline.
    virtual
    enum gmc_error_t
    getErrorCode()
    {
      return mError_code;
    };

    // Method to get a text description of the error code.
    virtual
    std::string
    getErrorText(gmc_error_t err);

    // PUBLIC METHODS PROVIDING ACCESS TO GQ GMC CAPABILITIES
    // Begin the public methods for issuing commands to the GQ GMC
    // and returning data (if any).

    // Method to get GQ GMC version.
    virtual
    std::string
    getVersion();

    // Method to get serial number
    virtual
    std::string
    getSerialNumber();

    // Method to get the count per minute value.
    virtual
    uint16_t
    getCPM();


    // Method to get the count per second value.
    virtual
    uint16_t
    getCPS();

    // Method to get voltage value of battery.
    virtual
    float
    getBatteryVoltage();

    // Method to get history data from internal flash. Returned
    // pointer points to private data history data buffer.
    virtual
    uint8_t * const
    getHistoryData(uint32_t address, uint32_t length);

    // Method to enable automatic reporting of count per second value.
    virtual
    void
    turnOnCPS();

    // Method to disable automatic reporting of count per second value.
    virtual
    void
    turnOffCPS();

    // Method to read the automatically transmitted CPS value.
    virtual
    uint16_t
    getAutoCPS();

    // Method to turn off the GQ GMC.
    virtual
    void
    turnOffPower();

    // Method to read configuration data.
    virtual
    void
    getConfigurationData();

    // Method to retrieve the data type logged in the history buffer.
    virtual
    enum saveDataType_t
    getSaveDataType();

    // Method to set the data type logged in the history buffer.
    virtual
    void
    setSaveDataType(enum saveDataType_t newSaveDataType);

    // Method to retrieve the address in the history buffer
    // where the logged data begins.
    virtual
    uint32_t
    getDataSaveAddress();

    // Method to set DataSaveAddress to zero, in other words,
    // set the address of data logging to begin in the
    // history buffer at address zero.
    virtual
    void
    resetDataSaveAddress();

    // Method to write configuration data.
    virtual
    void
    writeConfigurationData(enum cfg_param_t        cfgParameter,
                           enum cfg_bytecnt_t      cfgDataCount,
                           uint8_t const * const   cfgData);

    // Method to erase configuration data.
    virtual
    void
    eraseConfigurationData();

    // Method to make latest changes to configuration data take effect.
    // Invoke this method after setting configuration data using
    // writeConfiguration(), setDataSaveAddress(), or setSaveDataType().
    virtual
    void
    updateConfigurationData();

    // Public method to send key (emulate one of the 4 keys on the
    // front panel of the GQ GMC).
    virtual
    void
    sendKey(enum softkey_t key);

    // Public method to set the date month-day-year format. For example,
    // for January 1, 2013 use "013113".
    virtual
    void
    setDate(string date);

    // Public method to set the time of day: hour:minutes:seconds format.
    // For example, for 30 minutes past 1PM use "133000".
    virtual
    void
    setTime(string time);

    // There are no public data members


    private:

    // PRIVATE DATA

    // The device name of the USB port connected to the GQ GMC.
    std::string             mUSB_device;

    // Forced to use C style IO because C++ streams does not support
    // setting line discipline sufficiently on the serial port.
    int                     mUSB_serial;

    // Error flag for indicating failure, see enumeration of error codes
    // declared above enum gmc_error_t.
    enum gmc_error_t        mError_code;

    // The failure of each command results from failing to read the
    // returned data. So we need a separate status for the read failure
    // so it can be tested privately.
    bool                    mRead_status;

    // Special flag indicating that automatic reporting of counts per
    // second is turned off or on (off==false, on==true). This should
    // and is intended to be implemented as a mutex if and when threading
    // is implemented.
    bool                    mCPS_is_on;

    // The USB port uses big endian transfer (ie, MSB transmitted 1st).
    // This flag indicates the endianess of the host CPU. This is set
    // in the constructor by calling isBigEndian() method.
    bool                    mBig_endian;

    // This real number is the GQ GMC's firmware revision. This is needed
    // because the GQ GMC changed the command string for various commands
    // between versions of the firmware. This means that not all commands
    // will work with firmware prior to 2.15.
    float                   mFirmware_revision;

    // Storage for the history data, maximum of 4K bytes to be allocated
    // in constructor. The user can only request a max of 4K at a time.
    uint8_t *               mHistory_data;

    // Declare a structure for storage of the configuration data.
    // This is a replica of the GQ GMC's internal configuration data.
    // This is referred to as the host computer's local copy of the
    // GQ GMC's NVM configuration data elsewhere in the documentation.
    // The getConfigurationData method will deposit the GQ GMC's NVM data
    // into this structure. All configuration data is the binary value.
    // Booleans are just 0 or 1. But remember that the byte order is
    // big endian for multibyte data. So multibyte data may require
    // reversing the byte order for display to a user. Those data
    // whose semantics are understood are documented below, otherwise
    // no explanation means either we have no interest or the parameter
    // is better left to being updated physically using the GQ GMC's
    // front panel keys.
    struct cfg_data_t
    {
      uint8_t powerOnOff;              // byte 0
      uint8_t alarmOnOff;
      uint8_t speakerOnOff;
      uint8_t graphicModeOnOff;
      uint8_t backlightTimeoutSeconds; // byte 4
      uint8_t idleTitleDisplayMode;
      uint8_t alarmCPMValueHiByte;
      uint8_t alarmCPMValueLoByte;
      uint8_t calibrationCPMHiByte_0; // byte 8
      uint8_t calibrationCPMLoByte_0;
      uint8_t calibrationSvUcByte3_0;
      uint8_t calibrationSvUcByte2_0;
      uint8_t calibrationSvUcByte1_0; // byte 12
      uint8_t calibrationSvUcByte0_0;
      uint8_t calibrationCPMHiByte_1;
      uint8_t calibrationCPMLoByte_1;
      uint8_t calibrationSvUcByte3_1; // byte 16
      uint8_t calibrationSvUcByte2_1;
      uint8_t calibrationSvUcByte1_1;
      uint8_t calibrationSvUcByte0_1;
      uint8_t calibrationCPMHiByte_2; // byte 20
      uint8_t calibrationCPMLoByte_2;
      uint8_t calibrationSvUcByte3_2;
      uint8_t calibrationSvUcByte2_2;
      uint8_t calibrationSvUcByte1_2; // byte 24
      uint8_t calibrationSvUcByte0_2;
      uint8_t idleDisplayMode;
      uint8_t alarmValueuSvUcByte3;
      uint8_t alarmValueuSvUcByte2;   // byte 28
      uint8_t alarmValueuSvUcByte1;
      uint8_t alarmValueuSvUcByte0;
      uint8_t alarmType;
      // saveDataType specifies both the interval of data logging where
      // 0 = data logging is off, 1 = once per second, 2 = once per minute,
      // 3 = once per hour and the type of data saved where 0 = don't care,
      // 1 = counts/second, 2 = counts/minute, 3 = CPM averaged over hour.
      // Whenever this is changed, the GQ GMC inserts a date/timestamp
      // into the history data buffer.
      uint8_t saveDataType;           // byte 32
      uint8_t swivelDisplay;
      uint8_t zoomByte3;
      uint8_t zoomByte2;
      uint8_t zoomByte1;              // byte 36
      uint8_t zoomByte0;
      // dataSaveAddress represents the address of the first sample following
      // the insertion of a date/timestamp or label tag into the data buffer.
      // Periodically, a label or date/timestamp will be put into the buffer
      // without a change made to dataSaveAddress. So you always have to be
      // on the lookout for 55AA sequence when parsing data buffer. But you
      // have to do that anyway because you might encounter double byte data.
      uint8_t dataSaveAddress2;
      uint8_t dataSaveAddress1;
      uint8_t dataSaveAddress0;       // byte 40
      // dataReadAddress semantics is unknown. As far as I have seen,
      // its value is always zero.
      uint8_t dataReadAddress2;
      uint8_t dataReadAddress1;
      uint8_t dataReadAddress0;
      uint8_t nPowerSavingMode;       // byte 44
      uint8_t nSensitivityMode;
      uint8_t nCounterDelayHiByte;
      uint8_t nCounterDelayLoByte;
      uint8_t nVoltageOffset;         // byte 48
      uint8_t maxCPMHiByte;
      uint8_t maxCPMLoByte;
      uint8_t nSensitivityAutoModeThreshold;
      // saveDateTimeStamp is the date/timestamp of the logging run,
      // all data following up to the next date/timestamp are marked
      // in time by this date/timestamp, where
      uint8_t saveDateTimeStampByte5; // = year (last two digits) // byte 52
      uint8_t saveDateTimeStampByte4; // = month
      uint8_t saveDateTimeStampByte3; // = day
      uint8_t saveDateTimeStampByte2; // = hour
      uint8_t saveDateTimeStampByte1; // = minute // byte 56
      uint8_t saveDateTimeStampByte0; // = second
      // maxBytes is always 0xff
      uint8_t maxBytes;
      uint8_t spare[197]; // add spare to total 256 bytes
    } mCFG_Data;

    // PRIVATE METHODS


    // Method to load configuration data which writes all 256 bytes
    // of the configuration data to the GQ GMC in sequence. This is
    // called by updateConfigurationData().
    virtual
    void
    loadConfigurationData();

    // Because of the returned byte stream from the GQ GMC has no protocol,
    // that is, it is just a sequence of N bytes, we need a specialized
    // read. The write is straight forward, but is combined with the read
    // for reasons of code commonality. So we will call the write followed
    // by read as the 'communicate' method.
    void
    communicate(const std::string cmd, char * retdata, uint32_t retbytes);

    // This is the basic method to transmit the command to the GQ GMC.
    // communicate() calls this to transmit command.
    void
    sendCmd(const std::string cmd);

    // This is the basic method to read the return bytes from the command.
    // communicate() calls this to read the return byte stream of the
    // command.
    void
    readCmdReturn(char * retdata, uint32_t retbytes);

    // This is the method to determine endianess of host CPU. This is to be
    // called by constructor once and once only.
    bool
    isBigEndian(void);

  }; // end class GQGMC

} // end namespace GQLLC

// DOCUMENTATION CONTINUES IN gqgmc.cc
#endif  // gqgmc_hh_
