diff --git a/devices/geiger/geiger.go b/devices/geiger/geiger.go index 7599d56ce942a54296a4a8fbb6aeaecad52b3450..74648310595da52354c39e2fd74d66558459469e 100644 --- a/devices/geiger/geiger.go +++ b/devices/geiger/geiger.go @@ -18,7 +18,22 @@ func New(c Config) (Counter, error) { // Counter is an interface for Geiger Counters type Counter interface { - GetReading() (*Reading, error) + Clear() error + Version() (string, error) + SerialNum() (string, error) + GetCPM() (uint16, error) + GetCPS() (uint16, error) + GetVoltage() (int16, error) + GetHistoryData() + TurnOnCPS() error + TurnOffCPS() error + GetAutoCPS() (uint16, error) + TurnOffPower() + GetConfiguration() + SetConfiguration() + ResetConfiguration() + SetDate(date string) + SetTime(time string) } // Config contain the configuration for a Geiger Counter diff --git a/devices/geiger/gqgmc.go b/devices/geiger/gqgmc.go index 18b6b537518a2d2507ba903df8720b0b2d98fa0d..77c04d3112d3c71c61e854b8803faa137036a3f2 100644 --- a/devices/geiger/gqgmc.go +++ b/devices/geiger/gqgmc.go @@ -8,11 +8,18 @@ package geiger import ( + "fmt" "time" "github.com/tarm/serial" ) +// The Ideal and Low voltage threshholds +const ( + VoltageIdeal = 98 + VoltageTooLow = 75 +) + const ( powerOnOff = 0 alarmOnOff = 1 @@ -87,745 +94,72 @@ const ( saveMax ) -const ( - key1 = iota - key2 - key3 - key4 -) - const ( historyDataMaxSize = 0x1000 historyAddrMaxSize = 0x10000 - forFirmware = 2.23 - kNVMSize = 256 + forFirmware = 2.23 + nvmSize = 256 ) const ( -cmdGetSerial = "<GETSERIAL>>" -cmdGetVersion = "<GETVER>>" -cmdGetVoltage = "<GETVOLT>>" -cmdGetCpm = "<GETCPM>>" -cmdGetCps = "<GETCPS>>" -cmdGetCfg = "<GETCFG>>" -cmdEraseCfg = "<ECFG>>" -cmdUpdateCfg = "<CFGUPDATE>>" -cmdTurnOnCps = "<HEARTBEAT1>>" -cmdTurnOffCps = "<HEARTBEAT0>>" -cmdTurnOffPwr = "<POWEROFF>>" + cmdGetSerial = "<GETSERIAL>>" + cmdGetVersion = "<GETVER>>" + cmdGetVoltage = "<GETVOLT>>" + cmdGetCpm = "<GETCPM>>" + cmdGetCps = "<GETCPS>>" + cmdGetCfg = "<GETCFG>>" + cmdEraseCfg = "<ECFG>>" + cmdUpdateCfg = "<CFGUPDATE>>" + cmdTurnOnCps = "<HEARTBEAT1>>" + cmdTurnOffCps = "<HEARTBEAT0>>" + cmdTurnOffPwr = "<POWEROFF>>" ) const ( - errOpen = "Failed to open USB port" - errFirmware = "GQ GMC has older firmware. Some commands may not work." - errVersion = "Failed to read the version number of the firmware" - errSerialNumber = "Failed to read the serial number " - errCPM = "Failed to read the counts per minute " - errCPS = "Failed to read the counts per second " - errAutoCPS = "Failed to read auto counts per second " - errCFG = "Failed to get configuration data " - errEraseCFG = "Failed to erase configuration data " - errUpdateCFG = "Failed to update configuration data " - errClear = "Failed to clear USB input buffer. You should power cycle GQ GMC." - errBatteryVoltage = "Failed to read the battery voltage " - errHistoryData = "Failed to read the history data " - errHistoryDataLengthFmt = "Requested data length of the history command cannot exceed %d bytes" - errHistoryDataAddressFmt = "Address of the history command cannot exceed %d bytes" - errHistoryDataOverrunFmt = "History data length added to the address cannot exceed %d bytes" - errYear = "Failed to set year" - errMonth = "Failed to set month" - errDay = "Failed to set day" - errHour = "Failed to set hour" - errMinute = "Failed to set minute" - errSecond = "Failed to set second" - ) - -// 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 -func (gc *GQGMCCounter) getConfigurationData() -{ - // Issue command to get configuration and read returned data. - communicate(get_cfg_cmd, reinterpret_cast<char *>(&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 -func (gc *GQGMCCounter) 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 -func (gc *GQGMCCounter) 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 -func (gc *GQGMCCounter) 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 -func (gc *GQGMCCounter) 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 -func (gc *GQGMCCounter) 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<cfgDataCount; i++) - { - // Convert little endian to big endian which GQ GMC wants. - if (mBig_endian) - pCfg_Data[i] = cfgData[i]; - else - pCfg_Data[i] = cfgData[cfgDataCount-1-i]; - } // end for loop - - return; -} // end writeConfigurationData() - -// loadConfigurationData private method writes all 256 bytes -// of the configuration data to the GQ GMC. This will take -// over a minute to complete. This is a practice in patience. -// The GQ GMC's write_cfg_cmd only transmits a single byte -// at a time. So it takes 256 transmits and each transmit -// has to wait for a 0xAA return. The user obviously should -// not update the NVM configuration too often. -void -func (gc *GQGMCCounter) loadConfigurationData() -{ - const - uint32_t retsize = 1; - char ret_char[retsize+1]; - - // Need a pointer to the local host computer's copy - // of the NVM configuration data. - uint8_t * pCfg_Data = (uint8_t *)&mCFG_Data; - - // Begin formulating the write configuration data command. - // "AD" is just a place holder for the address byte and data byte - // that will be dynamically derived and inserted. - string write_cfg_cmd = "<WCFGAD>>"; - - // The parameter and its data have to be dynamically derived. - // write_cfg_cmd[5] is parameter enumeration (aka address offset) - // write_cfg_cmd[6] is parameter data - - // Pack address and data into write_cfg_data_cmd with big - // endian byte order. - - // Address (ie, parameter) is less than 256 since configuration - // data has a fixed size of 256 bytes, so address is one byte. - - // pack address and data into command - for(uint16_t i=0; i<kNVMSize; i++) - { - // Increment the address for each additional data byte. - write_cfg_cmd[5] = uint8_t(i); - - // Load data one byte at a time - write_cfg_cmd[6] = pCfg_Data[i]; - -/* // debug code - if (i < 64) - { - for(int i=0; i<5; i++) - cout << write_cfg_cmd[i]; - cout << Hex(write_cfg_cmd[5]); - cout << Hex(write_cfg_cmd[6]); - cout << write_cfg_cmd[7]; - cout << write_cfg_cmd[8]; - cout << endl; - } -*/ // end debug code - - // Issue command to write configuration data, one byte - // at a time because that is the native write configuration - // command of the GQ GMC. - communicate(write_cfg_cmd, ret_char, retsize); - - // if read of returned data succeeded, convert raw data to float - if (mRead_status == true) - { - // We really don't care about the return value of 0xAA. If the - // GQ GMC does not recognize the command, it returns nothing and - // we get a read_status error. - } - else // else for failure, set error code - { - mError_code = eWrite_CFG; - break; // break out of for loop - } // end (read_status == true) - } // end for loop - - return; -} // loadConfigurationData() - -// eraseConfigurationData public method erases the configuration data -// in its entirety. The configuration returns to its factory default -// setting. It might have been better to call this the reset -// configuration data command. The command is erase_cfg_cmd -// (see GQ GMC COMMANDS above). -void -func (gc *GQGMCCounter) eraseConfigurationData() -{ - const - uint32_t retsize = 1; - char ret_char[retsize+1]; - - // Issue command to erase NVM configuration. - communicate(erase_cfg_cmd, ret_char, retsize); - - // If read of returned data succeeded, convert raw data to float, - if (mRead_status == true) - { - // We really don't care about the return value of 0xAA. If the - // GQ GMC does not recognize the command, it returns nothing and - // we get a read_status error. - } - else // else for failure, set the error code. - { - mError_code = eErase_CFG; - } - - return; -} // end eraseConfigurationData() - - -// The updateConfigurationdata public method is called to make changes -// to configuration data take effect. All other methods to modify -// the configuration data do not cause the GQ GMC to immediately -// change operation. This would not be desireable since various -// changes to the configuration may be interdependent and so -// we would want the changes to take effect simultaneously to -// insure proper operation. There are no arguments to call. -// The command is update_cfg_cmd (see GQ GMC COMMANDS above). -// The user who calls this method may want to pop-up a window -// stating that this will take about one minute. That is about -// how long it will take to write all 256 bytes to the GQ GMC. -// This method calls eraseConfigurationData() and -// loadConfigurationData() as part of the procedure needed to -// force the GQ GMC to implement operational changes per the -// new NVM configuration data. -void -func (gc *GQGMCCounter) updateConfigurationData() -{ - const - uint32_t retsize = 1; - char ret_char[retsize+1]; - - // 1st, we have to erase configuration data - // cout << erase_cfg_cmd << endl; // debug - eraseConfigurationData(); - // 2nd, write all 256 bytes of NVM to GQ GMC - // cout << "load cfg" << endl; // debug - loadConfigurationData(); - - // cout << update_cfg_cmd << endl; // debug - // Issue command to update NVM and force GQ GMC to change - // operation in accordance to new configuration data. - communicate(update_cfg_cmd, ret_char, retsize); - - // If read of returned data succeeded, convert raw data to float, - if (mRead_status == true) - { - // We really don't care about the return value of 0xAA. If the - // GQ GMC does not recognize the command, it returns nothing and - // we get a read_status error. - } - else // else for failure, set the error code. - { - mError_code = eUpdate_CFG; - } - - return; -} // end updateConfigurationData() - - -// sendKey is the public method to emulate any one of the 4 keys on -// the front panel of the GQ GMC. The front panel has a 'left arrow', -// 'up arrow, 'down arrow', and 'enter' keys. These are used to -// navigate through the GQ GMC's menu system. In principle, the -// menu system can be used to set virtually any and all of the -// configuration data (although this is not recommended for -// such configuration data as the calibration values). -// So instead of the writeConfigurationData method, -// the proper sequence of sending the keys would do the -// same thing. The command is derived dynamically, but the actual -// command is "<KEY0>>" or "<KEY1>>" or "<KEY2>>" or "<KEY3>>". -// 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 -func (gc *GQGMCCounter) 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 = "<KEY"; - - // Append key number which is an enumeration equal to the - // ASCII value of the key, ie, '0', '1', '2', or '3'. - keycmd += uint8_t(key); - // Append ">>" - 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 <month><day><year>, 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 -func (gc *GQGMCCounter) 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, <SETDATEMMXX>> 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 = "<SETDATEMM"; - setMonthCmd += uint8_t(month); - setMonthCmd += ">>"; - communicate(setMonthCmd, ret_char, retsize); - } - - // Set the day, <SETDATEDDXX>> 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 = "<SETDATEDD"; - setDayCmd += uint8_t(day); - setDayCmd += ">>"; - communicate(setDayCmd, ret_char, retsize); - } - - // Set the year, <SETDATEYYXX>> 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 = "<SETDATEYY"; - setYearCmd += uint8_t(year); - setYearCmd += ">>"; - communicate(setYearCmd, ret_char, retsize); - } - - return; -} // end set Date() - -// setTime is the public method to set the time of day. The time is -// passed as an ASCII string with the format of <hour><minutes><seconds>, -// 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 -func (gc *GQGMCCounter) 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, <SETTIMEHHXX>> 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 = "<SETTIMEHH"; - setHourCmd += uint8_t(hour); - setHourCmd += ">>"; - communicate(setHourCmd, ret_char, retsize); - } - - // Set the minute, <SETTIMEMMXX>> 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 = "<SETTIMEMM"; - setMinuteCmd += uint8_t(minute); - setMinuteCmd += ">>"; - communicate(setMinuteCmd, ret_char, retsize); - } - - // Set the seconds, <SETTIMESSXX>> 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 = "<SETTIMESS"; - setSecondCmd += uint8_t(second); - setSecondCmd += ">>"; - communicate(setSecondCmd, ret_char, retsize); - } - - return; -} // end setTime() - - -// PRIVATE METHODS - -// communicate private method is used to write/read data to/from -// the GMC-300. This method is expressedly designed to be called -// by methods which send an ASCII string and expect to receive -// returned data. However for flexibility, if the command string -// is null, no command is transmitted and if the expected number -// of return bytes is zero, no read is performed. -// cmd is the ASCII string command. -// retdata is the repository for the returned data. -// retbytes is the number of bytes of returned data. -void -func (gc *GQGMCCounter) communicate(const string cmd, char * retdata, uint32_t retbytes) -{ - // Clear the USB port of any left over data from last exchange. Even - // though we know how many bytes the GQ GMC transmits for each - // command, experience has shown this is the safe thing to do since - // there is no protocol for the returned data. - clearUSB(); - - //cout << cmd << endl; - // 1st, issue the command to the GMC-300, this is always an ASCII - // string. - // For flexibility, only transmit if cmdbytes is not 'null'. - if (cmd.size() > 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 -func (gc *GQGMCCounter) 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 -func (gc *GQGMCCounter) 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; i++) - { - rcvd += read(mUSB_serial, inp, 1); - inp = &retdata[rcvd]; - if (rcvd >= retbytes) break; - } // end for loop - - // debugging code - /* - inp = &retdata[0]; - for(uint32_t i=0; i<retbytes; i++) - { - cout << Hex(inp[i]) << "-"; - if (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() + errOpen = "Failed to open USB port" + errFirmware = "GQ GMC has older firmware. Some commands may not work." + errVersion = "Failed to read the version number of the firmware" + errSerialNumber = "Failed to read the serial number " + errCPM = "Failed to read the counts per minute " + errCPS = "Failed to read the counts per second " + errAutoCPS = "Failed to read auto counts per second " + errCFG = "Failed to get configuration data " + errEraseCFG = "Failed to erase configuration data " + errUpdateCFG = "Failed to update configuration data " + errClear = "Failed to clear USB input buffer. You should power cycle GQ GMC." + errBatteryVoltage = "Failed to read the battery voltage " + errHistoryData = "Failed to read the history data " + errHistoryDataLengthFmt = "Requested data length of the history command cannot exceed %d bytes" + errHistoryDataAddressFmt = "Address of the history command cannot exceed %d bytes" + errHistoryDataOverrunFmt = "History data length added to the address cannot exceed %d bytes" + errYear = "Failed to set year" + errMonth = "Failed to set month" + errDay = "Failed to set day" + errHour = "Failed to set hour" + errMinute = "Failed to set minute" + errSecond = "Failed to set second" +) // GQGMCCounter is a GQ GMC Counter type GQGMCCounter struct { - fh string // TODO: make this a file handle. - config serial.Config + port *serial.Port + config *serial.Config } // NewGQGMC creates a new GQGMC Counter instance func NewGQGMC(c Config) (*GQGMCCounter, error) { - var gc GQGMCCounter - - portCfg := serial.Config { - Name: c.Device, - Baud: 57600, + cfg := serial.Config{ + Name: c.Device, + Baud: 57600, ReadTimeout: 500 * time.Millisecond, } - gc.port = OpenPort(portCfg) + p, err := serial.OpenPort(&cfg) + if err != nil { + return nil, err + } //vers := getVersion() - //getConfigurationData() - return &gc, nil + //getConfigurationData() + return &GQGMCCounter{port: p, config: &cfg}, nil } // Clear clears out any remaining data @@ -837,22 +171,22 @@ func (gc *GQGMCCounter) Clear() error { // Version gets the version of the device func (gc *GQGMCCounter) Version() (string, error) { - //communicate(get_version_cmd, version, versize); - // use cmdGetVersion. Returns 14 byte string. - return "", nil + //communicate(get_version_cmd, version, versize); + // use cmdGetVersion. Returns 14 byte string. + return "", nil } // SerialNum gets the serial number of the device func (gc *GQGMCCounter) SerialNum() (string, error) { - //communicate(get_serial_cmd, serial_number, sernumsize); - // use cmdGetSerial. Returns 7 bytes. - // Turn each 4 bits into corresponging hex char. - bs := []byte{0, 0x30, 0, 0xE3, 0x4A, 0x35, 0x1A} - for _, b := range bs { - fmt.Printf("%02X", b) - } - fmt.Println("") - return "", nil + //communicate(get_serial_cmd, serial_number, sernumsize); + // use cmdGetSerial. Returns 7 bytes. + // Turn each 4 bits into corresponging hex char. + bs := []byte{0, 0x30, 0, 0xE3, 0x4A, 0x35, 0x1A} + for _, b := range bs { + fmt.Printf("%02X", b) + } + fmt.Println("") + return "", nil } // GetCPM returns CPM @@ -879,12 +213,6 @@ func (gc *GQGMCCounter) GetCPS() (uint16, error) { return 0, nil } - -const ( - VoltageIdeal = 98 - VoltageTooLow = 75 -) - // GetVoltage returns current battery voltage func (gc *GQGMCCounter) GetVoltage() (int16, error) { // Do this differently - for 9.6 return 96. And if not supported, @@ -910,32 +238,38 @@ func (gc *GQGMCCounter) GetVoltage() (int16, error) { // GetHistoryData Should return history data but is unimplemented for now func (gc *GQGMCCounter) GetHistoryData() { // It's not recommended to use this so blank for now. + return } +// TurnOnCPS turns on CPS collection func (gc *GQGMCCounter) TurnOnCPS() error { -// 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). -//sendCmd(turn_on_cps_cmd); -// There is no pass/fail return from GQ GMC + // 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). + //sendCmd(turn_on_cps_cmd); + // There is no pass/fail return from GQ GMC + return nil } +// TurnOffCPS turns off CPS collection func (gc *GQGMCCounter) TurnOffCPS() error { -//sendCmd(turn_off_cps_cmd); -//call Clear() + //sendCmd(turn_off_cps_cmd); + //call Clear() + return nil } -func (gc *GQGMCCounter) getAutoCPS() (uint16, error) { +// GetAutoCPS gets a reading once auto CPS is turned on +func (gc *GQGMCCounter) GetAutoCPS() (uint16, error) { //uint32_t cpssize = 2; // 2 bytes of returned data //read-from-port(cps_char, cpssize); // 1st byte is MSB, but note that upper two bits are reserved bits. @@ -946,7 +280,93 @@ func (gc *GQGMCCounter) getAutoCPS() (uint16, error) { return 0, nil } +// TurnOffPower turns the device off func (gc *GQGMCCounter) TurnOffPower() { - sendCmd(turn_off_pwr_cmd); - // Note that power off cannot fail because the GQ GMC returns nothing. + //sendCmd(turn_off_pwr_cmd) + // Note that power off cannot fail because the GQ GMC returns nothing. + return +} + +// GetConfiguration reads configuration data +func (gc *GQGMCCounter) GetConfiguration() { + // Issue command to get configuration and read returned data. + // communicate(get_cfg_cmd, reinterpret_cast<char *>(&mCFG_Data), +} + +// SetConfiguration writes configuration data +func (gc *GQGMCCounter) SetConfiguration() { + // See the ConfigurationData functions in gqgmc.cc +} + +// ResetConfiguration resets to factory default +func (gc *GQGMCCounter) ResetConfiguration() { + //uint32_t retsize = 1; + //char ret_char[retsize+1]; + //communicate(erase_cfg_cmd, ret_char, retsize); +} + +// SetDate sets the date - format of YYYYMMDD +func (gc *GQGMCCounter) SetDate(date string) { + //setMonthCmd = "<SETDATEMM"; + //setMonthCmd += uint8_t(month); + //setMonthCmd += ">>"; + //communicate(setMonthCmd, ret_char, retsize); + + //setDayCmd = "<SETDATEDD"; + //setDayCmd += uint8_t(day); + //setDayCmd += ">>"; + //communicate(setDayCmd, ret_char, retsize); + + // year - last two digits + //setYearCmd = "<SETDATEYY"; + //setYearCmd += uint8_t(year); + //setYearCmd += ">>"; + //communicate(setYearCmd, ret_char, retsize); +} + +// SetTime sets the time (HH:MM:SS) +func (gc *GQGMCCounter) SetTime(time string) { + //setHourCmd = "<SETTIMEHH"; + //setHourCmd += uint8_t(hour); + //setHourCmd += ">>"; + //communicate(setHourCmd, ret_char, retsize); + + //setMinuteCmd = "<SETTIMEMM"; + //setMinuteCmd += uint8_t(minute); + //setMinuteCmd += ">>"; + //communicate(setMinuteCmd, ret_char, retsize); + + //setSecondCmd = "<SETTIMESS"; + //setSecondCmd += uint8_t(second); + //setSecondCmd += ">>"; + //communicate(setSecondCmd, ret_char, retsize); +} + +func (gc *GQGMCCounter) communicate(cmd []byte, length uint32) ([]byte, error) { + //clearUSB(); + //if (cmd.size() > 0) sendCmd(cmd); + //if (retbytes > 0) readCmdReturn(retdata, retbytes); + return nil, nil +} + +func (gc *GQGMCCounter) sendCmd(cmd []byte) { + // The port write thing + gc.port.Write(cmd) + return +} + +func (gc *GQGMCCounter) readCmdReturn(length uint32) ([]byte, error) { + // 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; i++) + // rcvd += read(mUSB_serial, inp, 1); + // inp = &retdata[rcvd]; + // if (rcvd >= retbytes) break; + return nil, nil }