Skip to content
Snippets Groups Projects
Commit 15c01345 authored by Kevin Lyda's avatar Kevin Lyda :speech_balloon:
Browse files

A further pass at communicating with the GQ GMC.

parent 869f07b2
Branches
No related tags found
No related merge requests found
Pipeline #
TODO 0 → 100644
Package - https://github.com/Debian/dh-make-golang
Writing data to device: https://golang.org/pkg/encoding/binary/
Also: https://github.com/tarm/serial
......@@ -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.
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment