gqgmc.go 9.89 KB
Newer Older
1 2 3 4 5 6 7 8 9
//
// gqgmc.go
// Copyright (C) 2017 kevin <kevin@phrye.com>
//
// Distributed under terms of the GPL license.
//

package geiger

10
import (
Kevin Lyda's avatar
Kevin Lyda committed
11
	"encoding/hex"
Kevin Lyda's avatar
Kevin Lyda committed
12
	"errors"
Kevin Lyda's avatar
Kevin Lyda committed
13
	"fmt"
Kevin Lyda's avatar
Kevin Lyda committed
14
	"strconv"
Kevin Lyda's avatar
Kevin Lyda committed
15
	"sync"
16 17 18 19 20
	"time"

	"github.com/tarm/serial"
)

Kevin Lyda's avatar
Kevin Lyda committed
21 22 23 24 25 26
// The Ideal and Low voltage threshholds
const (
	VoltageIdeal  = 98
	VoltageTooLow = 75
)

27
const (
Kevin Lyda's avatar
Kevin Lyda committed
28 29 30 31 32 33 34 35
	cmdGetSerial    = "<GETSERIAL>>"    // GMC-280, GMC-300 Re.2.11
	cmdGetVersion   = "<GETVER>>"       // GMC-280, GMC-300 Re.2.0x, Re.2.10
	cmdGetVoltage   = "<GETVOLT>>"      // GMC-280, GMC-300 Re.2.0x, Re.2.10
	cmdGetCPM       = "<GETCPM>>"       // GMC-280, GMC-300 Re.2.0x, Re.2.10
	cmdGetCPS       = "<GETCPS>>"       // GMC-280, GMC-300 Re.2.0x, Re.2.10
	cmdGetCfg       = "<GETCFG>>"       // GMC-280, GMC-300 Re.2.10
	cmdEraseCfg     = "<ECFG>>"         // GMC-280, GMC-300 Re.2.10
	cmdUpdateCfg    = "<CFGUPDATE>>"    // GMC-280, GMC-300 Re.2.20
Kevin Lyda's avatar
Kevin Lyda committed
36
	cmdTurnOnCPS    = "<HEARTBEAT1>>"   // GMC-280, GMC-300 Re.2.10
Kevin Lyda's avatar
Kevin Lyda committed
37 38
	cmdTurnOffCPS   = "<HEARTBEAT0>>"   // Re.2.10
	cmdTurnOffPwr   = "<POWEROFF>>"     // GMC-280, GMC-300 Re.2.11
Kevin Lyda's avatar
Kevin Lyda committed
39
	cmdTurnOnPwr    = "<POWERON>>"      // GMC-280, GMC-300 Re.3.10
Kevin Lyda's avatar
Kevin Lyda committed
40 41 42 43 44
	cmdFactoryReset = "<FACTORYRESET>>" // GMC-280, GMC-300 Re.3.00
	cmdReboot       = "<REBOOT>>"       // GMC-280, GMC-300 Re.3.00
	cmdGetTime      = "<GETDATETIME>>"  // GMC-280, GMC-300 Re.3.00
	cmdGetTemp      = "<GETTEMP>>"      // GMC-320 Re.3.01
	cmdGetGyro      = "<GETGYRO>>"      // GMC-320 Re.3.01
45 46
)

Kevin Lyda's avatar
Kevin Lyda committed
47 48 49 50 51
var (
	mod280n300 = []string{"GMC-280", "GMC-300"}
	mod320     = []string{"GMC-320"}
)

52 53
// GQGMCCounter is a GQ GMC Counter
type GQGMCCounter struct {
Kevin Lyda's avatar
Kevin Lyda committed
54 55
	port   *serial.Port
	config *serial.Config
Kevin Lyda's avatar
Kevin Lyda committed
56
	version,
Kevin Lyda's avatar
Kevin Lyda committed
57 58
	model,
	serial string
Kevin Lyda's avatar
Kevin Lyda committed
59
	mutex *sync.Mutex
60 61 62 63
}

