gqgmc.go 9.4 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
	"errors"
Kevin Lyda's avatar
Kevin Lyda committed
12
	"fmt"
Kevin Lyda's avatar
Kevin Lyda committed
13
	"strconv"
14
15
16
17
18
	"time"

	"github.com/tarm/serial"
)

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

25
const (
Kevin Lyda's avatar
Kevin Lyda committed
26
27
28
29
30
31
32
33
	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
34
	cmdTurnOnCPS    = "<HEARTBEAT1>>"   // GMC-280, GMC-300 Re.2.10
Kevin Lyda's avatar
Kevin Lyda committed
35
36
	cmdTurnOffCPS   = "<HEARTBEAT0>>"   // Re.2.10
	cmdTurnOffPwr   = "<POWEROFF>>"     // GMC-280, GMC-300 Re.2.11
Kevin Lyda's avatar
Kevin Lyda committed
37
	cmdTurnOnPwr    = "<POWERON>>"      // GMC-280, GMC-300 Re.3.10
Kevin Lyda's avatar
Kevin Lyda committed
38
39
40
41
42
	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
43
44
)

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

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

// NewGQGMC creates a new GQGMC Counter instance
func NewGQGMC(c Config) (*GQGMCCounter, error) {
Kevin Lyda's avatar
Kevin Lyda committed
60
61
62
63
	var gc GQGMCCounter
	var v []byte

	gc.config = &serial.Config{
Kevin Lyda's avatar
Kevin Lyda committed
64
65
		Name:        c.Device,
		Baud:        57600,
66
67
		ReadTimeout: 500 * time.Millisecond,
	}
Kevin Lyda's avatar
Kevin Lyda committed
68
	p, err := serial.OpenPort(gc.config)
Kevin Lyda's avatar
Kevin Lyda committed
69
70
71
	if err != nil {
		return nil, err
	}
Kevin Lyda's avatar
Kevin Lyda committed
72
73
	gc.port = p
	v, err = gc.communicate(cmdGetVersion, 14)
Kevin Lyda's avatar
Kevin Lyda committed
74
	gc.model = string(v[:7])
Kevin Lyda's avatar
Kevin Lyda committed
75
	gc.version = string(v[7:])
Kevin Lyda's avatar
Kevin Lyda committed
76
	//getConfigurationData()
Kevin Lyda's avatar
Kevin Lyda committed
77
	return &gc, nil
78
79
80
81
82
83
}

// 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
84
85
86
87
88
	for i := 0; i < 10; i++ {
		if _, err := gc.readCmd(1); err != nil {
			break
		}
	}
89
90
91
92
	return nil
}

// Version gets the version of the device
Kevin Lyda's avatar
Kevin Lyda committed
93
94
95
96
97
98
99
func (gc *GQGMCCounter) Version() string {
	return gc.version
}

// Model gets the model of the device
func (gc *GQGMCCounter) Model() string {
	return gc.model
100
101
102
103
}

// SerialNum gets the serial number of the device
func (gc *GQGMCCounter) SerialNum() (string, error) {
Kevin Lyda's avatar
Kevin Lyda committed
104
105
106
107
108
109
	if !gc.supportedModels(mod280n300) {
		return "", errors.New("Unsupported model")
	}
	if gc.versionLT("Re 2.11") {
		return "", errors.New("Unsupported version")
	}
Kevin Lyda's avatar
Kevin Lyda committed
110

Kevin Lyda's avatar
Kevin Lyda committed
111
	serStr := ""
Kevin Lyda's avatar
Kevin Lyda committed
112
	ser, err := gc.communicate(cmdGetSerial, 7)
Kevin Lyda's avatar
Kevin Lyda committed
113
	if err == nil {
Kevin Lyda's avatar
Kevin Lyda committed
114
115
116
		for _, b := range ser {
			serStr += fmt.Sprintf("%02X", b)
		}
Kevin Lyda's avatar
Kevin Lyda committed
117
	}
Kevin Lyda's avatar
Kevin Lyda committed
118
	return serStr, err
119
120
}

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

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

Kevin Lyda's avatar
Kevin Lyda committed
141
// Volts returns current battery voltage (times 10)
Kevin Lyda's avatar
Kevin Lyda committed
142
func (gc *GQGMCCounter) Volts() (int16, error) {
Kevin Lyda's avatar
Kevin Lyda committed
143
144
145
146
147
148
149
	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
150
151
152
153
154
	volts, err := gc.communicate(cmdGetVoltage, 1)
	if err != nil {
		return 0, err
	}
	return int16(volts[0]), err
155
156
157
158
159
}

// 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
160
	return
161
162
}

