-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathinherent.go
More file actions
288 lines (237 loc) · 9.23 KB
/
Copy pathinherent.go
File metadata and controls
288 lines (237 loc) · 9.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
// Copyright (c) 2017-2026 The ivi developers. All rights reserved.
// Project site: https://github.com/gotmc/ivi
// Use of this source code is governed by a MIT-style license that
// can be found in the LICENSE.txt file for the project.
package ivi
import (
"context"
"fmt"
"slices"
"strings"
"time"
"github.com/gotmc/query"
)
type idPart int
const (
mfrID idPart = iota
modelID
snID
fwrID
)
// Inherent provides the inherent capabilities for all IVI instruments.
type Inherent struct {
inst Transport
timeout time.Duration
manufacturer string
model string
serialNumber string
firmwareRevision string
InherentBase
}
// InherentBase provides the exported properties for the inherent capabilities
// of all IVI instruments.
type InherentBase struct {
ClassSpecRevision string
IDNString string
LocalControlCommand string // SCPI command to return to local control (default: "SYST:LOC")
GroupCapabilities []string
SupportedInstrumentModels []string
SupportedBusInterfaces []string
ClassSpecMajorVersion int
ClassSpecMinorVersion int
ResetDelay time.Duration
ClearDelay time.Duration
ReturnToLocal bool // Whether to return to local control on Close/Disable
}
// NewInherent creates a new Inherent struct using the given Transport
// interface, InherentBase struct, and timeout. If timeout is zero,
// DefaultTimeout is used. Note: callers should explicitly set ReturnToLocal in
// InherentBase (default Go zero value is false).
func NewInherent(inst Transport, base InherentBase, timeout time.Duration) Inherent {
if timeout == 0 {
timeout = DefaultTimeout
}
return Inherent{
inst: inst,
timeout: timeout,
InherentBase: base,
}
}
// Timeout returns the timeout used for instrument I/O operations.
func (inherent *Inherent) Timeout() time.Duration {
return inherent.timeout
}
// newContext creates a context with the driver's configured timeout.
func (inherent *Inherent) newContext() (context.Context, context.CancelFunc) {
return context.WithTimeout(context.Background(), inherent.timeout)
}
// CheckID queries the instrument for its identification string (*IDN?) and
// verifies that the instrument model is in the list of SupportedInstrumentModels.
// On success, IDNString is populated with the full *IDN? response, the four
// identification fields (manufacturer, model, serial number, firmware
// revision) are cached on the receiver, and the model string is returned.
// CheckID implements the IdQuery behavior described in Section 6.16 of
// IVI-3.2: Inherent Capabilities Specification.
//
// Once CheckID succeeds, the four identification accessors
// ([Inherent.FirmwareRevision], [Inherent.InstrumentManufacturer],
// [Inherent.InstrumentModel], [Inherent.InstrumentSerialNumber]) return the
// cached values without issuing additional SCPI.
func (inherent *Inherent) CheckID() (string, error) {
ctx, cancel := inherent.newContext()
defer cancel()
idn, err := query.String(ctx, inherent.inst, "*IDN?")
if err != nil {
return "", fmt.Errorf("error querying instrument identity: %w", err)
}
inherent.IDNString = strings.TrimSpace(idn)
if err := inherent.cacheIdentification(inherent.IDNString); err != nil {
return "", err
}
if !slices.Contains(inherent.SupportedInstrumentModels, inherent.model) {
return inherent.model, fmt.Errorf(
"%w: %q is not supported by this driver",
ErrUnsupportedModel,
inherent.model,
)
}
return inherent.model, nil
}
// FirmwareRevision returns the instrument firmware revision. It returns the
// value cached by [Inherent.CheckID]; if the cache is empty (the
// [WithoutIDQuery] path where construction-time *IDN? failed), it issues a
// live *IDN? query and caches the result. FirmwareRevision is the getter for
// the read-only inherent attribute Instrument Firmware Revision described in
// Section 5.18 of IVI-3.2: Inherent Capabilities Specification.
func (inherent *Inherent) FirmwareRevision() (string, error) {
return inherent.lookupIdentification(&inherent.firmwareRevision, fwrID)
}
// InstrumentManufacturer returns the instrument manufacturer. It returns the
// value cached by [Inherent.CheckID]; if the cache is empty it issues a live
// *IDN? query and caches the result. InstrumentManufacturer is the getter for
// the read-only inherent attribute Instrument Manufacturer described in
// Section 5.19 of IVI-3.2: Inherent Capabilities Specification.
func (inherent *Inherent) InstrumentManufacturer() (string, error) {
return inherent.lookupIdentification(&inherent.manufacturer, mfrID)
}
// InstrumentModel returns the instrument model. It returns the value cached
// by [Inherent.CheckID]; if the cache is empty it issues a live *IDN? query
// and caches the result. InstrumentModel is the getter for the read-only
// inherent attribute Instrument Model described in Section 5.20 of IVI-3.2:
// Inherent Capabilities Specification.
func (inherent *Inherent) InstrumentModel() (string, error) {
return inherent.lookupIdentification(&inherent.model, modelID)
}
// InstrumentSerialNumber returns the instrument serial number. It returns the
// value cached by [Inherent.CheckID]; if the cache is empty it issues a live
// *IDN? query and caches the result.
func (inherent *Inherent) InstrumentSerialNumber() (string, error) {
return inherent.lookupIdentification(&inherent.serialNumber, snID)
}
// lookupIdentification returns the cached identification field when
// populated, otherwise issues a live *IDN? query and caches the result.
func (inherent *Inherent) lookupIdentification(field *string, part idPart) (string, error) {
if *field != "" {
return *field, nil
}
value, err := inherent.queryIdentification(part)
if err != nil {
return "", err
}
*field = value
return value, nil
}
// cacheIdentification parses the given IDN string into the four cached
// identification fields on the receiver.
func (inherent *Inherent) cacheIdentification(idn string) error {
const numIdentificationParts = 4
parts := strings.Split(idn, ",")
if len(parts) != numIdentificationParts {
return fmt.Errorf("idn string (`%s`) could not be split in four", idn)
}
inherent.manufacturer = strings.TrimSpace(parts[mfrID])
inherent.model = strings.TrimSpace(parts[modelID])
inherent.serialNumber = strings.TrimSpace(parts[snID])
inherent.firmwareRevision = strings.TrimSpace(parts[fwrID])
return nil
}
// Reset resets the instrument.
func (inherent *Inherent) Reset() error {
ctx, cancel := inherent.newContext()
defer cancel()
if err := inherent.inst.Command(ctx, "*rst"); err != nil {
return err
}
// Wait for the device to finish resetting.
time.Sleep(inherent.ResetDelay)
return nil
}
// Clear clears the instrument.
func (inherent *Inherent) Clear() error {
ctx, cancel := inherent.newContext()
defer cancel()
if err := inherent.inst.Command(ctx, "*cls"); err != nil {
return err
}
// Wait for the device to finish clearing.
time.Sleep(inherent.ClearDelay)
return nil
}
// Disable places the instrument in a quiescent state as quickly as possible.
// If ReturnToLocal is true, this method also returns the instrument to local
// control by sending the SYST:LOC command, allowing the front panel to regain
// control. Disable provides the method described in Section 6.4 of IVI-3.2:
// Inherent Capabilities Specification.
//
// Errors from the local control command are intentionally ignored because not
// all instruments support SYST:LOC, and a failure to return to local should
// not prevent the instrument from being closed.
func (inherent *Inherent) Disable() error {
if !inherent.ReturnToLocal {
return nil
}
ctx, cancel := inherent.newContext()
defer cancel()
// Best-effort return to local control so the front panel regains control.
// Use the instrument-specific command if provided, otherwise default to
// SYST:LOC which is the most common SCPI local control command.
cmd := inherent.LocalControlCommand
if cmd == "" {
cmd = "SYST:LOC"
}
_ = inherent.inst.Command(ctx, cmd)
return nil
}
func (inherent *Inherent) queryIdentification(part idPart) (string, error) {
ctx, cancel := inherent.newContext()
defer cancel()
s, err := query.String(ctx, inherent.inst, "*IDN?")
if err != nil {
return "", err
}
return parseIdentification(strings.TrimSpace(s), part)
}
func parseIdentification(idn string, part idPart) (string, error) {
const numIdentificationParts = 4
parts := strings.Split(idn, ",")
if len(parts) != numIdentificationParts {
return "", fmt.Errorf("idn string (`%s`) could not be split in four", idn)
}
return strings.TrimSpace(parts[part]), nil
}
// SetReturnToLocal controls whether the instrument returns to local control
// when Disable() or Close() is called. Default is true.
func (inherent *Inherent) SetReturnToLocal(enabled bool) {
inherent.ReturnToLocal = enabled
}
// IsReturnToLocal returns whether the instrument will return to local control
// when Disable() or Close() is called.
func (inherent *Inherent) IsReturnToLocal() bool {
return inherent.ReturnToLocal
}
// Close returns the instrument to local front panel control by calling
// Disable. The underlying transport connection is not closed — that is the
// caller's responsibility since they created it.
func (inherent *Inherent) Close() error {
return inherent.Disable()
}