gqgmc.go 9.06 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
126
127
128
129
}

// NewGQGMC creates a new GQGMC Counter instance
func NewGQGMC(c Config) (*GQGMCCounter, error) {
Kevin Lyda's avatar
Kevin Lyda committed
130
131
132
	cfg := serial.Config{
		Name:        c.Device,
		Baud:        57600,
133
134
		ReadTimeout: 500 * time.Millisecond,
	}
Kevin Lyda's avatar
Kevin Lyda committed
135
136
137
138
139
140
	p, err := serial.OpenPort(&cfg)
	if err != nil {
		return nil, err
	}
	//getConfigurationData()
	return &GQGMCCounter{port: p, config: &cfg}, nil
141
142
143
144
145
146
}

// 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
147
148
149
150
151
	for i := 0; i < 10; i++ {
		if _, err := gc.readCmd(1); err != nil {
			break
		}
	}
152
153
154
155
156
	return nil
}

// Version gets the version of the device
func (gc *GQGMCCounter) Version() (string, error) {
Kevin Lyda's avatar
Kevin Lyda committed
157
158
	ver, err := gc.communicate(cmdGetVersion, 14)
	return string(ver), err
159
160
161
162
}

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

	ser, err := gc.communicate(cmdGetSerial, 7)
Kevin Lyda's avatar
Kevin Lyda committed
166
	if err == nil {
Kevin Lyda's avatar
Kevin Lyda committed
167
168
169
		for _, b := range ser {
			serStr += fmt.Sprintf("%02X", b)
		}
Kevin Lyda's avatar
Kevin Lyda committed
170
	}
Kevin Lyda's avatar
Kevin Lyda committed
171
	return serStr, err
172
173
}

Kevin Lyda's avatar
Kevin Lyda committed
174
func (gc *GQGMCCounter) getReading(what string) (uint16, error) {
Kevin Lyda's avatar
Kevin Lyda committed
175
	buf, err := gc.communicate(what, 2)
Kevin Lyda's avatar
Kevin Lyda committed
176
177
178
	if err != nil {
		return 0, err
	}
Kevin Lyda's avatar
Kevin Lyda committed
179
180
181
182
183
184
185
186
	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)
187
188
189
190
}

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

Kevin Lyda's avatar
Kevin Lyda committed
194
195
// Volts returns current battery voltage
func (gc *GQGMCCounter) Volts() (int16, error) {
196
197
198
199
200
201
202
203
204
	// 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
205
206
207
208
209
	volts, err := gc.communicate(cmdGetVoltage, 1)
	if err != nil {
		return 0, err
	}
	return int16(volts[0]), err
210
211
212
213
214
}

// 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
215
	return
216
217
}

Kevin Lyda's avatar
Kevin Lyda committed
218
// TurnOnCPS turns on CPS collection
219
func (gc *GQGMCCounter) TurnOnCPS() error {
Kevin Lyda's avatar
Kevin Lyda committed
220
221
222
223
224
225
226
227
228
229
230
231
232
	// 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
233
	gc.sendCmd(cmdTurnOnCPS)
Kevin Lyda's avatar
Kevin Lyda committed
234
	return nil
235
236
}

Kevin Lyda's avatar
Kevin Lyda committed
237
// TurnOffCPS turns off CPS collection
238
func (gc *GQGMCCounter) TurnOffCPS() error {
Kevin Lyda's avatar
Kevin Lyda committed
239
240
	gc.sendCmd(cmdTurnOffCPS)
	gc.Clear()
Kevin Lyda's avatar
Kevin Lyda committed
241
	return nil
242
243
}

Kevin Lyda's avatar
Kevin Lyda committed
244
245
// GetAutoCPS gets a reading once auto CPS is turned on
func (gc *GQGMCCounter) GetAutoCPS() (uint16, error) {
Kevin Lyda's avatar
Kevin Lyda committed
246
247
248
249
250
251
252
	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
253
254
}

Kevin Lyda's avatar
Kevin Lyda committed
255
// TurnOffPower turns the device off
256
func (gc *GQGMCCounter) TurnOffPower() {
Kevin Lyda's avatar
Kevin Lyda committed
257
	gc.sendCmd(cmdTurnOffPwr)
Kevin Lyda's avatar
Kevin Lyda committed
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
	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
279
280
// SetTime sets the time
func (gc *GQGMCCounter) SetTime(t time.Time) {
Kevin Lyda's avatar
Kevin Lyda committed
281
282
283
	//command: <SETDATETIME[YYMMDDHHMMSS]>>
	//Firmware supported: GMC-280, GMC-300 Re.3.00 or later

Kevin Lyda's avatar
Kevin Lyda committed
284
285
286
287
288
289
290
291
292
293
294
295
	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
296

Kevin Lyda's avatar
Kevin Lyda committed
297
298
299
300
301
302
303
	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
304
305
}

Kevin Lyda's avatar
Kevin Lyda committed
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
// 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]),
		int(b[3]), int(b[4]), int(b[5]), 0, nil)
	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
329
func (gc *GQGMCCounter) communicate(cmd string, length uint32) ([]byte, error) {
Kevin Lyda's avatar
Kevin Lyda committed
330
331
332
333
334
335
336
	gc.Clear()
	if len(cmd) > 0 {
		gc.sendCmd(cmd)
	}
	if length != 0 {
		return gc.readCmd(length)
	}
Kevin Lyda's avatar
Kevin Lyda committed
337
338
339
	return nil, nil
}

Kevin Lyda's avatar
Kevin Lyda committed
340
341
func (gc *GQGMCCounter) sendCmd(cmd string) {
	gc.port.Write([]byte(cmd))
Kevin Lyda's avatar
Kevin Lyda committed
342
343
344
	return
}

Kevin Lyda's avatar
Kevin Lyda committed
345
func (gc *GQGMCCounter) readCmd(length uint32) ([]byte, error) {
Kevin Lyda's avatar
Kevin Lyda committed
346
347
348
349
350
351
352
353
354
	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
355
}