// NewGQGMC creates a new GQGMC Counter instance
func NewGQGMC(c Config) (*GQGMCCounter, error) {
Kevin Lyda's avatar
Kevin Lyda committed
64
	var gc GQGMCCounter
Kevin Lyda's avatar
Kevin Lyda committed
65
	var buf []byte
Kevin Lyda's avatar
Kevin Lyda committed
66 67

	gc.config = &serial.Config{
Kevin Lyda's avatar
Kevin Lyda committed
68 69
		Name:        c.Device,
		Baud:        57600,
70 71
		ReadTimeout: 500 * time.Millisecond,
	}
Kevin Lyda's avatar
Kevin Lyda committed
72
	p, err := serial.OpenPort(gc.config)
Kevin Lyda's avatar
Kevin Lyda committed
73 74 75
	if err != nil {
		return nil, err
	}
Kevin Lyda's avatar
Kevin Lyda committed
76
	gc.port = p
Kevin Lyda's avatar
Kevin Lyda committed
77
	gc.mutex = &sync.Mutex{}
Kevin Lyda's avatar
Kevin Lyda committed
78 79 80 81 82 83 84 85 86 87 88
	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)
		}
	}
Kevin Lyda's avatar
Kevin Lyda committed
89
	//getConfigurationData()
Kevin Lyda's avatar
Kevin Lyda committed
90
	return &gc, nil
91 92 93 94 95 96
}

// Clear clears out any remaining data
func (gc *GQGMCCounter) Clear() error {
	// Read up to 10 chars until nothing comes back.
	// error otherwise.
Kevin Lyda's avatar
Kevin Lyda committed
97 98 99 100 101
	for i := 0; i < 10; i++ {
		if _, err := gc.readCmd(1); err != nil {
			break
		}
	}
102 103 104 105
	return nil
}

// Version gets the version of the device
Kevin Lyda's avatar
Kevin Lyda committed
106 107 108 109 110 111 112
func (gc *GQGMCCounter) Version() string {
	return gc.version
}

// Model gets the model of the device
func (gc *GQGMCCounter) Model() string {
	return gc.model
113 114
}

Kevin Lyda's avatar
Kevin Lyda committed
115 116 117
// Serial gets the serial number of the device
func (gc *GQGMCCounter) Serial() string {
	return gc.serial
118 119
}

Kevin Lyda's avatar
Kevin Lyda committed
120
func (gc *GQGMCCounter) getReading(what string) (uint16, error) {
Kevin Lyda's avatar
Kevin Lyda committed
121
	buf, err := gc.communicate(what, 2)
Kevin Lyda's avatar
Kevin Lyda committed
122 123 124
	if err != nil {
		return 0, err
	}
Kevin Lyda's avatar
Kevin Lyda committed
125 126 127 128 129 130 131 132
	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)
133 134 135 136
}

// GetCPS returns CPS
func (gc *GQGMCCounter) GetCPS() (uint16, error) {
Kevin Lyda's avatar
Kevin Lyda committed
137
	return gc.getReading(cmdGetCPS)
138 139
}

Kevin Lyda's avatar
Kevin Lyda committed
140
// Volts returns current battery voltage (times 10)
Kevin Lyda's avatar
Kevin Lyda committed
141
func (gc *GQGMCCounter) Volts() (int16, error) {
Kevin Lyda's avatar
Kevin Lyda committed
142 143 144 145 146 147 148
	if !gc.supportedModels(mod280n300) {
		return 0, errors.New("Unsupported model")
	}
	if gc.versionLT("Re 2.00") {
		return 0, errors.New("Unsupported version")
	}

Kevin Lyda's avatar
Kevin Lyda committed
149 150 151 152 153
	volts, err := gc.communicate(cmdGetVoltage, 1)
	if err != nil {
		return 0, err
	}
	return int16(volts[0]), err
154 155 156 157 158
}

// 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.
Kevin Lyda's avatar
Kevin Lyda committed
159
	return
160 161
}

Kevin Lyda's avatar
Kevin Lyda committed
162
// TurnOnCPS turns on CPS collection
163
func (gc *GQGMCCounter) TurnOnCPS() error {
Kevin Lyda's avatar
Kevin Lyda committed
164 165 166 167 168 169 170
	if !gc.supportedModels(mod280n300) {
		return errors.New("Unsupported model")
	}
	if gc.versionLT("Re 2.10") {
		return errors.New("Unsupported version")
	}

Kevin Lyda's avatar
Kevin Lyda committed
171
	gc.mutex.Lock()
Kevin Lyda's avatar
Kevin Lyda committed
172
	gc.sendCmd(cmdTurnOnCPS)
Kevin Lyda's avatar
Kevin Lyda committed
173
	gc.mutex.Unlock()
Kevin Lyda's avatar
Kevin Lyda committed
174
	return nil
175 176
}

