diff --git a/TODO b/TODO new file mode 100644 index 0000000000000000000000000000000000000000..5e7940e189e38e385a9770a2147d96c63e5950d7 --- /dev/null +++ b/TODO @@ -0,0 +1,4 @@ +Package - https://github.com/Debian/dh-make-golang + +Writing data to device: https://golang.org/pkg/encoding/binary/ +Also: https://github.com/tarm/serial diff --git a/devices/geiger/gqgmc.go b/devices/geiger/gqgmc.go index 73e4483044eb47681ee8c8116c501b1daa4a3a7a..18b6b537518a2d2507ba903df8720b0b2d98fa0d 100644 --- a/devices/geiger/gqgmc.go +++ b/devices/geiger/gqgmc.go @@ -7,19 +7,946 @@ package geiger +import ( + "time" + + "github.com/tarm/serial" +) + +const ( + powerOnOff = 0 + alarmOnOff = 1 + speakerOnOff = 2 + graphicModeOnOff = 3 + backlightTimeoutSeconds = 4 + idleTitleDisplayMode = 5 + alarmCPMValue = 6 + calibrationCPM0 = 8 + calibrationSvUc0 = 10 + calibrationCPM1 = 14 + calibrationSvUc1 = 16 + calibrationCPM2 = 20 + calibrationSvUc2 = 22 + idleDisplayMode = 26 + alarmValueuSvUc = 27 + alarmType = 31 + saveDataType = 32 + swivelDisplay = 33 + zoom = 34 + dataSaveAddress = 38 + dataReadAddress = 41 + nPowerSavingMode = 44 + nSensitivityMode = 45 + nCounterDelay = 46 + nVoltageOffset = 48 + maxCPM = 49 + nSensitivityAutoModeThreshold = 51 + saveDate = 52 + saveTime = 55 + maxBytes = 58 +) + +var configBytes = map[int]int{ + powerOnOff: 1, + alarmOnOff: 1, + speakerOnOff: 1, + graphicModeOnOff: 1, + backlightTimeoutSeconds: 1, + idleTitleDisplayMode: 1, + alarmCPMValue: 2, + calibrationCPM0: 2, + calibrationSvUc0: 4, + calibrationCPM1: 2, + calibrationSvUc1: 4, + calibrationCPM2: 2, + calibrationSvUc2: 4, + idleDisplayMode: 1, + alarmValueuSvUc: 4, + alarmType: 1, + saveDataType: 1, + swivelDisplay: 1, + zoom: 4, + dataSaveAddress: 3, + dataReadAddress: 3, + nPowerSavingMode: 1, + nSensitivityMode: 1, + nCounterDelay: 2, + nVoltageOffset: 1, + maxCPM: 2, + nSensitivityAutoModeThreshold: 1, + saveDate: 3, + saveTime: 3, + maxBytes: 1, +} + +const ( + saveOff = iota + saveCPS + saveCPM + saveCPH + saveMax +) + +const ( + key1 = iota + key2 + key3 + key4 +) + +const ( + historyDataMaxSize = 0x1000 + historyAddrMaxSize = 0x10000 + forFirmware = 2.23 + kNVMSize = 256 +) + +const ( +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() + // GQGMCCounter is a GQ GMC Counter type GQGMCCounter struct { fh string // TODO: make this a file handle. + config serial.Config } // NewGQGMC creates a new GQGMC Counter instance func NewGQGMC(c Config) (*GQGMCCounter, error) { - return &GQGMCCounter{ - fh: "TODO", - }, nil + var gc GQGMCCounter + + portCfg := serial.Config { + Name: c.Device, + Baud: 57600, + ReadTimeout: 500 * time.Millisecond, + } + gc.port = OpenPort(portCfg) + //vers := getVersion() + //getConfigurationData() + return &gc, nil +} + +// Clear clears out any remaining data +func (gc *GQGMCCounter) Clear() error { + // Read up to 10 chars until nothing comes back. + // error otherwise. + return nil +} + +// 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 +} + +// 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 +} + +// GetCPM returns CPM +func (gc *GQGMCCounter) GetCPM() (uint16, error) { + //uint32_t cpmsize = 2; // 2 bytes of returned data + //communicate(get_cpm_cmd, cpm_char, cpmsize); + // 1st byte is MSB, but note that upper two bits are reserved bits. + // Note that shifting and bitmasking performed in uP register, so + // endianess is irrevelant. + //cpm_int |= ((uint16_t(cpm_char[0]) << 8) & 0x3f00); + //cpm_int |= (uint16_t(cpm_char[1]) & 0x00ff); + return 0, nil +} + +// GetCPS returns CPS +func (gc *GQGMCCounter) GetCPS() (uint16, error) { + //uint32_t cpssize = 2; // 2 bytes of returned data + //communicate(get_cps_cmd, cps_char, cpssize); + // 1st byte is MSB, but note that upper two bits are reserved bits. + // Note that shifting and bitmasking performed in uP register, so + // endianess is irrevelant. + //cps_int |= ((uint16_t(cps_char[0]) << 8) & 0x3f00); + //cps_int |= (uint16_t(cps_char[1]) & 0x00ff); + 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, + // Return -1. + // Public method to read voltage value of battery. The GQ GMC returns + // a single byte whose integer value converted to a float divided by 10 + // equals the battery voltage. For example, 0x60 = 96 converts to 9.6 Volts. + // The ideal value is 9.8 volts. So practically speaking, we should not + // expect to see a value higher than 100. The command is get_voltage_cmd + // (see GQ GMC COMMANDS above). If the voltage falls below 7.5V, GQ LLC + // says the geiger counter cannot be expected to operate properly. + //func (gc *GQGMCCounter) getBatteryVoltage() + //uint32_t voltsize = 1; // one byte of returned data + // Issue command to get battery voltage and read returned data. + //communicate(get_voltage_cmd, voltage_char, voltsize); + // If read of returned data succeeded, convert raw data to float, + //int32_t voltage_int = int16_t(voltage_char[0]); + //voltage = float(voltage_int) / 10.0; + + return 0, nil +} + +// 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. +} + +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 +} + +func (gc *GQGMCCounter) TurnOffCPS() error { +//sendCmd(turn_off_cps_cmd); +//call Clear() +} + +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. + // Note that shifting and bitmasking performed in uP register, so + // endianess is irrevelant. + //cps_int |= ((uint16_t(cps_char[0]) << 8) & 0x3f00); + //cps_int |= (uint16_t(cps_char[1]) & 0x00ff); + return 0, nil } -// GetReading returns a reading. -func (gc *GQGMCCounter) GetReading() (*Reading, error) { - return nil, nil +func (gc *GQGMCCounter) TurnOffPower() { + sendCmd(turn_off_pwr_cmd); + // Note that power off cannot fail because the GQ GMC returns nothing. }