qcodes 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qcodes/__init__.py +65 -0
- qcodes/actions.py +207 -0
- qcodes/config/__init__.py +2 -0
- qcodes/config/config.py +396 -0
- qcodes/config/qcodesrc.json +11 -0
- qcodes/config/qcodesrc_schema.json +55 -0
- qcodes/data/__init__.py +0 -0
- qcodes/data/data_array.py +520 -0
- qcodes/data/data_set.py +803 -0
- qcodes/data/format.py +309 -0
- qcodes/data/gnuplot_format.py +366 -0
- qcodes/data/hdf5_format.py +385 -0
- qcodes/data/io.py +266 -0
- qcodes/data/location.py +166 -0
- qcodes/data/manager.py +163 -0
- qcodes/instrument/__init__.py +0 -0
- qcodes/instrument/base.py +630 -0
- qcodes/instrument/function.py +134 -0
- qcodes/instrument/ip.py +236 -0
- qcodes/instrument/metaclass.py +45 -0
- qcodes/instrument/mock.py +278 -0
- qcodes/instrument/parameter.py +1001 -0
- qcodes/instrument/remote.py +529 -0
- qcodes/instrument/server.py +189 -0
- qcodes/instrument/sweep_values.py +286 -0
- qcodes/instrument/visa.py +198 -0
- qcodes/instrument_drivers/AlazarTech/ATS.py +1001 -0
- qcodes/instrument_drivers/AlazarTech/ATS9870.py +266 -0
- qcodes/instrument_drivers/AlazarTech/ATS_acquisition_controllers.py +143 -0
- qcodes/instrument_drivers/AlazarTech/__init__.py +0 -0
- qcodes/instrument_drivers/QuTech/IVVI.py +390 -0
- qcodes/instrument_drivers/QuTech/__init__.py +0 -0
- qcodes/instrument_drivers/__init__.py +0 -0
- qcodes/instrument_drivers/agilent/Agilent_34400A.py +155 -0
- qcodes/instrument_drivers/agilent/E8527D.py +79 -0
- qcodes/instrument_drivers/agilent/HP33210A.py +104 -0
- qcodes/instrument_drivers/agilent/__init__.py +0 -0
- qcodes/instrument_drivers/agilent/test_suite.py +96 -0
- qcodes/instrument_drivers/ithaco/Ithaco_1211.py +110 -0
- qcodes/instrument_drivers/ithaco/__init__.py +0 -0
- qcodes/instrument_drivers/oxford/__init__.py +0 -0
- qcodes/instrument_drivers/oxford/mercuryiPS.py +358 -0
- qcodes/instrument_drivers/oxford/triton.py +227 -0
- qcodes/instrument_drivers/rigol/DG4000.py +591 -0
- qcodes/instrument_drivers/rigol/__init__.py +0 -0
- qcodes/instrument_drivers/rohde_schwarz/SGS100A.py +99 -0
- qcodes/instrument_drivers/rohde_schwarz/ZNB20.py +154 -0
- qcodes/instrument_drivers/rohde_schwarz/__init__.py +0 -0
- qcodes/instrument_drivers/signal_hound/USB_SA124B.py +567 -0
- qcodes/instrument_drivers/signal_hound/__init__.py +0 -0
- qcodes/instrument_drivers/stanford_research/SR560.py +116 -0
- qcodes/instrument_drivers/stanford_research/SR830.py +314 -0
- qcodes/instrument_drivers/stanford_research/SR865.py +305 -0
- qcodes/instrument_drivers/stanford_research/__init__.py +0 -0
- qcodes/instrument_drivers/tektronix/AWG5014.py +1228 -0
- qcodes/instrument_drivers/tektronix/AWG520.py +659 -0
- qcodes/instrument_drivers/tektronix/Keithley_2000.py +195 -0
- qcodes/instrument_drivers/tektronix/Keithley_2600.py +83 -0
- qcodes/instrument_drivers/tektronix/Keithley_2700.py +395 -0
- qcodes/instrument_drivers/tektronix/__init__.py +0 -0
- qcodes/instrument_drivers/test.py +94 -0
- qcodes/instrument_drivers/weinschel/Weinschel_8320.py +21 -0
- qcodes/instrument_drivers/weinschel/__init__.py +0 -0
- qcodes/instrument_drivers/weinschel/test_suite.py +27 -0
- qcodes/loops.py +1009 -0
- qcodes/measure.py +155 -0
- qcodes/plots/__init__.py +0 -0
- qcodes/plots/base.py +280 -0
- qcodes/plots/colors.py +135 -0
- qcodes/plots/pyqtgraph.py +384 -0
- qcodes/plots/qcmatplotlib.py +206 -0
- qcodes/process/__init__.py +0 -0
- qcodes/process/helpers.py +60 -0
- qcodes/process/qcodes_process.py +72 -0
- qcodes/process/server.py +395 -0
- qcodes/process/stream_queue.py +152 -0
- qcodes/station.py +178 -0
- qcodes/test.py +119 -0
- qcodes/tests/__init__.py +0 -0
- qcodes/tests/common.py +7 -0
- qcodes/tests/data_mocks.py +173 -0
- qcodes/tests/instrument_mocks.py +327 -0
- qcodes/tests/py35_syntax.py +13 -0
- qcodes/tests/test_combined_par.py +156 -0
- qcodes/tests/test_command.py +120 -0
- qcodes/tests/test_config.py +222 -0
- qcodes/tests/test_data.py +668 -0
- qcodes/tests/test_deferred_operations.py +105 -0
- qcodes/tests/test_driver_testcase.py +58 -0
- qcodes/tests/test_format.py +414 -0
- qcodes/tests/test_hdf5formatter.py +333 -0
- qcodes/tests/test_helpers.py +630 -0
- qcodes/tests/test_instrument.py +981 -0
- qcodes/tests/test_instrument_server.py +156 -0
- qcodes/tests/test_json.py +48 -0
- qcodes/tests/test_location_provider.py +109 -0
- qcodes/tests/test_loop.py +825 -0
- qcodes/tests/test_measure.py +49 -0
- qcodes/tests/test_metadata.py +49 -0
- qcodes/tests/test_multiprocessing.py +471 -0
- qcodes/tests/test_nested_attrs.py +100 -0
- qcodes/tests/test_parameter.py +151 -0
- qcodes/tests/test_plots.py +44 -0
- qcodes/tests/test_sweep_values.py +159 -0
- qcodes/tests/test_validators.py +482 -0
- qcodes/tests/test_visa.py +184 -0
- qcodes/utils/__init__.py +0 -0
- qcodes/utils/command.py +176 -0
- qcodes/utils/deferred_operations.py +276 -0
- qcodes/utils/helpers.py +417 -0
- qcodes/utils/metadata.py +42 -0
- qcodes/utils/nested_attrs.py +177 -0
- qcodes/utils/reload_code.py +98 -0
- qcodes/utils/threading.py +72 -0
- qcodes/utils/timing.py +89 -0
- qcodes/utils/validators.py +393 -0
- qcodes/version.py +1 -0
- qcodes/widgets/__init__.py +0 -0
- qcodes/widgets/display.py +47 -0
- qcodes/widgets/widgets.css +77 -0
- qcodes/widgets/widgets.js +237 -0
- qcodes/widgets/widgets.py +215 -0
- qcodes-0.1.0.dist-info/DESCRIPTION.rst +165 -0
- qcodes-0.1.0.dist-info/METADATA +194 -0
- qcodes-0.1.0.dist-info/RECORD +128 -0
- qcodes-0.1.0.dist-info/WHEEL +5 -0
- qcodes-0.1.0.dist-info/metadata.json +1 -0
- qcodes-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,630 @@
|
|
|
1
|
+
"""Instrument base class."""
|
|
2
|
+
import logging
|
|
3
|
+
import time
|
|
4
|
+
import warnings
|
|
5
|
+
import weakref
|
|
6
|
+
|
|
7
|
+
from qcodes.utils.metadata import Metadatable
|
|
8
|
+
from qcodes.utils.helpers import DelegateAttributes, strip_attrs, full_class
|
|
9
|
+
from qcodes.utils.nested_attrs import NestedAttrAccess
|
|
10
|
+
from qcodes.utils.validators import Anything
|
|
11
|
+
from .parameter import StandardParameter
|
|
12
|
+
from .function import Function
|
|
13
|
+
from .metaclass import InstrumentMetaclass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Instrument(Metadatable, DelegateAttributes, NestedAttrAccess,
|
|
17
|
+
metaclass=InstrumentMetaclass):
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
Base class for all QCodes instruments.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
name (str): an identifier for this instrument, particularly for
|
|
24
|
+
attaching it to a Station.
|
|
25
|
+
|
|
26
|
+
server_name (Optional[str]): If not ``None``, this instrument starts a
|
|
27
|
+
separate server process (or connects to one, if one already exists
|
|
28
|
+
with the same name) and all hardware calls are made there.
|
|
29
|
+
|
|
30
|
+
Default '', then we call classmethod ``default_server_name``,
|
|
31
|
+
passing in all the constructor kwargs, to determine the name.
|
|
32
|
+
If not overridden, this just gives 'Instruments'.
|
|
33
|
+
|
|
34
|
+
**see subclass constructors below for more on ``server_name``**
|
|
35
|
+
|
|
36
|
+
Use None to operate without a server - but then this Instrument
|
|
37
|
+
will not work with qcodes Loops or other multiprocess procedures.
|
|
38
|
+
|
|
39
|
+
If a server is used, the ``Instrument`` you asked for is
|
|
40
|
+
instantiated on the server, and the object you get in the main
|
|
41
|
+
process is actually a ``RemoteInstrument`` that proxies all method
|
|
42
|
+
calls, ``Parameters``, and ``Functions`` to the server.
|
|
43
|
+
|
|
44
|
+
The metaclass ``InstrumentMetaclass`` handles making either the
|
|
45
|
+
requested class or its RemoteInstrument proxy.
|
|
46
|
+
|
|
47
|
+
metadata (Optional[Dict]): additional static metadata to add to this
|
|
48
|
+
instrument's JSON snapshot.
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
Any unpicklable objects that are inputs to the constructor must be set
|
|
52
|
+
on server initialization, and must be shared between all instruments
|
|
53
|
+
that reside on the same server. To make this happen, set the
|
|
54
|
+
``shared_kwargs`` class attribute to a tuple of kwarg names that should
|
|
55
|
+
be treated this way.
|
|
56
|
+
|
|
57
|
+
It is an error to initialize two instruments on the same server with
|
|
58
|
+
different keys or values for ``shared_kwargs``, unless the later
|
|
59
|
+
instruments have NO ``shared_kwargs`` at all.
|
|
60
|
+
|
|
61
|
+
subclass constructors: ``server_name`` and any ``shared_kwargs`` must be
|
|
62
|
+
available as kwargs and kwargs ONLY (not positional) in all subclasses,
|
|
63
|
+
and not modified in the inheritance chain. This is because we need to
|
|
64
|
+
create the server before instantiating the actual instrument. The easiest
|
|
65
|
+
way to manage this is to accept ``**kwargs`` in your subclass and pass them
|
|
66
|
+
on to ``super().__init()``.
|
|
67
|
+
|
|
68
|
+
Attributes:
|
|
69
|
+
name (str): an identifier for this instrument, particularly for
|
|
70
|
+
attaching it to a Station.
|
|
71
|
+
|
|
72
|
+
parameters (Dict[Parameter]): All the parameters supported by this
|
|
73
|
+
instrument. Usually populated via ``add_parameter``
|
|
74
|
+
|
|
75
|
+
functions (Dict[Function]): All the functions supported by this
|
|
76
|
+
instrument. Usually populated via ``add_function``
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
shared_kwargs = ()
|
|
80
|
+
|
|
81
|
+
_all_instruments = {}
|
|
82
|
+
|
|
83
|
+
def __init__(self, name, server_name=None, **kwargs):
|
|
84
|
+
self._t0 = time.time()
|
|
85
|
+
super().__init__(**kwargs)
|
|
86
|
+
self.parameters = {}
|
|
87
|
+
self.functions = {}
|
|
88
|
+
|
|
89
|
+
self.name = str(name)
|
|
90
|
+
|
|
91
|
+
self.add_parameter('IDN', get_cmd=self.get_idn,
|
|
92
|
+
vals=Anything())
|
|
93
|
+
|
|
94
|
+
self._meta_attrs = ['name']
|
|
95
|
+
|
|
96
|
+
self._no_proxy_methods = {'__getstate__'}
|
|
97
|
+
|
|
98
|
+
def get_idn(self):
|
|
99
|
+
"""
|
|
100
|
+
Parse a standard VISA '*IDN?' response into an ID dict.
|
|
101
|
+
|
|
102
|
+
Even though this is the VISA standard, it applies to various other
|
|
103
|
+
types as well, such as IPInstruments, so it is included here in the
|
|
104
|
+
Instrument base class.
|
|
105
|
+
|
|
106
|
+
Override this if your instrument does not support '*IDN?' or
|
|
107
|
+
returns a nonstandard IDN string. This string is supposed to be a
|
|
108
|
+
comma-separated list of vendor, model, serial, and firmware, but
|
|
109
|
+
semicolon and colon are also common separators so we accept them here
|
|
110
|
+
as well.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
A dict containing vendor, model, serial, and firmware.
|
|
114
|
+
"""
|
|
115
|
+
try:
|
|
116
|
+
idstr = '' # in case self.ask fails
|
|
117
|
+
idstr = self.ask('*IDN?')
|
|
118
|
+
# form is supposed to be comma-separated, but we've seen
|
|
119
|
+
# other separators occasionally
|
|
120
|
+
for separator in ',;:':
|
|
121
|
+
# split into no more than 4 parts, so we don't lose info
|
|
122
|
+
idparts = [p.strip() for p in idstr.split(separator, 3)]
|
|
123
|
+
if len(idparts) > 1:
|
|
124
|
+
break
|
|
125
|
+
# in case parts at the end are missing, fill in None
|
|
126
|
+
if len(idparts) < 4:
|
|
127
|
+
idparts += [None] * (4 - len(idparts))
|
|
128
|
+
except:
|
|
129
|
+
logging.warn('Error getting or interpreting *IDN?: ' + repr(idstr))
|
|
130
|
+
idparts = [None, None, None, None]
|
|
131
|
+
|
|
132
|
+
# some strings include the word 'model' at the front of model
|
|
133
|
+
if str(idparts[1]).lower().startswith('model'):
|
|
134
|
+
idparts[1] = str(idparts[1])[5:].strip()
|
|
135
|
+
|
|
136
|
+
return dict(zip(('vendor', 'model', 'serial', 'firmware'), idparts))
|
|
137
|
+
|
|
138
|
+
@classmethod
|
|
139
|
+
def default_server_name(cls, **kwargs):
|
|
140
|
+
"""
|
|
141
|
+
Generate a default name for the server to host this instrument.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
**kwargs: the constructor kwargs, used if necessary to choose a
|
|
145
|
+
name.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
str: The default server name for the specific instrument instance
|
|
149
|
+
we are constructing.
|
|
150
|
+
"""
|
|
151
|
+
return 'Instruments'
|
|
152
|
+
|
|
153
|
+
def connect_message(self, idn_param='IDN', begin_time=None):
|
|
154
|
+
"""
|
|
155
|
+
Print a standard message on initial connection to an instrument.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
idn_param (str): name of parameter that returns ID dict.
|
|
159
|
+
Default 'IDN'.
|
|
160
|
+
begin_time (number): time.time() when init started.
|
|
161
|
+
Default is self._t0, set at start of Instrument.__init__.
|
|
162
|
+
"""
|
|
163
|
+
# start with an empty dict, just in case an instrument doesn't
|
|
164
|
+
# heed our request to return all 4 fields.
|
|
165
|
+
idn = {'vendor': None, 'model': None,
|
|
166
|
+
'serial': None, 'firmware': None}
|
|
167
|
+
idn.update(self.get(idn_param))
|
|
168
|
+
t = time.time() - (begin_time or self._t0)
|
|
169
|
+
|
|
170
|
+
con_msg = ('Connected to: {vendor} {model} '
|
|
171
|
+
'(serial:{serial}, firmware:{firmware}) '
|
|
172
|
+
'in {t:.2f}s'.format(t=t, **idn))
|
|
173
|
+
print(con_msg)
|
|
174
|
+
|
|
175
|
+
def __repr__(self):
|
|
176
|
+
"""Simplified repr giving just the class and name."""
|
|
177
|
+
return '<{}: {}>'.format(type(self).__name__, self.name)
|
|
178
|
+
|
|
179
|
+
def __del__(self):
|
|
180
|
+
"""Close the instrument and remove its instance record."""
|
|
181
|
+
try:
|
|
182
|
+
wr = weakref.ref(self)
|
|
183
|
+
if wr in getattr(self, '_instances', {}):
|
|
184
|
+
self._instances.remove(wr)
|
|
185
|
+
self.close()
|
|
186
|
+
except:
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
def close(self):
|
|
190
|
+
"""
|
|
191
|
+
Irreversibly stop this instrument and free its resources.
|
|
192
|
+
|
|
193
|
+
Subclasses should override this if they have other specific
|
|
194
|
+
resources to close.
|
|
195
|
+
"""
|
|
196
|
+
if hasattr(self, 'connection') and hasattr(self.connection, 'close'):
|
|
197
|
+
self.connection.close()
|
|
198
|
+
|
|
199
|
+
strip_attrs(self, whitelist=['name'])
|
|
200
|
+
self.remove_instance(self)
|
|
201
|
+
|
|
202
|
+
@classmethod
|
|
203
|
+
def record_instance(cls, instance):
|
|
204
|
+
"""
|
|
205
|
+
Record (a weak ref to) an instance in a class's instance list.
|
|
206
|
+
|
|
207
|
+
Also records the instance in list of *all* instruments, and verifies
|
|
208
|
+
that there are no other instruments with the same name.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
instance (Union[Instrument, RemoteInstrument]): Note: we *do not*
|
|
212
|
+
check that instance is actually an instance of ``cls``. This is
|
|
213
|
+
important, because a ``RemoteInstrument`` should function as an
|
|
214
|
+
instance of the instrument it proxies.
|
|
215
|
+
|
|
216
|
+
Raises:
|
|
217
|
+
KeyError: if another instance with the same name is already present
|
|
218
|
+
"""
|
|
219
|
+
wr = weakref.ref(instance)
|
|
220
|
+
name = instance.name
|
|
221
|
+
|
|
222
|
+
# First insert this instrument in the record of *all* instruments
|
|
223
|
+
# making sure its name is unique
|
|
224
|
+
existing_wr = cls._all_instruments.get(name)
|
|
225
|
+
if existing_wr and existing_wr():
|
|
226
|
+
raise KeyError('Another instrument has the name: {}'.format(name))
|
|
227
|
+
|
|
228
|
+
cls._all_instruments[name] = wr
|
|
229
|
+
|
|
230
|
+
# Then add it to the record for this specific subclass, using ``_type``
|
|
231
|
+
# to make sure we're not recording it in a base class instance list
|
|
232
|
+
if getattr(cls, '_type', None) is not cls:
|
|
233
|
+
cls._type = cls
|
|
234
|
+
cls._instances = []
|
|
235
|
+
cls._instances.append(wr)
|
|
236
|
+
|
|
237
|
+
@classmethod
|
|
238
|
+
def instances(cls):
|
|
239
|
+
"""
|
|
240
|
+
Get all currently defined instances of this instrument class.
|
|
241
|
+
|
|
242
|
+
You can use this to get the objects back if you lose track of them,
|
|
243
|
+
and it's also used by the test system to find objects to test against.
|
|
244
|
+
|
|
245
|
+
Note:
|
|
246
|
+
Will also include ``RemoteInstrument`` instances that proxy
|
|
247
|
+
instruments of this class.
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
List[Union[Instrument, RemoteInstrument]]
|
|
251
|
+
"""
|
|
252
|
+
if getattr(cls, '_type', None) is not cls:
|
|
253
|
+
# only instances of a superclass - we want instances of this
|
|
254
|
+
# exact class only
|
|
255
|
+
return []
|
|
256
|
+
return [wr() for wr in getattr(cls, '_instances', []) if wr()]
|
|
257
|
+
|
|
258
|
+
@classmethod
|
|
259
|
+
def remove_instance(cls, instance):
|
|
260
|
+
"""
|
|
261
|
+
Remove a particular instance from the record.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
instance (Union[Instrument, RemoteInstrument])
|
|
265
|
+
"""
|
|
266
|
+
wr = weakref.ref(instance)
|
|
267
|
+
if wr in cls._instances:
|
|
268
|
+
cls._instances.remove(wr)
|
|
269
|
+
|
|
270
|
+
# remove from all_instruments too, but don't depend on the
|
|
271
|
+
# name to do it, in case name has changed or been deleted
|
|
272
|
+
all_ins = cls._all_instruments
|
|
273
|
+
for name, ref in list(all_ins.items()):
|
|
274
|
+
if ref is wr:
|
|
275
|
+
del all_ins[name]
|
|
276
|
+
|
|
277
|
+
@classmethod
|
|
278
|
+
def find_instrument(cls, name, instrument_class=None):
|
|
279
|
+
"""
|
|
280
|
+
Find an existing instrument by name.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
name (str)
|
|
284
|
+
instrument_class (Optional[class]): The type of instrument
|
|
285
|
+
you are looking for.
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
Union[Instrument, RemoteInstrument]
|
|
289
|
+
|
|
290
|
+
Raises:
|
|
291
|
+
KeyError: if no instrument of that name was found, or if its
|
|
292
|
+
reference is invalid (dead).
|
|
293
|
+
TypeError: if a specific class was requested but a different
|
|
294
|
+
type was found
|
|
295
|
+
"""
|
|
296
|
+
ins = cls._all_instruments[name]()
|
|
297
|
+
|
|
298
|
+
if ins is None:
|
|
299
|
+
del cls._all_instruments[name]
|
|
300
|
+
raise KeyError('Instrument {} has been removed'.format(name))
|
|
301
|
+
|
|
302
|
+
if instrument_class is not None:
|
|
303
|
+
if not isinstance(ins, instrument_class):
|
|
304
|
+
raise TypeError(
|
|
305
|
+
'Instrument {} is {} but {} was requested'.format(
|
|
306
|
+
name, type(ins), instrument_class))
|
|
307
|
+
|
|
308
|
+
return ins
|
|
309
|
+
|
|
310
|
+
@classmethod
|
|
311
|
+
def find_component(cls, name_attr, instrument_class=None):
|
|
312
|
+
"""
|
|
313
|
+
Find a component of an existing instrument by name and attribute.
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
name_attr (str): A string in nested attribute format:
|
|
317
|
+
<name>.<attribute>[.<subattribute>] and so on.
|
|
318
|
+
For example, <attribute> can be a parameter name,
|
|
319
|
+
or a method name.
|
|
320
|
+
instrument_class (Optional[class]): The type of instrument
|
|
321
|
+
you are looking for this component within.
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
Any: The component requested.
|
|
325
|
+
"""
|
|
326
|
+
|
|
327
|
+
if '.' in name_attr:
|
|
328
|
+
name, attr = name_attr.split('.', 1)
|
|
329
|
+
ins = cls.find_instrument(name, instrument_class=instrument_class)
|
|
330
|
+
return ins.getattr(attr)
|
|
331
|
+
|
|
332
|
+
else:
|
|
333
|
+
# allow find_component to return the whole instrument,
|
|
334
|
+
# if no attribute was specified, for maximum generality.
|
|
335
|
+
return cls.find_instrument(name_attr,
|
|
336
|
+
instrument_class=instrument_class)
|
|
337
|
+
|
|
338
|
+
def add_parameter(self, name, parameter_class=StandardParameter,
|
|
339
|
+
**kwargs):
|
|
340
|
+
"""
|
|
341
|
+
Bind one Parameter to this instrument.
|
|
342
|
+
|
|
343
|
+
Instrument subclasses can call this repeatedly in their ``__init__``
|
|
344
|
+
for every real parameter of the instrument.
|
|
345
|
+
|
|
346
|
+
In this sense, parameters are the state variables of the instrument,
|
|
347
|
+
anything the user can set and/or get
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
name (str): How the parameter will be stored within
|
|
351
|
+
``instrument.parameters`` and also how you address it using the
|
|
352
|
+
shortcut methods: ``instrument.set(param_name, value)`` etc.
|
|
353
|
+
|
|
354
|
+
parameter_class (Optional[type]): You can construct the parameter
|
|
355
|
+
out of any class. Default ``StandardParameter``.
|
|
356
|
+
|
|
357
|
+
**kwargs: constructor arguments for ``parameter_class``.
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
dict: attribute information. Only used if you add parameters
|
|
361
|
+
from the ``RemoteInstrument`` rather than at construction, to
|
|
362
|
+
properly construct the proxy for this parameter.
|
|
363
|
+
|
|
364
|
+
Raises:
|
|
365
|
+
KeyError: if this instrument already has a parameter with this
|
|
366
|
+
name.
|
|
367
|
+
"""
|
|
368
|
+
if name in self.parameters:
|
|
369
|
+
raise KeyError('Duplicate parameter name {}'.format(name))
|
|
370
|
+
param = parameter_class(name=name, instrument=self, **kwargs)
|
|
371
|
+
self.parameters[name] = param
|
|
372
|
+
|
|
373
|
+
# for use in RemoteInstruments to add parameters to the server
|
|
374
|
+
# we return the info they need to construct their proxy
|
|
375
|
+
return param.get_attrs()
|
|
376
|
+
|
|
377
|
+
def add_function(self, name, **kwargs):
|
|
378
|
+
"""
|
|
379
|
+
Bind one Function to this instrument.
|
|
380
|
+
|
|
381
|
+
Instrument subclasses can call this repeatedly in their ``__init__``
|
|
382
|
+
for every real function of the instrument.
|
|
383
|
+
|
|
384
|
+
This functionality is meant for simple cases, principally things that
|
|
385
|
+
map to simple commands like '*RST' (reset) or those with just a few
|
|
386
|
+
arguments. It requires a fixed argument count, and positional args
|
|
387
|
+
only. If your case is more complicated, you're probably better off
|
|
388
|
+
simply making a new method in your ``Instrument`` subclass definition.
|
|
389
|
+
|
|
390
|
+
Args:
|
|
391
|
+
name (str): how the Function will be stored within
|
|
392
|
+
``instrument.Functions`` and also how you address it using the
|
|
393
|
+
shortcut methods: ``instrument.call(func_name, *args)`` etc.
|
|
394
|
+
|
|
395
|
+
**kwargs: constructor kwargs for ``Function``
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
A dict of attribute information. Only used if you add functions
|
|
399
|
+
from the ``RemoteInstrument`` rather than at construction, to
|
|
400
|
+
properly construct the proxy for this function.
|
|
401
|
+
|
|
402
|
+
Raises:
|
|
403
|
+
KeyError: if this instrument already has a function with this
|
|
404
|
+
name.
|
|
405
|
+
"""
|
|
406
|
+
if name in self.functions:
|
|
407
|
+
raise KeyError('Duplicate function name {}'.format(name))
|
|
408
|
+
func = Function(name=name, instrument=self, **kwargs)
|
|
409
|
+
self.functions[name] = func
|
|
410
|
+
|
|
411
|
+
# for use in RemoteInstruments to add functions to the server
|
|
412
|
+
# we return the info they need to construct their proxy
|
|
413
|
+
return func.get_attrs()
|
|
414
|
+
|
|
415
|
+
def snapshot_base(self, update=False):
|
|
416
|
+
"""
|
|
417
|
+
State of the instrument as a JSON-compatible dict.
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
update (bool): If True, update the state by querying the
|
|
421
|
+
instrument. If False, just use the latest values in memory.
|
|
422
|
+
|
|
423
|
+
Returns:
|
|
424
|
+
dict: base snapshot
|
|
425
|
+
"""
|
|
426
|
+
snap = {'parameters': dict((name, param.snapshot(update=update))
|
|
427
|
+
for name, param in self.parameters.items()),
|
|
428
|
+
'functions': dict((name, func.snapshot(update=update))
|
|
429
|
+
for name, func in self.functions.items()),
|
|
430
|
+
'__class__': full_class(self),
|
|
431
|
+
}
|
|
432
|
+
for attr in set(self._meta_attrs):
|
|
433
|
+
if hasattr(self, attr):
|
|
434
|
+
snap[attr] = getattr(self, attr)
|
|
435
|
+
return snap
|
|
436
|
+
|
|
437
|
+
#
|
|
438
|
+
# `write_raw` and `ask_raw` are the interface to hardware #
|
|
439
|
+
# `write` and `ask` are standard wrappers to help with error reporting #
|
|
440
|
+
#
|
|
441
|
+
|
|
442
|
+
def write(self, cmd):
|
|
443
|
+
"""
|
|
444
|
+
Write a command string with NO response to the hardware.
|
|
445
|
+
|
|
446
|
+
Subclasses that transform ``cmd`` should override this method, and in
|
|
447
|
+
it call ``super().write(new_cmd)``. Subclasses that define a new
|
|
448
|
+
hardware communication should instead override ``write_raw``.
|
|
449
|
+
|
|
450
|
+
Args:
|
|
451
|
+
cmd (str): the string to send to the instrument
|
|
452
|
+
|
|
453
|
+
Raises:
|
|
454
|
+
Exception: wraps any underlying exception with extra context,
|
|
455
|
+
including the command and the instrument.
|
|
456
|
+
"""
|
|
457
|
+
try:
|
|
458
|
+
self.write_raw(cmd)
|
|
459
|
+
except Exception as e:
|
|
460
|
+
e.args = e.args + ('writing ' + repr(cmd) + ' to ' + repr(self),)
|
|
461
|
+
raise e
|
|
462
|
+
|
|
463
|
+
def write_raw(self, cmd):
|
|
464
|
+
"""
|
|
465
|
+
Low level method to write a command string to the hardware.
|
|
466
|
+
|
|
467
|
+
Subclasses that define a new hardware communication should override
|
|
468
|
+
this method. Subclasses that transform ``cmd`` should instead
|
|
469
|
+
override ``write``.
|
|
470
|
+
|
|
471
|
+
Args:
|
|
472
|
+
cmd (str): the string to send to the instrument
|
|
473
|
+
"""
|
|
474
|
+
raise NotImplementedError(
|
|
475
|
+
'Instrument {} has not defined a write method'.format(
|
|
476
|
+
type(self).__name__))
|
|
477
|
+
|
|
478
|
+
def ask(self, cmd):
|
|
479
|
+
"""
|
|
480
|
+
Write a command string to the hardware and return a response.
|
|
481
|
+
|
|
482
|
+
Subclasses that transform ``cmd`` should override this method, and in
|
|
483
|
+
it call ``super().ask(new_cmd)``. Subclasses that define a new
|
|
484
|
+
hardware communication should instead override ``ask_raw``.
|
|
485
|
+
|
|
486
|
+
Args:
|
|
487
|
+
cmd (str): the string to send to the instrument
|
|
488
|
+
|
|
489
|
+
Returns:
|
|
490
|
+
response (str, normally)
|
|
491
|
+
|
|
492
|
+
Raises:
|
|
493
|
+
Exception: wraps any underlying exception with extra context,
|
|
494
|
+
including the command and the instrument.
|
|
495
|
+
"""
|
|
496
|
+
try:
|
|
497
|
+
return self.ask_raw(cmd)
|
|
498
|
+
except Exception as e:
|
|
499
|
+
e.args = e.args + ('asking ' + repr(cmd) + ' to ' + repr(self),)
|
|
500
|
+
raise e
|
|
501
|
+
|
|
502
|
+
def ask_raw(self, cmd):
|
|
503
|
+
"""
|
|
504
|
+
Low level method to write to the hardware and return a response.
|
|
505
|
+
|
|
506
|
+
Subclasses that define a new hardware communication should override
|
|
507
|
+
this method. Subclasses that transform ``cmd`` should instead
|
|
508
|
+
override ``ask``.
|
|
509
|
+
|
|
510
|
+
Args:
|
|
511
|
+
cmd (str): the string to send to the instrument
|
|
512
|
+
"""
|
|
513
|
+
raise NotImplementedError(
|
|
514
|
+
'Instrument {} has not defined an ask method'.format(
|
|
515
|
+
type(self).__name__))
|
|
516
|
+
|
|
517
|
+
#
|
|
518
|
+
# shortcuts to parameters & setters & getters #
|
|
519
|
+
#
|
|
520
|
+
# instrument['someparam'] === instrument.parameters['someparam'] #
|
|
521
|
+
# instrument.someparam === instrument.parameters['someparam'] #
|
|
522
|
+
# instrument.get('someparam') === instrument['someparam'].get() #
|
|
523
|
+
# etc... #
|
|
524
|
+
#
|
|
525
|
+
|
|
526
|
+
delegate_attr_dicts = ['parameters', 'functions']
|
|
527
|
+
|
|
528
|
+
def __getitem__(self, key):
|
|
529
|
+
"""Delegate instrument['name'] to parameter or function 'name'."""
|
|
530
|
+
try:
|
|
531
|
+
return self.parameters[key]
|
|
532
|
+
except KeyError:
|
|
533
|
+
return self.functions[key]
|
|
534
|
+
|
|
535
|
+
def set(self, param_name, value):
|
|
536
|
+
"""
|
|
537
|
+
Shortcut for setting a parameter from its name and new value.
|
|
538
|
+
|
|
539
|
+
Args:
|
|
540
|
+
param_name (str): The name of a parameter of this instrument.
|
|
541
|
+
value (any): The new value to set.
|
|
542
|
+
"""
|
|
543
|
+
self.parameters[param_name].set(value)
|
|
544
|
+
|
|
545
|
+
def get(self, param_name):
|
|
546
|
+
"""
|
|
547
|
+
Shortcut for getting a parameter from its name.
|
|
548
|
+
|
|
549
|
+
Args:
|
|
550
|
+
param_name (str): The name of a parameter of this instrument.
|
|
551
|
+
|
|
552
|
+
Returns:
|
|
553
|
+
any: The current value of the parameter.
|
|
554
|
+
"""
|
|
555
|
+
return self.parameters[param_name].get()
|
|
556
|
+
|
|
557
|
+
def call(self, func_name, *args):
|
|
558
|
+
"""
|
|
559
|
+
Shortcut for calling a function from its name.
|
|
560
|
+
|
|
561
|
+
Args:
|
|
562
|
+
func_name (str): The name of a function of this instrument.
|
|
563
|
+
*args: any arguments to the function.
|
|
564
|
+
|
|
565
|
+
Returns:
|
|
566
|
+
any: The return value of the function.
|
|
567
|
+
"""
|
|
568
|
+
return self.functions[func_name].call(*args)
|
|
569
|
+
|
|
570
|
+
#
|
|
571
|
+
# info about what's in this instrument, to help construct the remote #
|
|
572
|
+
#
|
|
573
|
+
|
|
574
|
+
def connection_attrs(self, new_id):
|
|
575
|
+
"""
|
|
576
|
+
Collect info to reconstruct the instrument API in the RemoteInstrument.
|
|
577
|
+
|
|
578
|
+
Args:
|
|
579
|
+
new_id (int): The ID of this instrument on its server.
|
|
580
|
+
This is how the RemoteInstrument points its calls to the
|
|
581
|
+
correct server instrument when it calls the server.
|
|
582
|
+
|
|
583
|
+
Returns:
|
|
584
|
+
dict: Dictionary of name: str, id: int, parameters: dict,
|
|
585
|
+
functions: dict, _methods: dict
|
|
586
|
+
parameters, functions, and _methods are dictionaries of
|
|
587
|
+
name: List(str) of attributes to be proxied in the remote.
|
|
588
|
+
"""
|
|
589
|
+
return {
|
|
590
|
+
'name': self.name,
|
|
591
|
+
'id': new_id,
|
|
592
|
+
'parameters': {name: p.get_attrs()
|
|
593
|
+
for name, p in self.parameters.items()},
|
|
594
|
+
'functions': {name: f.get_attrs()
|
|
595
|
+
for name, f in self.functions.items()},
|
|
596
|
+
'_methods': self._get_method_attrs()
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
def _get_method_attrs(self):
|
|
600
|
+
"""
|
|
601
|
+
Construct a dict of methods this instrument has.
|
|
602
|
+
|
|
603
|
+
Returns:
|
|
604
|
+
dict: Dictionary of method names : list of attributes each method
|
|
605
|
+
has that should be proxied. As of now, this is just its
|
|
606
|
+
docstring, if it has one.
|
|
607
|
+
"""
|
|
608
|
+
out = {}
|
|
609
|
+
|
|
610
|
+
for attr in dir(self):
|
|
611
|
+
value = getattr(self, attr)
|
|
612
|
+
if ((not callable(value)) or
|
|
613
|
+
value is self.parameters.get(attr) or
|
|
614
|
+
value is self.functions.get(attr) or
|
|
615
|
+
attr in self._no_proxy_methods):
|
|
616
|
+
# Functions and Parameters are callable and they show up in
|
|
617
|
+
# dir(), but they have their own listing.
|
|
618
|
+
continue
|
|
619
|
+
|
|
620
|
+
out[attr] = ['__doc__'] if hasattr(value, '__doc__') else []
|
|
621
|
+
|
|
622
|
+
return out
|
|
623
|
+
|
|
624
|
+
def __getstate__(self):
|
|
625
|
+
"""Prevent pickling instruments, and give a nice error message."""
|
|
626
|
+
raise RuntimeError(
|
|
627
|
+
'qcodes Instruments should not be pickled. Likely this means you '
|
|
628
|
+
'were trying to use a local instrument (defined with '
|
|
629
|
+
'server_name=None) in a background Loop. Local instruments can '
|
|
630
|
+
'only be used in Loops with background=False.')
|