Kevin Lyda's avatar
Kevin Lyda committed
177
// TurnOffCPS turns off CPS collection
178
func (gc *GQGMCCounter) TurnOffCPS() error {
Kevin Lyda's avatar
Kevin Lyda committed
179 180 181 182 183 184 185
	if !gc.supportedModels(mod280n300) {
		return errors.New("Unsupported model")
	}
	if gc.versionLT("Re 2.10") {
		return errors.New("Unsupported version")
	}

Kevin Lyda's avatar
Kevin Lyda committed
186
	gc.mutex.Lock()
Kevin Lyda's avatar
Kevin Lyda committed
187 188
	gc.sendCmd(cmdTurnOffCPS)
	gc.Clear()
Kevin Lyda's avatar
Kevin Lyda committed
189
	gc.mutex.Unlock()
Kevin Lyda's avatar
Kevin Lyda committed
190
	return nil
191 192
}

Kevin Lyda's avatar
Kevin Lyda committed
193 194
// GetAutoCPS gets a reading once auto CPS is turned on
func (gc *GQGMCCounter) GetAutoCPS() (uint16, error) {
Kevin Lyda's avatar
Kevin Lyda committed
195
	gc.mutex.Lock()
Kevin Lyda's avatar
Kevin Lyda committed
196
	buf, err := gc.readCmd(2)
Kevin Lyda's avatar
Kevin Lyda committed
197 198
	gc.mutex.Unlock()

Kevin Lyda's avatar
Kevin Lyda committed
199 200 201 202 203 204
	if err != nil {
		return 0, err
	}
	cps := ((uint16(buf[0]) << 8) & 0x3f00)
	cps |= (uint16(buf[1]) & 0x00ff)
	return cps, nil
205 206
}

Kevin Lyda's avatar
Kevin Lyda committed
207 208
// TurnOnPower turns the device on
func (gc *GQGMCCounter) TurnOnPower() {
Kevin Lyda's avatar
Kevin Lyda committed
209 210 211 212 213 214 215
	if !gc.supportedModels(mod280n300) {
		return
	}
	if gc.versionLT("Re 3.10") {
		return
	}

Kevin Lyda's avatar
Kevin Lyda committed
216
	gc.mutex.Lock()
Kevin Lyda's avatar
Kevin Lyda committed
217
	gc.sendCmd(cmdTurnOnPwr)
Kevin Lyda's avatar
Kevin Lyda committed
218
	gc.mutex.Unlock()
Kevin Lyda's avatar
Kevin Lyda committed
219 220 221
	return
}

Kevin Lyda's avatar
Kevin Lyda committed
222
// TurnOffPower turns the device off
223
func (gc *GQGMCCounter) TurnOffPower() {
Kevin Lyda's avatar
Kevin Lyda committed
224 225 226 227 228 229 230
	if !gc.supportedModels(mod280n300) {
		return
	}
	if gc.versionLT("Re 2.11") {
		return
	}

Kevin Lyda's avatar
Kevin Lyda committed
231
	gc.mutex.Lock()
Kevin Lyda's avatar
Kevin Lyda committed
232
	gc.sendCmd(cmdTurnOffPwr)
Kevin Lyda's avatar
Kevin Lyda committed
233
	gc.mutex.Unlock()
Kevin Lyda's avatar
Kevin Lyda committed
234 235 236 237
	return
}

// GetConfiguration reads configuration data
Kevin Lyda's avatar
Kevin Lyda committed
238
func (gc *GQGMCCounter) GetConfiguration() error {
Kevin Lyda's avatar
Kevin Lyda committed
239
	if !gc.supportedModels(mod280n300) {
Kevin Lyda's avatar
Kevin Lyda committed
240
		return errors.New("Unsupported Model")
Kevin Lyda's avatar
Kevin Lyda committed
241 242
	}
	if gc.versionLT("Re 2.10") {
Kevin Lyda's avatar
Kevin Lyda committed
243
		return errors.New("Unsupported version")
Kevin Lyda's avatar
Kevin Lyda committed
244 245
	}

Kevin Lyda's avatar
Kevin Lyda committed
246 247
	cfg, err := gc.communicate(cmdGetCfg, 256)
	if err != nil {
Kevin Lyda's avatar
Kevin Lyda committed
248
		return err
Kevin Lyda's avatar
Kevin Lyda committed
249
	}
Kevin Lyda's avatar
Kevin Lyda committed
250 251
	fmt.Printf("Configuration: %+v\n", cfg)
	return nil
Kevin Lyda's avatar
Kevin Lyda committed
252 253 254 255
}

