Select Git revision
gqgmc.go 11.09 KiB
//
// gqgmc.go
// Copyright (C) 2017 kevin <kevin@phrye.com>
//
// 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 = "<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"
)
// 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<char *>(&mCFG_Data),
}
// SetConfiguration writes configuration data
func (gc *GQGMCCounter) SetConfiguration() {
// See the ConfigurationData functions in gqgmc.cc
}
// ResetConfiguration resets to factory default
func (gc *GQGMCCounter) ResetConfiguration() {
//uint32_t retsize = 1;
//char ret_char[retsize+1];
//communicate(erase_cfg_cmd, ret_char, retsize);
}
// SetDate sets the date - format of YYYYMMDD
func (gc *GQGMCCounter) SetDate(date string) {
//setMonthCmd = "<SETDATEMM";
//setMonthCmd += uint8_t(month);
//setMonthCmd += ">>";
//communicate(setMonthCmd, ret_char, retsize);
//setDayCmd = "<SETDATEDD";
//setDayCmd += uint8_t(day);
//setDayCmd += ">>";
//communicate(setDayCmd, ret_char, retsize);
// year - last two digits
//setYearCmd = "<SETDATEYY";
//setYearCmd += uint8_t(year);
//setYearCmd += ">>";
//communicate(setYearCmd, ret_char, retsize);
}
// SetTime sets the time (HH:MM:SS)
func (gc *GQGMCCounter) SetTime(time string) {
//setHourCmd = "<SETTIMEHH";
//setHourCmd += uint8_t(hour);
//setHourCmd += ">>";
//communicate(setHourCmd, ret_char, retsize);
//setMinuteCmd = "<SETTIMEMM";
//setMinuteCmd += uint8_t(minute);
//setMinuteCmd += ">>";
//communicate(setMinuteCmd, ret_char, retsize);
//setSecondCmd = "<SETTIMESS";
//setSecondCmd += uint8_t(second);
//setSecondCmd += ">>";
//communicate(setSecondCmd, ret_char, retsize);
}
func (gc *GQGMCCounter) communicate(cmd []byte, length uint32) ([]byte, error) {
gc.Clear()
if len(cmd) > 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
}