// // gqgmc.go // Copyright (C) 2017 kevin // // Distributed under terms of the GPL license. // package geiger import ( "encoding/hex" "errors" "fmt" "strconv" "sync" "time" "github.com/tarm/serial" ) // The Ideal and Low voltage threshholds const ( VoltageIdeal = 98 VoltageTooLow = 75 ) const ( cmdGetSerial = ">" // GMC-280, GMC-300 Re.2.11 cmdGetVersion = ">" // GMC-280, GMC-300 Re.2.0x, Re.2.10 cmdGetVoltage = ">" // GMC-280, GMC-300 Re.2.0x, Re.2.10 cmdGetCPM = ">" // GMC-280, GMC-300 Re.2.0x, Re.2.10 cmdGetCPS = ">" // GMC-280, GMC-300 Re.2.0x, Re.2.10 cmdGetCfg = ">" // GMC-280, GMC-300 Re.2.10 cmdEraseCfg = ">" // GMC-280, GMC-300 Re.2.10 cmdUpdateCfg = ">" // GMC-280, GMC-300 Re.2.20 cmdTurnOnCPS = ">" // GMC-280, GMC-300 Re.2.10 cmdTurnOffCPS = ">" // Re.2.10 cmdTurnOffPwr = ">" // GMC-280, GMC-300 Re.2.11 cmdTurnOnPwr = ">" // GMC-280, GMC-300 Re.3.10 cmdFactoryReset = ">" // GMC-280, GMC-300 Re.3.00 cmdReboot = ">" // GMC-280, GMC-300 Re.3.00 cmdGetTime = ">" // GMC-280, GMC-300 Re.3.00 cmdGetTemp = ">" // GMC-320 Re.3.01 cmdGetGyro = ">" // GMC-320 Re.3.01 ) var ( mod280n300 = []string{"GMC-280", "GMC-300"} mod320 = []string{"GMC-320"} ) // GQGMCCounter is a GQ GMC Counter type GQGMCCounter struct { port *serial.Port config *serial.Config version, model, serial string mutex *sync.Mutex } // NewGQGMC creates a new GQGMC Counter instance func NewGQGMC(c Config) (*GQGMCCounter, error) { var gc GQGMCCounter var buf []byte gc.config = &serial.Config{ Name: c.Device, Baud: 57600, ReadTimeout: 500 * time.Millisecond, } p, err := serial.OpenPort(gc.config) if err != nil { return nil, err } gc.port = p gc.mutex = &sync.Mutex{} buf, err = gc.communicate(cmdGetVersion, 14) if err == nil { gc.model = string(buf[:7]) gc.version = string(buf[7:]) } if gc.supportedModels(mod280n300) && !gc.versionLT("Re 2.11") { buf, err := gc.communicate(cmdGetSerial, 7) if err == nil { gc.serial = hex.EncodeToString(buf) } } //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. for i := 0; i < 10; i++ { if _, err := gc.recv(1); err != nil { break } } return nil } // Version gets the version of the device func (gc *GQGMCCounter) Version() string { return gc.version } // Model gets the model of the device func (gc *GQGMCCounter) Model() string { return gc.model } // Serial gets the serial number of the device func (gc *GQGMCCounter) Serial() string { return gc.serial } func (gc *GQGMCCounter) getReading(what string) (uint16, error) { buf, err := gc.communicate(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) } // Volts returns current battery voltage (times 10) func (gc *GQGMCCounter) Volts() (int16, error) { if !gc.supportedModels(mod280n300) { return 0, errors.New("Unsupported model") } if gc.versionLT("Re 2.00") { return 0, errors.New("Unsupported version") } volts, err := gc.communicate(cmdGetVoltage, 1) if err != nil { return 0, err } return int16(volts[0]), err } // 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 { if !gc.supportedModels(mod280n300) { return errors.New("Unsupported model") } if gc.versionLT("Re 2.10") { return errors.New("Unsupported version") } gc.mutex.Lock() gc.send(cmdTurnOnCPS) gc.mutex.Unlock() return nil } // TurnOffCPS turns off CPS collection func (gc *GQGMCCounter) TurnOffCPS() error { if !gc.supportedModels(mod280n300) { return errors.New("Unsupported model") } if gc.versionLT("Re 2.10") { return errors.New("Unsupported version") } gc.mutex.Lock() gc.send(cmdTurnOffCPS) gc.Clear() gc.mutex.Unlock() return nil } // GetAutoCPS gets a reading once auto CPS is turned on func (gc *GQGMCCounter) GetAutoCPS() (uint16, error) { gc.mutex.Lock() buf, err := gc.recv(2) gc.mutex.Unlock() if err != nil { return 0, err } cps := ((uint16(buf[0]) << 8) & 0x3f00) cps |= (uint16(buf[1]) & 0x00ff) return cps, nil } // TurnOnPower turns the device on func (gc *GQGMCCounter) TurnOnPower() { if !gc.supportedModels(mod280n300) { return } if gc.versionLT("Re 3.10") { return } gc.mutex.Lock() gc.send(cmdTurnOnPwr) gc.mutex.Unlock() return } // TurnOffPower turns the device off func (gc *GQGMCCounter) TurnOffPower() { if !gc.supportedModels(mod280n300) { return } if gc.versionLT("Re 2.11") { return } gc.mutex.Lock() gc.send(cmdTurnOffPwr) gc.mutex.Unlock() return } // GetConfiguration reads configuration data func (gc *GQGMCCounter) GetConfiguration() error { if !gc.supportedModels(mod280n300) { return errors.New("Unsupported Model") } if gc.versionLT("Re 2.10") { return errors.New("Unsupported version") } cfg, err := gc.communicate(cmdGetCfg, 256) if err != nil { return err } fmt.Printf("Configuration: %+v\n", cfg) return nil } // SetConfiguration writes configuration data func (gc *GQGMCCounter) SetConfiguration() { if !gc.supportedModels(mod280n300) { return } if gc.versionLT("Re 2.10") { return } // See the ConfigurationData functions in gqgmc.cc } // SetTime sets the time func (gc *GQGMCCounter) SetTime(t time.Time) { if !gc.supportedModels(mod280n300) { return } if gc.versionLT("Re 2.23") { return } gc.mutex.Lock() defer gc.mutex.Unlock() if gc.versionLT("Re 3.30") { gc.setTimeParts(t) } gc.setTimeAll(t) } func (gc *GQGMCCounter) setTimeParts(t time.Time) { cmd := make([]byte, 13) var timeCmds = []struct { cmd string unit int }{ {">") // Mutex acquired in setTime() gc.port.Write(cmd) gc.recv(1) } } func (gc *GQGMCCounter) setTimeAll(t time.Time) { cmd := make([]byte, 20) copy(cmd[:], ">") // Mutex acquired in setTime() gc.port.Write(cmd) gc.recv(1) } // GetTime gets the time func (gc *GQGMCCounter) GetTime() (time.Time, error) { if !gc.supportedModels(mod280n300) { return time.Unix(0, 0), errors.New("Unsupported model") } if gc.versionLT("Re 3.00") { return time.Unix(0, 0), errors.New("Unsupported version") } b, err := gc.communicate(cmdGetTime, 7) if err != nil { return time.Unix(0, 0), err } t := time.Date(int(b[0])+2000, time.Month(b[1]), int(b[2]), int(b[3]), int(b[4]), int(b[5]), 0, time.Local) return t, nil } // GetTemp gets the temp func (gc *GQGMCCounter) GetTemp() (float64, error) { if !gc.supportedModels(mod320) { return 0, errors.New("Unsupported model") } if gc.versionLT("Re 3.01") { return 0, errors.New("Unsupported version") } t, err := gc.communicate(cmdGetTemp, 4) if err != nil { return 0, err } var temp float64 temp, err = strconv.ParseFloat(fmt.Sprintf("%d.%d", uint8(t[0]), uint8(t[1])), 64) if err != nil { return 0, err } if t[2] != 0 { temp = -temp } return temp, nil } // GetGyro gets the position in space func (gc *GQGMCCounter) GetGyro() (int16, int16, int16, error) { if !gc.supportedModels(mod320) { return 0, 0, 0, errors.New("Unsupported model") } if gc.versionLT("Re 3.01") { return 0, 0, 0, errors.New("Unsupported version") } buf, err := gc.communicate(cmdGetGyro, 7) if err != nil { return 0, 0, 0, err } x := (int16(buf[0]) << 8) x |= (int16(buf[1]) & 0x00ff) y := (int16(buf[0]) << 8) y |= (int16(buf[1]) & 0x00ff) z := (int16(buf[0]) << 8) z |= (int16(buf[1]) & 0x00ff) return x, y, z, nil } // FactoryReset does a factory reset func (gc *GQGMCCounter) FactoryReset() { if !gc.supportedModels(mod280n300) { return } if gc.versionLT("Re 3.00") { return } gc.mutex.Lock() gc.send(cmdFactoryReset) gc.mutex.Unlock() return } // Reboot reboots the device func (gc *GQGMCCounter) Reboot() { if !gc.supportedModels(mod280n300) { return } if gc.versionLT("Re 3.00") { return } gc.mutex.Lock() gc.send(cmdReboot) gc.mutex.Unlock() return } func (gc *GQGMCCounter) supportedModels(models []string) bool { for _, model := range models { if model == gc.model { return true } } return false } func (gc *GQGMCCounter) versionLT(version string) bool { return gc.version < version } func (gc *GQGMCCounter) communicate(cmd string, length int) ([]byte, error) { gc.mutex.Lock() defer gc.mutex.Unlock() gc.Clear() if len(cmd) > 0 { gc.send(cmd) } if length != 0 { return gc.recv(length) } return nil, nil } func (gc *GQGMCCounter) send(cmd string) { gc.port.Write([]byte(cmd)) return } func (gc *GQGMCCounter) recv(length int) ([]byte, error) { buf := make([]byte, length) n, err := gc.port.Read(buf) if err != nil { return nil, err } if n != length { for i := 0; i < 20; i++ { _, err = gc.port.Read(buf[len(buf):]) if err != nil { return nil, err } if len(buf) == length { break } } } if n != length { return nil, fmt.Errorf("Short read (got: %d, wanted: %d)", uint32(n), length) } return buf, nil }