// SetConfiguration writes configuration data
func (gc *GQGMCCounter) SetConfiguration() {
Kevin Lyda's avatar
Kevin Lyda committed
256 257 258 259 260 261 262
	if !gc.supportedModels(mod280n300) {
		return
	}
	if gc.versionLT("Re 2.10") {
		return
	}

Kevin Lyda's avatar
Kevin Lyda committed
263 264 265
	// See the ConfigurationData functions in gqgmc.cc
}

Kevin Lyda's avatar
Kevin Lyda committed
266 267
// SetTime sets the time
func (gc *GQGMCCounter) SetTime(t time.Time) {
Kevin Lyda's avatar
Kevin Lyda committed
268
	if !gc.supportedModels(mod280n300) {
Kevin Lyda's avatar
Kevin Lyda committed
269 270 271 272 273
		return
	}
	if gc.versionLT("Re 2.23") {
		return
	}
Kevin Lyda's avatar
Kevin Lyda committed
274 275
	gc.mutex.Lock()
	defer gc.mutex.Unlock()
Kevin Lyda's avatar
Kevin Lyda committed
276 277 278 279 280
	if gc.versionLT("Re 3.30") {
		gc.setTimeParts(t)
	}
	gc.setTimeAll(t)
}
Kevin Lyda's avatar
Kevin Lyda committed
281

Kevin Lyda's avatar
Kevin Lyda committed
282
func (gc *GQGMCCounter) setTimeParts(t time.Time) {
Kevin Lyda's avatar
Kevin Lyda committed
283 284 285 286 287
	cmd := make([]byte, 13)
	var timeCmds = []struct {
		cmd  string
		unit int
	}{
Kevin Lyda's avatar
Kevin Lyda committed
288
		{"<SETDATEYY", t.Year() - 2000},
Kevin Lyda's avatar
Kevin Lyda committed
289 290 291 292 293 294
		{"<SETDATEMM", int(t.Month())},
		{"<SETDATEDD", t.Day()},
		{"<SETTIMEHH", t.Hour()},
		{"<SETTIMEMM", t.Minute()},
		{"<SETTIMESS", t.Second()},
	}
Kevin Lyda's avatar
Kevin Lyda committed
295

Kevin Lyda's avatar
Kevin Lyda committed
296 297 298 299
	for _, c := range timeCmds {
		copy(cmd[:], c.cmd)
		cmd[10] = uint8(c.unit)
		copy(cmd[11:], ">>")
Kevin Lyda's avatar
Kevin Lyda committed
300
		// Mutex acquired in setTime()
Kevin Lyda's avatar
Kevin Lyda committed
301 302
		gc.port.Write(cmd)
		gc.readCmd(1)
Kevin Lyda's avatar
Kevin Lyda committed
303
	}
Kevin Lyda's avatar
Kevin Lyda committed
304 305
}

Kevin Lyda's avatar
Kevin Lyda committed
306
func (gc *GQGMCCounter) setTimeAll(t time.Time) {
Kevin Lyda's avatar
Kevin Lyda committed
307
	cmd := make([]byte, 20)
Kevin Lyda's avatar
Kevin Lyda committed
308
	copy(cmd[:], "<SETDATETIME")
Kevin Lyda's avatar
Kevin Lyda committed
309
	cmd[12] = uint8(t.Year() - 2000)
Kevin Lyda's avatar
Kevin Lyda committed
310 311 312 313 314 315
	cmd[13] = uint8(int(t.Month()))
	cmd[14] = uint8(t.Day())
	cmd[15] = uint8(t.Hour())
	cmd[16] = uint8(t.Minute())
	cmd[17] = uint8(t.Second())
	copy(cmd[18:], ">>")
Kevin Lyda's avatar
Kevin Lyda committed
316
	// Mutex acquired in setTime()
Kevin Lyda's avatar
Kevin Lyda committed
317 318
	gc.port.Write(cmd)
	gc.readCmd(1)
Kevin Lyda's avatar
Kevin Lyda committed
319 320
}

Kevin Lyda's avatar
Kevin Lyda committed
321 322
// GetTime gets the time
func (gc *GQGMCCounter) GetTime() (time.Time, error) {
Kevin Lyda's avatar
Kevin Lyda committed
323 324 325 326 327 328 329
	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")
	}

Kevin Lyda's avatar
Kevin Lyda committed
330 331 332 333 334
	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]),
Kevin Lyda's avatar
Kevin Lyda committed
335
		int(b[3]), int(b[4]), int(b[5]), 0, time.Local)
