// // gqgmc.go // Copyright (C) 2017 kevin // // Distributed under terms of the GPL license. // package geiger import ( "errors" "fmt" "time" "github.com/tarm/serial" ) // The Ideal and Low voltage threshholds const ( VoltageIdeal = 98 VoltageTooLow = 75 ) 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 ( historyDataMaxSize = 0x1000 historyAddrMaxSize = 0x10000 forFirmware = 2.23 nvmSize = 256 ) const ( cmdGetSerial = ">" cmdGetVersion = ">" cmdGetVoltage = ">" cmdGetCPM = ">" cmdGetCPS = ">" cmdGetCfg = ">" cmdEraseCfg = ">" cmdUpdateCfg = ">" cmdTurnOnCPS = ">" cmdTurnOffCPS = ">" cmdTurnOffPwr = ">" ) 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" ) // GQGMCCounter is a GQ GMC Counter type GQGMCCounter struct { port *serial.Port config *serial.Config } // NewGQGMC creates a new GQGMC Counter instance func NewGQGMC(c Config) (*GQGMCCounter, error) { cfg := serial.Config{ Name: c.Device, Baud: 57600, ReadTimeout: 500 * time.Millisecond, } p, err := serial.OpenPort(&cfg) if err != nil { return nil, err } //vers := getVersion() //getConfigurationData() return &GQGMCCounter{port: p, config: &cfg}, 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 } func (gc *GQGMCCounter) getReading(what string) (uint16, error) { buf, err := gc.communicate([]byte(what), 2) if err != nil { return 0, err } reading := ((uint16(buf[0]) << 8) & 0x3f00) reading |= (uint16(buf[1]) & 0x00ff) return reading, nil } // GetCPM returns CPM func (gc *GQGMCCounter) GetCPM() (uint16, error) { return gc.getReading(cmdGetCPM) } // GetCPS returns CPS func (gc *GQGMCCounter) GetCPS() (uint16, error) { return gc.getReading(cmdGetCPS) } // 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. return } // TurnOnCPS turns on CPS collection func (gc *GQGMCCounter) TurnOnCPS() error { // turnOnCPS is public method to enable automatic reporting of the // count per second (CPS) value. First, I would say don't use this // command. Since the returned data has no protocol, that is, there // is no start/stop marker, no identification, no nothing but a // sequence of bytes, any other command issued while CPS is turned on // will take extraordinary effort not to confuse its returned data // with the CPS data. To handle it correctly, you would have to // wait for the CPS data to be returned, and then issue the command. // This would be most safely done by creating a new thread to // read the CPS and using a mutex to signal opportunity to issue a // separate command. The command is turn_on_cps_cmd // (see GQ GMC COMMANDS above). The returned data is always two // bytes so that samples > 255 can be reported (even though unlikely). //sendCmd(turn_on_cps_cmd); // There is no pass/fail return from GQ GMC return nil } // TurnOffCPS turns off CPS collection func (gc *GQGMCCounter) TurnOffCPS() error { //sendCmd(turn_off_cps_cmd); //call Clear() return nil } // GetAutoCPS gets a reading once auto CPS is turned on func (gc *GQGMCCounter) GetAutoCPS() (uint16, error) { //uint32_t cpssize = 2; // 2 bytes of returned data //read-from-port(cps_char, cpssize); // 1st byte is MSB, but note that upper two bits are reserved bits. // 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 } // TurnOffPower turns the device off func (gc *GQGMCCounter) TurnOffPower() { //sendCmd(turn_off_pwr_cmd) // Note that power off cannot fail because the GQ GMC returns nothing. return } // GetConfiguration reads configuration data func (gc *GQGMCCounter) GetConfiguration() { // Issue command to get configuration and read returned data. // communicate(get_cfg_cmd, reinterpret_cast(&mCFG_Data), } // SetConfiguration writes configuration data func (gc *GQGMCCounter) SetConfiguration() { // See the ConfigurationData functions in gqgmc.cc } // ResetConfiguration resets to factory default func (gc *GQGMCCounter) ResetConfiguration() { //uint32_t retsize = 1; //char ret_char[retsize+1]; //communicate(erase_cfg_cmd, ret_char, retsize); } // SetDate sets the date - format of YYYYMMDD func (gc *GQGMCCounter) SetDate(date string) { //setMonthCmd = " 0 { gc.sendCmd(cmd) } if length != 0 { return gc.readCmd(length) } return nil, nil } func (gc *GQGMCCounter) sendCmd(cmd []byte) { gc.port.Write(cmd) return } func (gc *GQGMCCounter) readCmd(length uint32) ([]byte, error) { buf := make([]byte, length) n, err := gc.port.Read(buf) if err != nil { return nil, err } if uint32(n) != length { return nil, errors.New("Short read") } return buf, nil }