// ************************************************************************** // File: gqgmc.cc // // Author: Phil Gillaspy // // Last Modified: 04/26/2012 // // Synopsis: // Define the class and its methods describing the capabilities // of the GQ Electronics LLC Geiger-Muller Counter (GQ GMC). // this code applies to model 300 and later geiger counters. // // CONTINUATION OF DOCUMENTATION FROM gqgmc.hh // // // C++ includes #include #include #include #include #include #include using namespace std; // These are the C stdio includes needed for configuring the serial port. #include #include #include #include // These are GQ GMC project specific includes #include "gqgmc.hh" using namespace GQLLC; // LOCAL CONSTANTS // // GQ GMC COMMANDS /* Here we declare local constants which are the ASCII strings for the various commands to the GQ GMC. These are not declared as data members of the GQGMC class because there is just no need to do so. The user never need know the actual string commands because there is a dedicated public method for each command. All commands start with '<'. That is followed by ASCII text, followed by ">>". Commands which take binary data as parameters must be dynamically constructed within the command method. */ static const string get_serial_cmd = ">"; static const string get_version_cmd = ">"; static const string get_voltage_cmd = ">"; static const string get_cpm_cmd = ">"; static const string get_cps_cmd = ">"; static const string get_cfg_cmd = ">"; static const string erase_cfg_cmd = ">"; static const string update_cfg_cmd = ">"; static const string turn_on_cps_cmd = ">"; static const string turn_off_cps_cmd = ">"; static const string turn_off_pwr_cmd = ">"; // The get_history data command has to be formed dynamically since // the additional data are parameters of the command. // This is also true of the write configuration data command. // The send software key command also needs a extra data parameter // and so it needs to be formed dynamically. // The set time and set date commands also use a hexadecimal parameter // and so need to be formed dynamically. // FIRMWARE CONSTANT // Older firmware is any revision prior to 2.23. Not all commands will // work on older firmware because some commands were not supported or // the command was changed. static float const kNew_Firmware = 2.23f; // Size of the NVM configuration data on the GQ GMC static uint32_t const kNVMSize = 256; // C STYLE SERIAL PORT CONFIGURATION // The termios structure is needed to configure the USB/serial port // for baudrate and raw line discipline. static struct termios settings; // debug code // LOCAL UTILITIES // These Hex.... routines are nice utilities to convert a raw // binary byte to an ASCII digit, ie, 1 digit string as hex data. // The principle use is to print out the data for debugging. // These were plagarized and adapted from code on the internet. struct HexCharStruct { unsigned char c; HexCharStruct(unsigned char _c) : c(_c) { } }; static std::ostream& operator<<(std::ostream& o, const HexCharStruct& hs) { o.width(2); o.fill('0'); return (o << std::hex << (int)hs.c); } static HexCharStruct Hex(unsigned char _c) { return HexCharStruct(_c); } // GQGMC CLASS CONSTRUCTOR // // Constructor implementation needs to initialize the private // variables, allocate memory for data history, and determine // endianess of processor. GQGMC::GQGMC() { // CPS is false until proven true mCPS_is_on = false; // Allocate history_data on heap mHistory_data = new uint8_t[kHistory_Data_Maxsize]; // Determine endianess of host CPU mBig_endian = isBigEndian(); } // end GQGMC constructor // GQGMC CLASS DESTRUCTOR // Destructor implemented inline in class declaration (see gqgmc.hh) // SUPPORTING PUBLIC METHODS // // Method to call to open USB port. We are using classic C style IO // because the C++ IO streams did not have enough flexibility to // control serial port. I could not set the baudrate using iostream. // Apparently, C++ IO is fixated on files and not hardware devices. // Input usb_device_name is the name of the USB to serial device // driver name. void GQGMC::openUSB(string usb_device_name) { // Assign name of USB device in private data mUSB_device = usb_device_name; // for example, /dev/usb/ttyUSB0 // Open the USB port using a USB to serial converter device driver. // We are using the C stdio open only because it allows the low // level control necessary to set line discpline and baudrate. // The line discpline is raw, that is, no character translation, // no handshaking, no nothing. The return data from the GQ GMC // are always raw binary (well, some of the raw binary might be // ASCII). Output to the GQ GMC is always ASCII with // some commands taking on binary data parameters. cout << mUSB_device.c_str() << endl; // Open usb serial port for reading and writing. mUSB_serial = open(mUSB_device.c_str(), O_RDWR); // If port opened successfully, then proceed to set line discipline. if (mUSB_serial != -1) { mError_code = eNoProblem; // Since USB serial is opened successfully, set baud rate to 57600 // and other settings to force raw binary data exchange. fcntl(mUSB_serial, F_SETFL, 0); memset(&settings,0,sizeof(settings)); settings.c_cflag = CS8 | CREAD | CLOCAL; settings.c_iflag = 0; // setting line discpline to raw binary settings.c_oflag = 0; settings.c_lflag = 0; settings.c_cc[VMIN] = 0; // This combo of VMIN & VTIME means to wait settings.c_cc[VTIME] = 5; // a maximum of 0.5 seconds for each // requested or expected byte of data. cfsetispeed(&settings, B57600); cfsetospeed(&settings, B57600); tcsetattr(mUSB_serial, TCSANOW, &settings); // Now that the port is successfuly opened, we secretly (unknown to // the user) interrogate the GMC-300 to determine the firmware revision. // For older firmware, a warning is issued to the user. Older firmware // will not support all commands. string vers = getVersion(); if (mRead_status == true) { // Parse version string to extract firmware revision. The revision // is a f4.1 formatted string in the last four characters of string. stringstream firmware_rev; firmware_rev << vers[10] << vers[11] << vers[12] << vers[13]; // Convert revision string to float in order to compare firmware_rev >> mFirmware_revision; // There may be a change to commands caused by change to // firmware. We only need to compare if revision is old or new. if (mFirmware_revision < kNew_Firmware) mError_code = eOlder_firmware; // Get a fresh copy of the GQ GMC's NVM configuration data. getConfigurationData(); } // end if (error_code == eNoProblem) // else error_code will have been set by getVersion() } else // usb not opened successfully { mError_code = eUSB_open_failed; } // end if (usb_serial != -1) // It is the responsibility of the caller to test the error_code. return; } // end openUSB() // Method to close USB port is pretty trivial. Implementation is // to just close port. void GQGMC::closeUSB() { close(mUSB_serial); return; } // end closeUSB() // clearUSB is public method to clear the read (input) buffer of // the serial port. This method exists due to vagaries of the // turn_on_cps_cmd. Since there is no protocol for the returned // data (ie, no start, no stop, no ack, no nak), the synchronization // of the getAutoCPS() method with the GQ GMC is flakey. Consequently, // when the turn_off_cps_cmd happens, there may be left over // data in the USB input buffer. So this method simply reads the // USB input buffer until it is empty. void GQGMC::clearUSB() { uint32_t rcvd(0); char inp; // Assume that there isn't that much left over from any previous // read. In other words, we couldn't ever get that far off. // So read only 10 bytes. const uint16_t kMaxtries(10); // Read returned data until input buffer is empty as indicated by // rcvd = 0 or not. If rcvd = 0 then the buffer is empty and that is // what we want. for(uint16_t i=0; i 0) mError_code = eClear_USB; return; }// end clearUSB() // Public method getErrorCode() to check error code of GQGMC class. // Implemented inline in class declaration (see gqgmc.hh) // Public method to get a text description of the error code. Intended // to be used by external code to display this text to user. External // code would know the error code from the most recent call to a method. // If the error code is non-zero, then call this routine to obtain a // textual description of the error. string GQGMC::getErrorText(gmc_error_t err) { stringstream err_msg; // error message returned in this string switch(err) { case eNoProblem: // The user should not call with this error code err_msg << ""; break; // because they are expected to test the code first. case eUSB_open_failed: err_msg << "The USB port did not open successfully." << endl; break; case eOlder_firmware: err_msg << "Your GQ GMC has older firmware." << endl << "Some commands may not work." << endl; break; case eGet_version: err_msg << "The command to read the version number of the firmware " << "failed." << endl; break; case eGet_serial_number: err_msg << "The command to read the serial number failed." << endl; break; case eGet_CPM: err_msg << "The command to read the counts per minute failed." << endl; break; case eGet_CPS: err_msg << "The command to read the counts per second failed." << endl; break; case eGet_AutoCPS: err_msg << "The command to read auto counts per second failed." << endl; break; case eGet_CFG: err_msg << "The command to get configuration data failed." << endl; break; case eErase_CFG: err_msg << "The command to erase configuration data failed." << endl; break; case eUpdate_CFG: err_msg << "The command to update configuration data failed." << endl; break; case eClear_USB: err_msg << "Failed to clear USB input buffer. You should power " << "cycle GQ GMC." << endl; break; case eGet_battery_voltage: err_msg << "The command to read the battery voltage failed." << endl; break; case eGet_history_data: err_msg << "The command to read the history data failed." << endl; break; case eGet_history_data_length: err_msg << "The requested data length of the history command cannot " << "exceed " << dec << kHistory_Data_Maxsize << " bytes." << endl; break; case eGet_history_data_address: err_msg << "The address of the history command cannot exceed " << dec << kHistory_Addr_Maxsize << " bytes." << endl; break; case eGet_history_data_overrun: err_msg << "The history data length added to the address cannot exceed " << dec << kHistory_Addr_Maxsize << " bytes." << endl; break; case eSet_Year: err_msg << "The set year command failed." << endl; break; case eSet_Month: err_msg << "The set month command failed." << endl; break; case eSet_Day: err_msg << "The set day command failed." << endl; break; case eSet_Hour: err_msg << "The set hour command failed." << endl; break; case eSet_Minute: err_msg << "The set minute command failed." << endl; break; case eSet_Second: err_msg << "The set second command failed." << endl; break; default: // this should never happen since user should have break; // obtained a valid code using getErrorCode(). } // end switch(err) return err_msg.str(); } // end get_error_info() // PUBLIC METHODS PROVIDING ACCESS TO GQ GMC CAPABILITIES // Public method to get GQ geiger counter model name and firmware revision. // Example of returned data is "GMC-300Re2.11". Note that this is the only // command that has all ASCII as the return data. The command string is // get_version_cmd (see GQ GMC COMMANDS above). string GQGMC::getVersion() { // The GMC returns 14 characters as the version in an alphanumeric string. const uint32_t versize = 14; char version[versize+1]; // add one for null, '\0' // Issue the command to get the version and read the returned data communicate(get_version_cmd, version, versize); // If reading data failed, fake returned data and setup error code, if (mRead_status == false) { stringstream ss; ss << "invalidinvalid"; // how convenient, exactly 14 characters ss >> version; mError_code = eGet_version; } // else the version is returned as an ASCII string in version. return string(version); } // end getVersion() // Public method to get serial number. The serial number is returned as 7 // binary bytes. For example, if the GQ GMC returns 00 30 00 E3 4A 35 1A, // total 7 bytes in raw binary, then the serial number is "003000E34A351A". // Notice how each nibble is effectively a single ASCII hexadecimal digit. // The command string is get_serial_cmd (see GQ GMC COMMANDS above). string GQGMC::getSerialNumber() { const uint32_t sernumsize = 7; // 7 bytes of returned data char serial_number[sernumsize+1];// returned data uint8_t nibble[2*sernumsize]; // each nibble of returned data stringstream ss; // Issue the command to get serial number and read returned data. communicate(get_serial_cmd, serial_number, sernumsize); // If the read of the returned data succeeded, convert raw data to string. if (mRead_status == true) { // Convert each nibble to an 8-bit int because each nibble // represents a separate serial number digit. for(uint32_t i=0; i> 4; nibble[(i*2)+1] = (serial_number[i] & 0x0F); // Convert byte array to string of hex ASCII char. Do not use // Hex utility because that forces each arg to have 2 char field. ss << hex << uint16_t(nibble[i*2]) << uint16_t(nibble[(i*2)+1]); } } else // for read failure, set byte array to null and set error code { for(uint32_t i=0; i 255 // and the number of data samples in the history buffer will greatly // diminished (decreased by factor of 2). // // The ASCII tag string is 55AA02LLCCCCCCCC.... where // 55AA is start of sequence marker, // 02 is the enum code indicating that the tag ASCII string follows, // LL is the number of ASCII characters in the tag, // CC is an ASCII character, there being LL number of ASCII characters. // There is no 55AA end of sequence marker. // // The history data itself is either CPS or CPM. If CPS, the data byte // is typically a sequence of 00, 01's, 02's, and occassionally a 03. // If CPM, the data is typically anywhere from 0x0a (10 decimal) // up to 0x1e (30 decimal). But remember that any time the count per // time exceeds 255, then the special 55AADHDL sequence kicks in. // // The history data continues until either the date/timestamp or label // tag special sequences are inserted into the history buffer. // // If the user requests an area of the history buffer which has no data as // yet recorded, then the start of the history buffer is used, in other // words, it is as if the user requested address zero. // // If the user requests a small area of the history buffer, then chances // are high that the data retrieved will have no date/timestamp embedded. // So it is impossible to know when the data was recorded. The 64K history // buffer is divided into 4K blocks and it is guaranteed that there will // be a date/timestamp somewhere within each 4K block. For these reasons, // the user should request no less than 4K bytes on 4K byte boundary. // Note that 4K bytes is also the maximum request allowed. So for all // practical purposes, all get history commands should be 4K bytes. // // The following is typical history buffer log of counts per second // (leading 0 is suppressed). Note that the data/timestamp is // embedded about half way through (2012, April 1, 17:10). // 1 0 1 1 0 0 0 1 0 1 0 0 0 0 2 0 0 0 0 0 0 2 0 0 0 2 0 0 0 0 0 0 // 0 1 0 0 0 0 0 0 1 0 0 2 0 0 1 0 1 0 2 0 0 0 1 2 1 0 0 0 1 0 1 1 // 1 1 0 0 0 0 0 0 1 1 0 1 0 0 0 1 0 4 1 0 0 0 0 1 0 0 2 2 0 2 0 0 // 0 2 1 1 0 0 1 1 0 0 1 1 2 0 0 3 2 0 1 0 0 0 2 0 0 2 0 0 0 0 0 0 // 0 0 1 2 0 0 0 55 aa 0 c 4 1 11 1f a 55 aa 1 1 0 0 0 0 0 0 1 0 0 0 1 1 // 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 1 1 1 1 1 0 0 0 1 0 1 0 0 0 0 0 1 // 1 0 0 0 0 0 0 0 1 0 0 1 0 2 1 0 1 2 1 1 0 1 1 0 0 0 0 0 0 0 0 0 // 0 0 1 0 0 0 0 0 0 2 0 0 0 0 0 1 1 3 0 0 2 0 0 0 0 0 0 1 1 0 1 0 // // The following is a typical history buffer log of counts per // minute (leading 0 is suppressed). Note that there are two // date/timestamps. The first indicates the current logging is // set to counts per second, but the second changes that to // counts per minute. Also notice, there is a single two byte // sample (55AA021b). The ff data indicates an area of the // history buffer that has no data. // 0 0 0 0 0 0 1 0 0 0 1 1 0 0 0 0 // 1 3 0 0 0 0 0 1 1 0 0 2 0 0 0 0 // 0 0 55 aa 0 c 4 2 11 e 34 55 aa 1 55 aa // 0 c 4 2 11 e 35 55 aa 2 1b 16 12 18 18 16 // 14 13 18 13 18 24 a6 ff ff ff ff ff ff ff ff ff // ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff // // Given that the desired amount of history buffer has been // retrieved, parsing of the data can proceed by working from the // beginning to end or vica versa. Either way, parsing would be // looking for the embedded date/timestamp in order to fix the // point in time when the data logging begins. The first data // sample after the date/timestamp is therefore the beginning of // a new recording. Proceeding forward, each successive byte is // the next count per second (CPS) or count per minute (CPM) data // sample. However, caution must be taken to check for the // 55AA sequence which could be either a new date/timestamp or a // new label, or a two byte data sample. In the case of a // date/timestamp, the parsing must check for a change in the // sampling mode, in other words, does the logging change from CPS // to CPM or vica versa. // // The address of the first data sample of the newest log run // is maintained in the configuration data as the parameters, // dataSaveAddress0, dataSaveAddress1, dataSaveAddress3. This address // represents the offset from the beginning of the history buffer. // The first data sample will always be preceeded by date/timestamp. // So this date/timestamp should be read in order to orient // the data log. Any time the sampling mode is changed, the // dataSaveAddress0/1/2 is updated, otherwise it remains the same // until the logging wraps around to the same address. // Incidentally, the configuration data also maintains the parameter, // saveDataType, which contains the sampling mode. // // If you intend to use the GQ GMC to log radiation data for // any time longer than a few hours, it is not recommended to // use the built-in logging capability of the GQ GMC. Instead, // do the logging yourself by simply calling getCPM() method, // letting the host computer do the storage and time/date tracking. uint8_t * const GQGMC::getHistoryData(uint32_t address, uint32_t length) { // Initialize history data to zeroes. The max allowed request is // kHistory_Data_Maxsize = 4K bytes. Even if the user requests less // than kHistory_Data_Maxsize, we zero out the whole array. for(uint32_t i=0;i kHistory_Data_Maxsize) mError_code = eGet_history_data_length; // check address if (address > kHistory_Addr_Maxsize) mError_code = eGet_history_data_address; // check address plus length if ((address + length) > kHistory_Addr_Maxsize) mError_code = eGet_history_data_overrun; // Trap error here, return immediately if there is an error if (mError_code != eNoProblem) return &mHistory_data[address]; // Since the request is within boundaries, formulate history command. string get_history_data_cmd = "> 16) & 0x000000ff); // msb get_history_data_cmd += uint8_t((address >> 8) & 0x000000ff); get_history_data_cmd += uint8_t((address >> 0) & 0x000000ff); // lsb // pack length get_history_data_cmd += uint8_t((length >> 8) & 0x00ff); // lsb get_history_data_cmd += uint8_t((length >> 0) & 0x00ff); // msb // And of course, command finishes with ">>" get_history_data_cmd += ">>"; // Issue command to get history data and read returned data communicate(get_history_data_cmd, (char *)&mHistory_data[0], length); // If read of returned data failed, set error code. if (mRead_status == false) { mError_code = eGet_history_data; } // Note that the returned data is a byte array that has to be parsed // further in order to extract the actual history data. return &mHistory_data[0]; } // end getHistoryData() // turnOnCPS is public method to enable automatic reporting of the // count per second (CPS) value. First, I would say don't use this // command. Since the returned data has no protocol, that is, there // is no start/stop marker, no identification, no nothing but a // sequence of bytes, any other command issued while CPS is turned on // will take extraordinary effort not to confuse its returned data // with the CPS data. To handle it correctly, you would have to // wait for the CPS data to be returned, and then issue the command. // This would be most safely done by creating a new thread to // read the CPS and using a mutex to signal opportunity to issue a // separate command. The command is turn_on_cps_cmd // (see GQ GMC COMMANDS above). The returned data is always two // bytes so that samples > 255 can be reported (even though unlikely). void GQGMC::turnOnCPS() { sendCmd(turn_on_cps_cmd); // There is no pass/fail return from GQ GMC // Set flag that auto-transmission of CPS is turned on. This is to be // changed to a mutex when threading is implemented. mCPS_is_on = true; return; } // end turnOnCPS() // turnOffCPS is the public method to disable automatic reporting // of count per second value. The command is turn_off_cps_cmd // (see GQ GMC COMMANDS above). Another complication of turning // on the auto-CPS, is that turning it off is asynchronous with // it being reported. So there may a left over data sample which // has to be cleaned up. void GQGMC::turnOffCPS() { sendCmd(turn_off_cps_cmd); // turnOffCPS command has no return data, so there is no way // to know if the command succeeds or fails. // Set flag of CPS auto-transmission to false mCPS_is_on = false; // Since turning off CPS is asynchronous with the GQ GMC's transmission // of the CPS, we will attempt to clear the input buffer of any left // over data. clearUSB(); return; } // end turnOffCPS() // getAutoCPS is public method to read CPS. This is the method to // be called after the user turns on the auto-CPS. If the user has // not called turnOnCPS(), then this method will issue error // messages. uint16_t GQGMC::getAutoCPS() { const uint32_t cpssize = 2; char cps_char[cpssize+1]; uint16_t cps_int = 0; // Issue command to get CPS and read returned data. Note that // we are calling readCmdReturn directly, therefore bypassing // the communicate method. The user should have already issued // turn_on_cps_cmd. readCmdReturn(cps_char, cpssize); // If read of returned data succeeded, convert raw data to integer, if (mRead_status == true) { // 1st byte is MSB, but note that upper two bits are reserved // bits. Assume shift & bitmasking take place in register which // is independent of big endian vs little endian architecture. cps_int |= ((uint16_t(cps_char[0]) << 8) & 0x3f00); cps_int |= (uint16_t(cps_char[1]) & 0x00ff); } else // else failure will return cps = 0 and set error code { mError_code = eGet_AutoCPS; } return cps_int; } // end getAutoCPS() // turnOffPower public method turns off the GQ GMC-300. The command // is turn_off_pwr_cmd (see GQ GMC COMMANDS above). This will turn // off the GQ GMC, so there shouldn't be any other commands issued // to the GQ GMC following this command. void GQGMC::turnOffPower() { sendCmd(turn_off_pwr_cmd); // Note that power off cannot fail because the GQ GMC returns nothing. return; } // end turnOffPower() // CONFIGURATION DATA // // Setting the configuration data of the GQ GMC is not a // straight forward procedure. The following statement is the // principle reason why: the GQ GMC does not use or keep a RAM copy // of its non-volatile memory (NVM) (configuration data is in NVM). // That condition coupled with the fact that the EEPROM // used by the GQ GMC can only be reprogrammed // all 256 bytes at a shot means that if you write a byte of // configuration data and then read back the configuration data, // you will not see your data changed as expected. // // All this means that in order to change the configuration // parameters and have the GQ GMC change its operations accordingly, // 1) the host computer must keep its own copy of NVM, // 2) update its own copy of NVM, // 3) issue the erase configuration command, // 4) write all 256 bytes of configuration data at one shot, // 5) follow immediately with an update configuration command. // // When the GQ GMC receives the update configuration command, // then and only then does it re-configure its operation in // accordance with the NVM configuration data. Keeping the host // computer's copy of the NVM accurate and up to date can be // problematic since behind the back of the host computer, // the GQ GMC can be changed manually. // // The GQGMC software makes a valiant attempt to hide all this // from the user. First, immediately following the opening of // the USB port, the software silently reads the configuration // data from the GQ GMC to obtain its own copy of NVM. // From that point on, it is assumed that no manual changes // to the GQ GMC will occur. The GQGMC software then reads/writes // it own local copy of NVM. When the user issues the Update CFG // command, the GQGMC software silently // 1) issues the erase configuraton command, // 2) writes all 256 bytes of NVM, // 3) issues the update configuration command. // The user software may at any time cause a Get configuration // command in which case, the user must be aware that the GQGMC's // local copy of NVM will be overwritten. // getConfigurationData public method reads configuration data. You // don't request pieces of the configuration data, all 256 bytes // are returned, although, there are currently only about 60 // bytes used (corresponding to about 50 parameters). The command is // get_cfg_cmd (see GQ GMC COMMANDS above). void GQGMC::getConfigurationData() { // Issue command to get configuration and read returned data. communicate(get_cfg_cmd, reinterpret_cast(&mCFG_Data), sizeof(mCFG_Data)); // If read of returned data failed, set error code. if (mRead_status == false) { mError_code = eGet_CFG; } // debugging code /* uint8_t * inp = (uint8_t *)&mCFG_Data; for(uint32_t i=0; i<64; i++) { cout << Hex(inp[i]) << "-"; if (i > 0) if (((i+1)%16) == 0) cout << endl; if (i > 62)break; } cout << endl; */ // end debug code return; } // end getConfigurationData() // The following get methods are provided in order to access the // configuration data, not all configuration data, but only those // parameters which are needed to retrieve and parse the history data. // getSaveDataType is a get method to retrieve the saved data type // parameter in the configuration data. Note that SaveDataType is // retrieved from the host computer's local copy of NVM // configuration data. The return type is an enumeration // which matches the following possibilities: // 0 = logging is off, // 1 = counts per second, // 2 = counts per minute, // 3 = CPM averaged per hour. enum saveDataType_t GQGMC::getSaveDataType() { return ((enum saveDataType_t)(mCFG_Data.saveDataType)); } // end getSaveDataType() // setSaveDataType is a set method to reconfigure the data type // logged in the history buffer. This method is provided as a // convenience instead of using the writeConfigurationData method // since changing the saved data type is considered to be // more commonly used configuration change. The passed argument // is an enumeration (see definition in gqgmc.hh) whose value // is to be the new value of the saveDataType configuration // parameter. Note that only the host computer's local copy // of NVM is updated. The user must issue a update configuration // command to cause the GQGMC to implement the NVM changes. void GQGMC::setSaveDataType(enum saveDataType_t newSaveDataType) { uint8_t saveData = uint8_t(newSaveDataType); // Use writeConfigurationData method writeConfigurationData(eSaveDataType, eSaveDataType_bytecnt, &saveData); // error condition will be handled by writeConfigurationData() return; } // end setSaveDatatype method // getDataSaveAddress is get method to retrieve the address // in the history buffer where the logged data begins. See // getHistoryData method for an explanation of the // dataSaveAddress in the configuration data structure. // The returned value is a 32 bit address, although the // dataSaveAddress maximum value cannot exceed 24 bits worth. // Note that the DataSaveAddress is retrieved from the host // computer's local copy of the GQ GMC's NVM configuration data. uint32_t GQGMC::getDataSaveAddress() { uint32_t address(0); address |= (uint32_t(mCFG_Data.dataSaveAddress2) << 16); address |= (uint32_t(mCFG_Data.dataSaveAddress1) << 8); address |= (uint32_t(mCFG_Data.dataSaveAddress0) << 0); return address; } // end getDataSaveAddress() // The resetDataSaveAddress sets the dataSaveAddress configuration // parameter to the value of 0x10. This is provided as a // convenience instead of using writeConfigurationData() directly // because setting the start of the history buffer back to zero // would be a common thing to do. Note that the DataSaveAddress // is being updated in the host computer's local copy of the // GQ GMC's NVM configuration data. The user must issue the // update configuration command to force the GQ GMC to implement // the NVM configuration data changes. void GQGMC::resetDataSaveAddress() { uint32_t address(0x10); // 0x10 provides room for date/timestamp // Use writeConfigurationData() method writeConfigurationData(eDataSaveAddress, eDataSaveAddress_bytecnt, (uint8_t *)(&address)); // error condition handled by writeConfigurationData() return; } // end resetDataSaveAddress() // writeConfiguratonData is the public method to write configuration // data. It takes the following parameters. // // cfgParameter is actually the offset from the beginning of the // configuration data of the desired parameter. It was explicitly // created this way and assigned the offset as its enumeration value so // that cfgParameter serves both as an enumeration and as the actual // address of the configuration parameter. // // cfgData is a pointer to an array of raw binary for the parameter. // // cfgDataCount is the number of bytes of cfgData. Since cfgData is // passed as pointer, we need to supply the array length separately // as the cfgDataCount parameter. // // Note that this method updates the local copy of the GQ GMC's // NVM configuration data. As noted previously in the documentation, // the GQ GMC does not support a direct method of updating its // NVM configuration data and having it take effect immediately. // // This method is not an exact reflection of the native GQ GMC write // configuration command. The native GQ GMC write configuration // only writes one byte at a time. So writing multibyte configuration // parameters would take multiple writes, one per data byte. Instead, // we abstract the write configuration command so the user does not // have to know this much detail. We can do this because we // know a priori the base address of each parameter and how many // bytes each parameter needs. So this method is intended to // handle parameters with multibyte data by requiring the user // to only pass the parameter enumeration, its byte count, and // value. // // Note that changes to the configuration data do not have // immediate effect since we writing to the local copy of the // GQ GMC's NVM configuration data. To take effect, the user must // call the updateConfigurationData() method. Before doing so, all // changes to the configuration data should be completed so that // interdependent configuration parameters are updated // simultaneously. void GQGMC::writeConfigurationData(enum cfg_param_t cfgParameter, enum cfg_bytecnt_t cfgDataCount, uint8_t const * const cfgData) { uint8_t * pCfg_Data = (uint8_t *)&mCFG_Data + uint8_t(cfgParameter); for(int i=0; i>" or ">" or ">" or ">". // So for the purpose that the user need not know the actual // command string, the softkey_t enumeration is created and used // as the passed argument to the method. See the enum softkey_t // declaration in gqgmc.hh for more discussion. // // For successive sendKey calls there is a trick to know when // using the sendKey method. The trick is you can't // transmit sendKey too fast and you can't do it too slow. // Another thing is that the Save Data option menu starts with // the current data type and then cycles through the other options. // So you have to know what is the current data type and // then send the Enter key the proper number of times to cycle // to the desired option. When moving through the menu we use // 0.5 seconds, but when moving through a pop up options menu // we have to move faster and so should use 0.25 seconds // between sendKey in that context. void GQGMC::sendKey(enum softkey_t key) { char inp[1]; // This will not be used, just needed as dummy arg // Begin formulating the send key command. string keycmd = ">" keycmd += ">>"; communicate(keycmd, inp, 0); // no return data // Since the sendkey command returns no data there is no way to // test success of communication. // Debug code /* for(int i=0; i<7; i++) cout << Hex(keycmd[i]); cout << endl; */ return; } // end sendKey() // setDate is the public method to set the date. The date is passed as // an ASCII string with the format of , for example, // 112312 is November (11th month) 12, 2012. The year is specified // as the last two digits of the century since presumably the date is // is being set to the current date. In reality, the GQ GMC has a separate // command for setting each of the month, day, and year. void GQGMC::setDate(string date) { const uint32_t retsize = 1; char ret_char[retsize+1]; // The date is broken up into three separate commands one each for // month, day, and year as supported by the GQ GMC. // Set the month, > where XX = month byte. { // uint16_t is necessary since ss >> uint8_t does not work. uint16_t month=0; string setMonthCmd; stringstream ss; ss << date[0] << date[1]; ss >> month; //cout << "month = " << Hex(uint8_t(month)) << endl; setMonthCmd = "> where XX = day byte. { uint16_t day=0; string setDayCmd; stringstream ss; ss << date[2] << date[3]; ss >> day; //cout << "day = " << Hex(uint8_t(day)) << endl; setDayCmd = "> where XX = year byte. { uint16_t year=0; string setYearCmd; stringstream ss; ss << date[4] << date[5]; ss >> year; //cout << "year = " << Hex(uint8_t(year)) << endl; setYearCmd = ", // for example, 142256 is the 14th hour, 22 minutes after the hour, // 56 seconds after the minute. The hour is given in 24 hour format // counting from 0 to 23. In reality, the GQ GMC provides a separate // command for setting each of the hour, minutes and seconds. void GQGMC::setTime(string time) { const uint32_t retsize = 1; char ret_char[retsize+1]; // The time is broken up into three separate commands one each for // hour, minute, and second as supported by the GQ GMC. // Set the hour, > where XX = hour byte. { uint16_t hour=0; // stringstream does not convert to uint8_t string setHourCmd; stringstream ss; ss << time[0] << time[1]; ss >> hour; //cout << "hours = " << Hex(uint8_t(hour)) << endl; setHourCmd = "> where XX = minute byte. { uint16_t minute=0; string setMinuteCmd; stringstream ss; ss << time[2] << time[3]; ss >> minute; //cout << "minute = " << Hex(uint8_t(minute)) << endl; setMinuteCmd = "> where XX = second byte. { uint16_t second=0; string setSecondCmd; stringstream ss; ss << time[4] << time[5]; ss >> second; //cout << "second = " << Hex(uint8_t(second)) << endl; setSecondCmd = " 0) sendCmd(cmd); // 2nd, read the return data, for all commands except get version // this is always raw binary data. // For flexibility, only read if return is not 'null'. if (retbytes > 0) readCmdReturn(retdata, retbytes); return; } // end communicate() // sendCmd is the private method (the basic method) to transmit // the command to the GMC-300. // cmd is the ASCII string to send as the command. void GQGMC::sendCmd(const string cmd) { // This is a common place to reset the error code since it is always // called for any command. mError_code = eNoProblem; // This is a common place to reset the read status since every read // is always preceeded by a write command (except for turn_on_cps!). mRead_status = true; // Call low level C stdio routine to write to USB port. write(mUSB_serial, cmd.c_str(), cmd.size()); return; } // end sendCmd() // readCmdReturn is the private method (the basic method) to read // the return bytes from the command. // retdata is the repository for the returned data. // retbytes is the number of bytes of the returned data. void GQGMC::readCmdReturn(char * retdata, uint32_t retbytes) { uint32_t rcvd = 0; // the number of received bytes char * inp = &retdata[0]; // pointer to returned data char array // start pointer off at beginning of // repository. // Assume read will succeed, replicated here only because of the // nature of the turn_on_cps command which automatically returns // data without a preceeding write. mRead_status = true; // Now read the returned raw byte string. Do this by reading one byte // at a time until the requested number of bytes are attained. However, // the serial port has been setup to timeout each read attempt. So if // after N calls to read, we haven't yet read all N bytes, declare // a failure. The read is done this way to avoid an indefinite blocking // situation when 0 bytes are returned by the GQ GMC. The only good thing // about this methodology is that the largest possible read is only 4K // for the history data. So the read never really takes that much time. for(uint32_t i=0; i= retbytes) break; } // end for loop // debugging code /* inp = &retdata[0]; for(uint32_t i=0; i 0) if (((i+1)%16) == 0) cout << endl; if (i > 62)break; } cout << endl; cout << "rcvd = " << rcvd << endl; */ // end debug code // Communication is considered a failure if less than the expected // number of bytes is returned by the GMC-300. if (rcvd < retbytes) mRead_status = false; return; } // readCmdReturn() // isBigEndian is the method to determine endianess of host CPU. // This is to be called by constructor once and once only. This is // needed because the GQ GMC wants data transmitted to it in // big endian and returns data in big endian, but the host computer // could be little endian. The routine was adapted from code // found on the internet. bool GQGMC::isBigEndian(void) { uint32_t x(0xff); bool bigend(reinterpret_cast(&x)[0] == 0); return bigend; } // end isBigEndian() // end file gqgmc.cc