Kevin Lyda's avatar
Kevin Lyda committed
336 337 338
	return t, nil
}

Kevin Lyda's avatar
Kevin Lyda committed
339
// GetTemp gets the temp
Kevin Lyda's avatar
Kevin Lyda committed
340
func (gc *GQGMCCounter) GetTemp() (float64, error) {
Kevin Lyda's avatar
Kevin Lyda committed
341 342 343 344 345 346 347
	if !gc.supportedModels(mod320) {
		return 0, errors.New("Unsupported model")
	}
	if gc.versionLT("Re 3.01") {
		return 0, errors.New("Unsupported version")
	}

Kevin Lyda's avatar
Kevin Lyda committed
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
	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
}

Kevin Lyda's avatar
Kevin Lyda committed
364 365
// GetGyro gets the position in space
func (gc *GQGMCCounter) GetGyro() (int16, int16, int16, error) {
Kevin Lyda's avatar
Kevin Lyda committed
366 367 368 369 370 371 372
	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")
	}

Kevin Lyda's avatar
Kevin Lyda committed
373 374 375 376 377 378 379 380 381 382 383 384
	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
}
Kevin Lyda's avatar
Kevin Lyda committed
385

Kevin Lyda's avatar
Kevin Lyda committed
386 387
// FactoryReset does a factory reset
func (gc *GQGMCCounter) FactoryReset() {
Kevin Lyda's avatar
Kevin Lyda committed
388 389 390 391 392 393 394
	if !gc.supportedModels(mod280n300) {
		return
	}
	if gc.versionLT("Re 3.00") {
		return
	}

Kevin Lyda's avatar
Kevin Lyda committed
395
	gc.mutex.Lock()
Kevin Lyda's avatar
Kevin Lyda committed
396
	gc.sendCmd(cmdFactoryReset)
Kevin Lyda's avatar
Kevin Lyda committed
397
	gc.mutex.Unlock()
Kevin Lyda's avatar
Kevin Lyda committed
398 399 400 401 402
	return
}

// Reboot reboots the device
func (gc *GQGMCCounter) Reboot() {
Kevin Lyda's avatar
Kevin Lyda committed
403 404 405 406 407 408 409
	if !gc.supportedModels(mod280n300) {
		return
	}
	if gc.versionLT("Re 3.00") {
		return
	}

Kevin Lyda's avatar
Kevin Lyda committed
410
	gc.mutex.Lock()
Kevin Lyda's avatar
Kevin Lyda committed
411
	gc.sendCmd(cmdReboot)
Kevin Lyda's avatar
Kevin Lyda committed
412
	gc.mutex.Unlock()
Kevin Lyda's avatar
Kevin Lyda committed
413 414 415
	return
}

Kevin Lyda's avatar
Kevin Lyda committed
416 417 418 419 420 421 422 423 424 425 426 427 428
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
}

Kevin Lyda's avatar
Kevin Lyda committed
429
func (gc *GQGMCCounter) communicate(cmd string, length uint32) ([]byte, error) {
Kevin Lyda's avatar
Kevin Lyda committed
430 431
	gc.mutex.Lock()
	defer gc.mutex.Unlock()
Kevin Lyda's avatar
Kevin Lyda committed
432 433 434 435 436 437 438
	gc.Clear()
	if len(cmd) > 0 {
		gc.sendCmd(cmd)
	}
	if length != 0 {
		return gc.readCmd(length)
	}
Kevin Lyda's avatar
Kevin Lyda committed
439 440 441
	return nil, nil
}

Kevin Lyda's avatar
Kevin Lyda committed
442 443
func (gc *GQGMCCounter) sendCmd(cmd string) {
	gc.port.Write([]byte(cmd))
Kevin Lyda's avatar
Kevin Lyda committed
444 445 446
	return
}

Kevin Lyda's avatar
Kevin Lyda committed
447
func (gc *GQGMCCounter) readCmd(length uint32) ([]byte, error) {
Kevin Lyda's avatar
Kevin Lyda committed
448 449 450 451 452 453
	buf := make([]byte, length)
	n, err := gc.port.Read(buf)
	if err != nil {
		return nil, err
	}
	if uint32(n) != length {
Kevin Lyda's avatar
Kevin Lyda committed
454
		return nil, fmt.Errorf("Short read (got: %d, wanted: %d)", uint32(n), length)
Kevin Lyda's avatar
Kevin Lyda committed
455 456
	}
	return buf, nil
457
}