Kevin Lyda's avatar
Kevin Lyda committed
163
// TurnOnCPS turns on CPS collection
164
func (gc *GQGMCCounter) TurnOnCPS() error {
Kevin Lyda's avatar
Kevin Lyda committed
165
166
167
168
169
170
171
	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
172
	gc.sendCmd(cmdTurnOnCPS)
Kevin Lyda's avatar
Kevin Lyda committed
173
	return nil
174
175
}

Kevin Lyda's avatar
Kevin Lyda committed
176
// TurnOffCPS turns off CPS collection
177
func (gc *GQGMCCounter) TurnOffCPS() error {
Kevin Lyda's avatar
Kevin Lyda committed
178
179
180
181
182
183
184
	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
185
186
	gc.sendCmd(cmdTurnOffCPS)
	gc.Clear()
Kevin Lyda's avatar
Kevin Lyda committed
187
	return nil
188
189
}

Kevin Lyda's avatar
Kevin Lyda committed
190
191
// GetAutoCPS gets a reading once auto CPS is turned on
func (gc *GQGMCCounter) GetAutoCPS() (uint16, error) {
Kevin Lyda's avatar
Kevin Lyda committed
192
193
194
195
196
197
198
	buf, err := gc.readCmd(2)
	if err != nil {
		return 0, err
	}
	cps := ((uint16(buf[0]) << 8) & 0x3f00)
	cps |= (uint16(buf[1]) & 0x00ff)
	return cps, nil
199
200
}

Kevin Lyda's avatar
Kevin Lyda committed
201
202
// TurnOnPower turns the device on
func (gc *GQGMCCounter) TurnOnPower() {
Kevin Lyda's avatar
Kevin Lyda committed
203
204
205
206
207
208
209
	if !gc.supportedModels(mod280n300) {
		return
	}
	if gc.versionLT("Re 3.10") {
		return
	}

Kevin Lyda's avatar
Kevin Lyda committed
210
211
212
213
	gc.sendCmd(cmdTurnOnPwr)
	return
}

Kevin Lyda's avatar
Kevin Lyda committed
214
// TurnOffPower turns the device off
215
func (gc *GQGMCCounter) TurnOffPower() {
Kevin Lyda's avatar
Kevin Lyda committed
216
217
218
219
220
221
222
	if !gc.supportedModels(mod280n300) {
		return
	}
	if gc.versionLT("Re 2.11") {
		return
	}

Kevin Lyda's avatar
Kevin Lyda committed
223
	gc.sendCmd(cmdTurnOffPwr)
Kevin Lyda's avatar
Kevin Lyda committed
224
225
226
227
228
	return
}

// GetConfiguration reads configuration data
func (gc *GQGMCCounter) GetConfiguration() {
Kevin Lyda's avatar
Kevin Lyda committed
229
230
231
232
233
234
235
	if !gc.supportedModels(mod280n300) {
		return
	}
	if gc.versionLT("Re 2.10") {
		return
	}

Kevin Lyda's avatar
Kevin Lyda committed
236
237
238
239
240
	cfg, err := gc.communicate(cmdGetCfg, 256)
	if err != nil {
		return
	}
	fmt.Printf("%+v\n", cfg)
Kevin Lyda's avatar
Kevin Lyda committed
241
242
243
244
}

// SetConfiguration writes configuration data
func (gc *GQGMCCounter) SetConfiguration() {
Kevin Lyda's avatar
Kevin Lyda committed
245
246
247
248
249
250
251
	if !gc.supportedModels(mod280n300) {
		return
	}
	if gc.versionLT("Re 2.10") {
		return
	}

Kevin Lyda's avatar
Kevin Lyda committed
252
253
254
	// See the ConfigurationData functions in gqgmc.cc
}

Kevin Lyda's avatar
Kevin Lyda committed
255
256
// SetTime sets the time
func (gc *GQGMCCounter) SetTime(t time.Time) {
Kevin Lyda's avatar
Kevin Lyda committed
257
	if !gc.supportedModels(mod280n300) {
Kevin Lyda's avatar
Kevin Lyda committed
258
259
260
261
262
263
264
265
266
267
		return
	}
	if gc.versionLT("Re 2.23") {
		return
	}
	if gc.versionLT("Re 3.30") {
		gc.setTimeParts(t)
	}
	gc.setTimeAll(t)
}
Kevin Lyda's avatar
Kevin Lyda committed
268

