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.
}