gqgmc.go 8.26 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
	cmdGetSerial  = "<GETSERIAL>>"
	cmdGetVersion = "<GETVER>>"
	cmdGetVoltage = "<GETVOLT>>"
Kevin Lyda's avatar
Kevin Lyda committed
109
110
	cmdGetCPM     = "<GETCPM>>"
	cmdGetCPS     = "<GETCPS>>"
Kevin Lyda's avatar
Kevin Lyda committed
111
112
113
	cmdGetCfg     = "<GETCFG>>"
	cmdEraseCfg   = "<ECFG>>"
	cmdUpdateCfg  = "<CFGUPDATE>>"
Kevin Lyda's avatar
Kevin Lyda committed
114
115
	cmdTurnOnCPS  = "<HEARTBEAT1>>"
	cmdTurnOffCPS = "<HEARTBEAT0>>"
Kevin Lyda's avatar
Kevin Lyda committed
116
	cmdTurnOffPwr = "<POWEROFF>>"
117
118
)

119
120
// GQGMCCounter is a GQ GMC Counter
type GQGMCCounter struct {
Kevin Lyda's avatar
Kevin Lyda committed
121
122
	port   *serial.Port
	config *serial.Config
123
124
125
126
}

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

// Clear clears out any remaining data
func (gc *GQGMCCounter) Clear() error {
	// Read up to 10 chars until nothing comes back.
	// error otherwise.
	return nil
}

// Version gets the version of the device
func (gc *GQGMCCounter) Version() (string, error) {
Kevin Lyda's avatar
Kevin Lyda committed
149
150
	ver, err := gc.communicate(cmdGetVersion, 14)
	return string(ver), err
151
152
153
154
}

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

	ser, err := gc.communicate(cmdGetSerial, 7)
Kevin Lyda's avatar
Kevin Lyda committed
158
	if err == nil {
Kevin Lyda's avatar
Kevin Lyda committed
159
160
161
		for _, b := range ser {
			serStr += fmt.Sprintf("%02X", b)
		}
Kevin Lyda's avatar
Kevin Lyda committed
162
	}
Kevin Lyda's avatar
Kevin Lyda committed
163
	return serStr, err
164
165
}

Kevin Lyda's avatar
Kevin Lyda committed
166
func (gc *GQGMCCounter) getReading(what string) (uint16, error) {
Kevin Lyda's avatar
Kevin Lyda committed
167
	buf, err := gc.communicate(what, 2)
Kevin Lyda's avatar
Kevin Lyda committed
168
169
170
	if err != nil {
		return 0, err
	}
Kevin Lyda's avatar
Kevin Lyda committed
171
172
173
174
175
176
177
178
	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)
179
180
181
182
}

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

Kevin Lyda's avatar
Kevin Lyda committed
186
187
// Volts returns current battery voltage
func (gc *GQGMCCounter) Volts() (int16, error) {
188
189
190
191
192
193
194
195
196
	// 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
197
198
199
200
201
	volts, err := gc.communicate(cmdGetVoltage, 1)
	if err != nil {
		return 0, err
	}
	return int16(volts[0]), err
202
203
204
205
206
}

// 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
207
	return
208
209
}

Kevin Lyda's avatar
Kevin Lyda committed
210
// TurnOnCPS turns on CPS collection
211
func (gc *GQGMCCounter) TurnOnCPS() error {
Kevin Lyda's avatar
Kevin Lyda committed
212
213
214
215
216
217
218
219
220
221
222
223
224
	// 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
225
	gc.sendCmd(cmdTurnOnCPS)
Kevin Lyda's avatar
Kevin Lyda committed
226
	return nil
227
228
}

Kevin Lyda's avatar
Kevin Lyda committed
229
// TurnOffCPS turns off CPS collection
230
func (gc *GQGMCCounter) TurnOffCPS() error {
Kevin Lyda's avatar
Kevin Lyda committed
231
232
	gc.sendCmd(cmdTurnOffCPS)
	gc.Clear()
Kevin Lyda's avatar
Kevin Lyda committed
233
	return nil
234
235
}

Kevin Lyda's avatar
Kevin Lyda committed
236
237
// GetAutoCPS gets a reading once auto CPS is turned on
func (gc *GQGMCCounter) GetAutoCPS() (uint16, error) {
Kevin Lyda's avatar
Kevin Lyda committed
238
239
240
241
242
243
244
	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
245
246
}

Kevin Lyda's avatar
Kevin Lyda committed
247
// TurnOffPower turns the device off
248
func (gc *GQGMCCounter) TurnOffPower() {
Kevin Lyda's avatar
Kevin Lyda committed
249
	gc.sendCmd(cmdTurnOffPwr)
Kevin Lyda's avatar
Kevin Lyda committed
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
	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
271
272
273
274
275
276
277
278
279
280
281
282
283
284
// SetTime sets the time
func (gc *GQGMCCounter) SetTime(t time.Time) {
	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
285

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

Kevin Lyda's avatar
Kevin Lyda committed
295
func (gc *GQGMCCounter) communicate(cmd string, length uint32) ([]byte, error) {
Kevin Lyda's avatar
Kevin Lyda committed
296
297
298
299
300
301
302
	gc.Clear()
	if len(cmd) > 0 {
		gc.sendCmd(cmd)
	}
	if length != 0 {
		return gc.readCmd(length)
	}
Kevin Lyda's avatar
Kevin Lyda committed
303
304
305
	return nil, nil
}

Kevin Lyda's avatar
Kevin Lyda committed
306
307
func (gc *GQGMCCounter) sendCmd(cmd string) {
	gc.port.Write([]byte(cmd))
Kevin Lyda's avatar
Kevin Lyda committed
308
309
310
	return
}

Kevin Lyda's avatar
Kevin Lyda committed
311
func (gc *GQGMCCounter) readCmd(length uint32) ([]byte, error) {
Kevin Lyda's avatar
Kevin Lyda committed
312
313
314
315
316
317
318
319
320
	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
321
}