gqgmc.go 9.39 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"
13
14
15
16
17
	"time"

	"github.com/tarm/serial"
)

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

24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
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
Kevin Lyda's avatar
Kevin Lyda committed
101
102
	forFirmware        = 2.23
	nvmSize            = 256
103
104
105
)

const (
Kevin Lyda's avatar
Kevin Lyda committed
106
107
108
109
110
111
112
113
114
115
116
117
118
119
	cmdGetSerial    = "<GETSERIAL>>"
	cmdGetVersion   = "<GETVER>>"
	cmdGetVoltage   = "<GETVOLT>>"
	cmdGetCPM       = "<GETCPM>>"
	cmdGetCPS       = "<GETCPS>>"
	cmdGetCfg       = "<GETCFG>>"
	cmdEraseCfg     = "<ECFG>>"
	cmdUpdateCfg    = "<CFGUPDATE>>"
	cmdTurnOnCPS    = "<HEARTBEAT1>>"
	cmdTurnOffCPS   = "<HEARTBEAT0>>"
	cmdTurnOffPwr   = "<POWEROFF>>"
	cmdFactoryReset = "<FACTORYRESET>>"
	cmdReboot       = "<REBOOT>>"
	cmdGetTime      = "<GETDATETIME>>"
120
121
)

122
123
// GQGMCCounter is a GQ GMC Counter
type GQGMCCounter struct {
Kevin Lyda's avatar
Kevin Lyda committed
124
125
	port   *serial.Port
	config *serial.Config
Kevin Lyda's avatar
Kevin Lyda committed
126
127
128
	version,
	shortVer,
	model string
129
130
131
132
}

// NewGQGMC creates a new GQGMC Counter instance
func NewGQGMC(c Config) (*GQGMCCounter, error) {
Kevin Lyda's avatar
Kevin Lyda committed
133
134
135
136
	var gc GQGMCCounter
	var v []byte

	gc.config = &serial.Config{
Kevin Lyda's avatar
Kevin Lyda committed
137
138
		Name:        c.Device,
		Baud:        57600,
139
140
		ReadTimeout: 500 * time.Millisecond,
	}
Kevin Lyda's avatar
Kevin Lyda committed
141
	p, err := serial.OpenPort(gc.config)
Kevin Lyda's avatar
Kevin Lyda committed
142
143
144
	if err != nil {
		return nil, err
	}
Kevin Lyda's avatar
Kevin Lyda committed
145
146
	gc.port = p
	v, err = gc.communicate(cmdGetVersion, 14)
Kevin Lyda's avatar
Kevin Lyda committed
147
	gc.model = string(v[:7])
Kevin Lyda's avatar
Kevin Lyda committed
148
149
	gc.version = string(v[7:])
	gc.shortVer = string(v[10:])
Kevin Lyda's avatar
Kevin Lyda committed
150
	//getConfigurationData()
Kevin Lyda's avatar
Kevin Lyda committed
151
	return &gc, nil
152
153
154
155
156
157
}

// 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
158
159
160
161
162
	for i := 0; i < 10; i++ {
		if _, err := gc.readCmd(1); err != nil {
			break
		}
	}
163
164
165
166
	return nil
}

// Version gets the version of the device
Kevin Lyda's avatar
Kevin Lyda committed
167
168
169
170
171
172
173
174
175
176
177
178
func (gc *GQGMCCounter) Version() string {
	return gc.version
}

// Ver gets the short version of the device
func (gc *GQGMCCounter) Ver() string {
	return gc.shortVer
}

// Model gets the model of the device
func (gc *GQGMCCounter) Model() string {
	return gc.model
179
180
181
182
}

// SerialNum gets the serial number of the device
func (gc *GQGMCCounter) SerialNum() (string, error) {
Kevin Lyda's avatar
Kevin Lyda committed
183
184
185
	serStr := ""

	ser, err := gc.communicate(cmdGetSerial, 7)
Kevin Lyda's avatar
Kevin Lyda committed
186
	if err == nil {
Kevin Lyda's avatar
Kevin Lyda committed
187
188
189
		for _, b := range ser {
			serStr += fmt.Sprintf("%02X", b)
		}
Kevin Lyda's avatar
Kevin Lyda committed
190
	}
Kevin Lyda's avatar
Kevin Lyda committed
191
	return serStr, err
192
193
}

Kevin Lyda's avatar
Kevin Lyda committed
194
func (gc *GQGMCCounter) getReading(what string) (uint16, error) {
Kevin Lyda's avatar
Kevin Lyda committed
195
	buf, err := gc.communicate(what, 2)
Kevin Lyda's avatar
Kevin Lyda committed
196
197
198
	if err != nil {
		return 0, err
	}
Kevin Lyda's avatar
Kevin Lyda committed
199
200
201
202
203
204
205
206
	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)
207
208
209
210
}

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