Kevin Lyda's avatar
Kevin Lyda committed
269
func (gc *GQGMCCounter) setTimeParts(t time.Time) {
Kevin Lyda's avatar
Kevin Lyda committed
270
271
272
273
274
	cmd := make([]byte, 13)
	var timeCmds = []struct {
		cmd  string
		unit int
	}{
Kevin Lyda's avatar
Kevin Lyda committed
275
		{"<SETDATEYY", t.Year() - 2000},
Kevin Lyda's avatar
Kevin Lyda committed
276
277
278
279
280
281
		{"<SETDATEMM", int(t.Month())},
		{"<SETDATEDD", t.Day()},
		{"<SETTIMEHH", t.Hour()},
		{"<SETTIMEMM", t.Minute()},
		{"<SETTIMESS", t.Second()},
	}
Kevin Lyda's avatar
Kevin Lyda committed
282

Kevin Lyda's avatar
Kevin Lyda committed
283
284
285
286
	for _, c := range timeCmds {
		copy(cmd[:], c.cmd)
		cmd[10] = uint8(c.unit)
		copy(cmd[11:], ">>")
Kevin Lyda's avatar
Kevin Lyda committed
287
288
		gc.port.Write(cmd)
		gc.readCmd(1)
Kevin Lyda's avatar
Kevin Lyda committed
289
	}
Kevin Lyda's avatar
Kevin Lyda committed
290
291
}

Kevin Lyda's avatar
Kevin Lyda committed
292
func (gc *GQGMCCounter) setTimeAll(t time.Time) {
Kevin Lyda's avatar
Kevin Lyda committed
293
	cmd := make([]byte, 20)
Kevin Lyda's avatar
Kevin Lyda committed
294
	copy(cmd[:], "<SETDATETIME")
Kevin Lyda's avatar
Kevin Lyda committed
295
	cmd[12] = uint8(t.Year() - 2000)
Kevin Lyda's avatar
Kevin Lyda committed
296
297
298
299
300
301
	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
302
303
	gc.port.Write(cmd)
	gc.readCmd(1)
Kevin Lyda's avatar
Kevin Lyda committed
304
305
}

Kevin Lyda's avatar
Kevin Lyda committed
306
307
// GetTime gets the time
func (gc *GQGMCCounter) GetTime() (time.Time, error) {
Kevin Lyda's avatar
Kevin Lyda committed
308
309
310
311
312
313
314
	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
315
316
317
318
319
	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
320
		int(b[3]), int(b[4]), int(b[5]), 0, time.Local)
Kevin Lyda's avatar
Kevin Lyda committed
321
322
323
	return t, nil
}

Kevin Lyda's avatar
Kevin Lyda committed
324
// GetTemp gets the temp
Kevin Lyda's avatar
Kevin Lyda committed
325
func (gc *GQGMCCounter) GetTemp() (float64, error) {
Kevin Lyda's avatar
Kevin Lyda committed
326
327
328
329
330
331
332
	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
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
	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
349
350
// GetGyro gets the position in space
func (gc *GQGMCCounter) GetGyro() (int16, int16, int16, error) {
Kevin Lyda's avatar
Kevin Lyda committed
351
352
353
354
355
356
357
	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
358
359
360
361
362
363
364
365
366
367
368
369
	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
370

Kevin Lyda's avatar
Kevin Lyda committed
371
372
// FactoryReset does a factory reset
func (gc *GQGMCCounter) FactoryReset() {
Kevin Lyda's avatar
Kevin Lyda committed
373
374
375
376
377
378
379
	if !gc.supportedModels(mod280n300) {
		return
	}
	if gc.versionLT("Re 3.00") {
		return
	}

Kevin Lyda's avatar
Kevin Lyda committed
380
381
382
383
384
385
	gc.sendCmd(cmdFactoryReset)
	return
}

// Reboot reboots the device
func (gc *GQGMCCounter) Reboot() {
Kevin Lyda's avatar
Kevin Lyda committed
386
387
388
389
390
391
392
	if !gc.supportedModels(mod280n300) {
		return
	}
	if gc.versionLT("Re 3.00") {
		return
	}

Kevin Lyda's avatar
Kevin Lyda committed
393
394
395
396
	gc.sendCmd(cmdReboot)
	return
}

Kevin Lyda's avatar
Kevin Lyda committed
397
398
399
400
401
402
403
404
405
406
407
408
409
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
410
func (gc *GQGMCCounter) communicate(cmd string, length uint32) ([]byte, error) {
Kevin Lyda's avatar
Kevin Lyda committed
411
412
413
414
415
416
417
	gc.Clear()
	if len(cmd) > 0 {
		gc.sendCmd(cmd)
	}
	if length != 0 {
		return gc.readCmd(length)
	}
Kevin Lyda's avatar
Kevin Lyda committed
418
419
420
	return nil, nil
}

Kevin Lyda's avatar
Kevin Lyda committed
421
422
func (gc *GQGMCCounter) sendCmd(cmd string) {
	gc.port.Write([]byte(cmd))
Kevin Lyda's avatar
Kevin Lyda committed
423
424
425
	return
}

Kevin Lyda's avatar
Kevin Lyda committed
426
func (gc *GQGMCCounter) readCmd(length uint32) ([]byte, error) {
Kevin Lyda's avatar
Kevin Lyda committed
427
428
429
430
431
432
433
434
435
	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
436
}