Kevin Lyda's avatar
Kevin Lyda committed
214
215
// Volts returns current battery voltage
func (gc *GQGMCCounter) Volts() (int16, error) {
216
217
218
219
220
221
222
223
224
	// 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.
Kevin Lyda's avatar
Kevin Lyda committed
225
226
227
228
229
	volts, err := gc.communicate(cmdGetVoltage, 1)
	if err != nil {
		return 0, err
	}
	return int16(volts[0]), err
230
231
232
233
234
}

// 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
235
	return
236
237
}

Kevin Lyda's avatar
Kevin Lyda committed
238
// TurnOnCPS turns on CPS collection
239
func (gc *GQGMCCounter) TurnOnCPS() error {
Kevin Lyda's avatar
Kevin Lyda committed
240
241
242
243
244
245
246
247
248
249
250
251
252
	// 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).
Kevin Lyda's avatar
Kevin Lyda committed
253
	gc.sendCmd(cmdTurnOnCPS)
Kevin Lyda's avatar
Kevin Lyda committed
254
	return nil
255
256
}

Kevin Lyda's avatar
Kevin Lyda committed
257
// TurnOffCPS turns off CPS collection
258
func (gc *GQGMCCounter) TurnOffCPS() error {
Kevin Lyda's avatar
Kevin Lyda committed
259
260
	gc.sendCmd(cmdTurnOffCPS)
	gc.Clear()
Kevin Lyda's avatar
Kevin Lyda committed
261
	return nil
262
263
}

Kevin Lyda's avatar
Kevin Lyda committed
264
265
// GetAutoCPS gets a reading once auto CPS is turned on
func (gc *GQGMCCounter) GetAutoCPS() (uint16, error) {
Kevin Lyda's avatar
Kevin Lyda committed
266
267
268
269
270
271
272
	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
273
274
}

Kevin Lyda's avatar
Kevin Lyda committed
275
// TurnOffPower turns the device off
276
func (gc *GQGMCCounter) TurnOffPower() {
Kevin Lyda's avatar
Kevin Lyda committed
277
	gc.sendCmd(cmdTurnOffPwr)
Kevin Lyda's avatar
Kevin Lyda committed
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
	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);
}

Kevin Lyda's avatar
Kevin Lyda committed
299
300
// SetTime sets the time
func (gc *GQGMCCounter) SetTime(t time.Time) {
Kevin Lyda's avatar
Kevin Lyda committed
301
302
303
	//command: <SETDATETIME[YYMMDDHHMMSS]>>
	//Firmware supported: GMC-280, GMC-300 Re.3.00 or later

Kevin Lyda's avatar
Kevin Lyda committed
304
305
306
307
308
309
310
311
312
313
314
315
	cmd := make([]byte, 13)
	var timeCmds = []struct {
		cmd  string
		unit int
	}{
		{"<SETDATEYY", t.Year()},
		{"<SETDATEMM", int(t.Month())},
		{"<SETDATEDD", t.Day()},
		{"<SETTIMEHH", t.Hour()},
		{"<SETTIMEMM", t.Minute()},
		{"<SETTIMESS", t.Second()},
	}
Kevin Lyda's avatar
Kevin Lyda committed
316

Kevin Lyda's avatar
Kevin Lyda committed
317
318
319
320
321
322
323
	for _, c := range timeCmds {
		copy(cmd[:], c.cmd)
		cmd[10] = uint8(c.unit)
		copy(cmd[11:], ">>")
		gc.port.Write(cmd)
		gc.readCmd(1)
	}
Kevin Lyda's avatar
Kevin Lyda committed
324
325
}

Kevin Lyda's avatar
Kevin Lyda committed
326
327
328
329
330
331
332
// GetTime gets the time
func (gc *GQGMCCounter) GetTime() (time.Time, error) {
	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
333
		int(b[3]), int(b[4]), int(b[5]), 0, time.Local)
Kevin Lyda's avatar
Kevin Lyda committed
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
	return t, nil
}

// FactoryReset does a factory reset
func (gc *GQGMCCounter) FactoryReset() {
	gc.sendCmd(cmdFactoryReset)
	return
}

// Reboot reboots the device
func (gc *GQGMCCounter) Reboot() {
	gc.sendCmd(cmdReboot)
	return
}

Kevin Lyda's avatar
Kevin Lyda committed
349
func (gc *GQGMCCounter) communicate(cmd string, length uint32) ([]byte, error) {
Kevin Lyda's avatar
Kevin Lyda committed
350
351
352
353
354
355
356
	gc.Clear()
	if len(cmd) > 0 {
		gc.sendCmd(cmd)
	}
	if length != 0 {
		return gc.readCmd(length)
	}
Kevin Lyda's avatar
Kevin Lyda committed
357
358
359
	return nil, nil
}

Kevin Lyda's avatar
Kevin Lyda committed
360
361
func (gc *GQGMCCounter) sendCmd(cmd string) {
	gc.port.Write([]byte(cmd))
Kevin Lyda's avatar
Kevin Lyda committed
362
363
364
	return
}

Kevin Lyda's avatar
Kevin Lyda committed
365
func (gc *GQGMCCounter) readCmd(length uint32) ([]byte, error) {
Kevin Lyda's avatar
Kevin Lyda committed
366
367
368
369
370
371
372
373
374
	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
375
}