1
|
|
"""
|
2
|
|
Modules are the basic building blocks of Pyrpl.
|
3
|
|
|
4
|
|
The internal structure of the FPGA is made of individual modules, each
|
5
|
|
performing a well defined task. Each of these FPGA modules are represented
|
6
|
|
in python by a :obj:`HardwareModule`.
|
7
|
|
|
8
|
|
Higher-level operations, for instance those that need a coordinated
|
9
|
|
operation of several HardwareModules is performed by a SoftwareModule,
|
10
|
|
defined in a class derived from :obj:`Module`.
|
11
|
|
|
12
|
|
Thus, all modules (both :obj:`HardwareModule` and Software modules inherit
|
13
|
|
from :obj:`Module` which gives them basic capabilities such as displaying
|
14
|
|
their attributes in the GUI having their state load and saved in the config
|
15
|
|
file.
|
16
|
|
"""
|
17
|
|
|
18
|
3
|
from .attributes import BaseAttribute, ModuleAttribute
|
19
|
3
|
from .widgets.module_widgets import ModuleWidget
|
20
|
3
|
from .curvedb import CurveDB
|
21
|
3
|
from .pyrpl_utils import unique_list, DuplicateFilter
|
22
|
|
|
23
|
3
|
import logging
|
24
|
3
|
import numpy as np
|
25
|
3
|
from six import with_metaclass
|
26
|
3
|
from collections import OrderedDict
|
27
|
3
|
from qtpy import QtCore
|
28
|
|
|
29
|
|
|
30
|
3
|
class SignalLauncher(QtCore.QObject):
|
31
|
|
"""
|
32
|
|
Object that is used to handle signal the emission for a :obj:`Module`.
|
33
|
|
|
34
|
|
A QObject that is connected to the widgets to update their value when
|
35
|
|
attributes of a module change. Any timers needed to implement the module
|
36
|
|
functionality shoud be implemented here as well.
|
37
|
|
"""
|
38
|
3
|
update_attribute_by_name = QtCore.Signal(str, list)
|
39
|
|
# The name of the property that has changed, the list is [new_value],
|
40
|
|
# the new_value of the attribute
|
41
|
3
|
change_options = QtCore.Signal(str, list) # name of the
|
42
|
|
# SelectProperty, list of new options
|
43
|
3
|
refresh_filter_options = QtCore.Signal(str) # name of the
|
44
|
|
# FilterProperty, new options are contained in self.valid_frequencies()
|
45
|
3
|
change_ownership = QtCore.Signal() # The owner of the module has
|
46
|
|
# changed
|
47
|
|
|
48
|
3
|
def __init__(self, module):
|
49
|
3
|
super(SignalLauncher, self).__init__()
|
50
|
3
|
self.module = module
|
51
|
|
|
52
|
3
|
def emit_signal_by_name(self, name, *args, **kwds):
|
53
|
|
"""Emits signal "name" with the specfified args and kwds."""
|
54
|
0
|
signal = getattr(self, name)
|
55
|
0
|
signal.emit(*args, **kwds)
|
56
|
|
|
57
|
3
|
def connect_widget(self, widget):
|
58
|
|
"""
|
59
|
|
Establishes all connections between the module and the widget by name.
|
60
|
|
"""
|
61
|
|
#self.update_attribute_by_name.connect(widget.update_attribute_by_name)
|
62
|
3
|
for key in dir(self.__class__):
|
63
|
3
|
val = getattr(self, key)
|
64
|
3
|
if isinstance(val, QtCore.pyqtBoundSignal) and hasattr(widget,
|
65
|
|
key):
|
66
|
3
|
val.connect(getattr(widget, key))
|
67
|
|
|
68
|
3
|
def _clear(self):
|
69
|
|
""" Destroys the object by disconnecting all signals and by killing all timers"""
|
70
|
0
|
for key in dir(self.__class__):
|
71
|
0
|
val = getattr(self, key)
|
72
|
0
|
if isinstance(val, QtCore.pyqtBoundSignal):
|
73
|
0
|
try:
|
74
|
0
|
val.disconnect()
|
75
|
0
|
except TypeError: # occurs if signal is not connected to anything
|
76
|
0
|
pass
|
77
|
|
|
78
|
|
|
79
|
3
|
class ModuleMetaClass(type):
|
80
|
|
"""
|
81
|
|
Generate Module classes with two features:
|
82
|
|
- __new__ lets attributes know what name they are referred to in the
|
83
|
|
class that contains them.
|
84
|
|
- __new__ also lists all the submodules. This info will be used when
|
85
|
|
instantiating submodules at module instanciation time.
|
86
|
|
- __init__ auto-generates the function setup() and its docstring """
|
87
|
3
|
def __init__(self, classname, bases, classDict):
|
88
|
|
"""
|
89
|
|
Magic to retrieve the name of the attributes in the attributes
|
90
|
|
themselves.
|
91
|
|
see http://code.activestate.com/recipes/577426-auto-named-decriptors/
|
92
|
|
Iterate through the new class' __dict__ and update the .name of all
|
93
|
|
recognised BaseAttribute.
|
94
|
|
|
95
|
|
+ list all submodules attributes
|
96
|
|
|
97
|
|
formerly __init__
|
98
|
|
1. Takes care of adding all submodules attributes to the list
|
99
|
|
self._module_attributes
|
100
|
|
|
101
|
|
2. Takes care of creating 'setup(**kwds)' function of the module.
|
102
|
|
The setup function executes set_attributes(**kwds) and then _setup().
|
103
|
|
|
104
|
|
We cannot use normal inheritance because we want a customized
|
105
|
|
docstring for each module. The docstring is created here by
|
106
|
|
concatenating the module's _setup docstring and individual
|
107
|
|
setup_attribute docstrings.
|
108
|
|
"""
|
109
|
3
|
if classname == 'ModuleContainer':
|
110
|
0
|
pass
|
111
|
|
|
112
|
|
# 0. make all attributes aware of their name in the class containing them
|
113
|
3
|
for name, attr in self.__dict__.items():
|
114
|
3
|
if isinstance(attr, BaseAttribute):
|
115
|
3
|
attr.name = name
|
116
|
|
# 1a. prepare _setup_attributes etc.
|
117
|
3
|
_setup_attributes, _gui_attributes, _module_attributes = [], [], []
|
118
|
|
|
119
|
3
|
for base in reversed(bases): # append all base class _setup_attributes
|
120
|
3
|
try: _setup_attributes += base._setup_attributes
|
121
|
3
|
except AttributeError: pass
|
122
|
3
|
try: _gui_attributes += base._gui_attributes
|
123
|
3
|
except AttributeError: pass
|
124
|
3
|
try: _module_attributes += base._module_attributes
|
125
|
3
|
except AttributeError: pass
|
126
|
3
|
_setup_attributes += self._setup_attributes
|
127
|
3
|
_gui_attributes += self._gui_attributes
|
128
|
|
# 1b. make a list of _module_attributes and add _module_attributes to _setup_attributes
|
129
|
3
|
for name, attr in self.__dict__.items():
|
130
|
3
|
if isinstance(attr, ModuleAttribute):
|
131
|
3
|
_module_attributes.append(name)
|
132
|
3
|
self._module_attributes = unique_list(_module_attributes)
|
133
|
|
# 1c. add _module_attributes to _setup_attributes if the submodule has _setup_attributes
|
134
|
3
|
for name in self._module_attributes:
|
135
|
3
|
attr = getattr(self, name)
|
136
|
1
|
if True: #len(attr.module_cls._setup_attributes) > 0:
|
137
|
3
|
_setup_attributes.append(name)
|
138
|
|
#1d. Set the unique list of _setup_attributes
|
139
|
3
|
self._setup_attributes = unique_list(_setup_attributes)
|
140
|
3
|
self._gui_attributes = unique_list(_gui_attributes)
|
141
|
|
# 2. create setup(**kwds)
|
142
|
3
|
if "setup" not in classDict:
|
143
|
|
# a. generate a setup function
|
144
|
3
|
def setup(self, **kwds):
|
145
|
3
|
self._setup_ongoing = True
|
146
|
3
|
try:
|
147
|
|
# user can redefine any setup_attribute through kwds
|
148
|
3
|
for key in self._setup_attributes:
|
149
|
3
|
if key in kwds:
|
150
|
0
|
value = kwds.pop(key)
|
151
|
0
|
setattr(self, key, value)
|
152
|
3
|
if len(kwds) > 0:
|
153
|
0
|
self._logger.warning(
|
154
|
|
"Trying to load attribute %s of module %s that "
|
155
|
|
"are invalid setup_attributes.",
|
156
|
|
sorted(kwds.keys())[0], self.name)
|
157
|
3
|
if hasattr(self, '_setup'):
|
158
|
3
|
self._setup()
|
159
|
|
finally:
|
160
|
3
|
self._setup_ongoing = False
|
161
|
|
# b. place the new setup function in the module class
|
162
|
3
|
self.setup = setup
|
163
|
|
# 3. if setup has no docstring, then make one
|
164
|
3
|
self.make_setup_docstring(classDict)
|
165
|
|
# 4. make the new class
|
166
|
|
#return super(ModuleMetaClass, cls).__new__(cls, classname, bases, classDict)
|
167
|
|
|
168
|
|
#@classmethod
|
169
|
3
|
def make_setup_docstring(self, classDict):
|
170
|
|
"""
|
171
|
|
Returns a docstring for the function 'setup' that is composed of:
|
172
|
|
- the '_setup' docstring
|
173
|
|
- the list of all setup_attributes docstrings
|
174
|
|
"""
|
175
|
|
# get initial docstring (python 2 and python 3 syntax)
|
176
|
3
|
try: doc = self._setup.__doc__ + '\n'
|
177
|
3
|
except:
|
178
|
3
|
try: doc = self._setup.__func__.__doc__ + '\n'
|
179
|
3
|
except: doc = ""
|
180
|
3
|
doc += "attributes\n=========="
|
181
|
3
|
for attr_name in self._setup_attributes:
|
182
|
3
|
attr = getattr(self, attr_name)
|
183
|
3
|
doc += "\n " + attr_name + ": " + attr.__doc__
|
184
|
3
|
setup = self.setup
|
185
|
|
# docstring syntax differs between python versions. Python 2:
|
186
|
3
|
if hasattr(setup, "__func__"):
|
187
|
1
|
setup.__func__.__doc__ = doc
|
188
|
|
# ... python 3
|
189
|
2
|
elif hasattr(setup, '__doc__'):
|
190
|
2
|
setup.__doc__ = doc
|
191
|
|
|
192
|
|
|
193
|
3
|
class DoSetup(object):
|
194
|
|
"""
|
195
|
|
A context manager that allows to nicely write Module setup functions.
|
196
|
|
|
197
|
|
Usage example in :py:meth:`Module._setup()`::
|
198
|
|
|
199
|
|
def _setup(self):
|
200
|
|
# _setup_ongoing is False by default
|
201
|
|
assert self._setup_ongoing == False
|
202
|
|
with self.do_setup:
|
203
|
|
# now _setup_ongoing is True
|
204
|
|
assert self._setup_ongoing == True
|
205
|
|
# do stuff that might fail
|
206
|
|
raise BaseException()
|
207
|
|
# even if _setup fails, _setup_ongoing is False afterwards or in
|
208
|
|
# the next call to _setup()
|
209
|
|
assert self._setup_ongoing == False
|
210
|
|
"""
|
211
|
3
|
def __init__(self, parent):
|
212
|
3
|
self.parent = parent
|
213
|
|
|
214
|
3
|
def __enter__(self):
|
215
|
0
|
self.parent._setup_ongoing = True
|
216
|
|
|
217
|
3
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
218
|
0
|
self.parent._setup_ongoing = False
|
219
|
0
|
if exc_type is not None:
|
220
|
0
|
self.parent._logger.warning("Exception %s was raised while "
|
221
|
|
"_setup_ongoing was True: %s, %s",
|
222
|
|
exc_type, exc_val, exc_tb)
|
223
|
|
|
224
|
|
|
225
|
3
|
class Module(with_metaclass(ModuleMetaClass, object)):
|
226
|
|
# The Syntax for defining a metaclass changed from Python 2 to 3.
|
227
|
|
# with_metaclass is compatible with both versions and roughly does this:
|
228
|
|
# def with_metaclass(meta, *bases):
|
229
|
|
# """Create a base class with a metaclass."""
|
230
|
|
# return meta("NewBase", bases, {})
|
231
|
|
# Specifically, ModuleMetaClass ensures that attributes have automatically
|
232
|
|
# their internal name set properly upon module creation.
|
233
|
|
"""
|
234
|
|
A module is a component of pyrpl doing a specific task.
|
235
|
|
|
236
|
|
Module is the base class for instruments such as the
|
237
|
|
Scope/Lockbox/NetworkAnalyzer. A module can have a widget to build a
|
238
|
|
graphical user interface on top of it.
|
239
|
|
It is composed of attributes (see attributes.py) whose values represent
|
240
|
|
the current state of the module (more precisely, the state is defined
|
241
|
|
by the value of all attributes in _setup_attributes)
|
242
|
|
The module can be slaved or freed by a user or another module. When the
|
243
|
|
module is freed, it goes back to the state immediately before being
|
244
|
|
slaved. To make sure the module is freed, use the syntax::
|
245
|
|
|
246
|
|
with pyrpl.mod_mag.pop('owner') as mod:
|
247
|
|
mod.do_something()
|
248
|
|
mod.do_something_else()
|
249
|
|
|
250
|
|
Attributes:
|
251
|
|
`get_setup_attributes()`: returns a dict with the current values of
|
252
|
|
the setup attributes
|
253
|
|
``set_setup_attributes(**kwds)``: sets the provided setup_attributes
|
254
|
|
(setup is not called)
|
255
|
|
`save_state(name)`: saves the current 'state' (using
|
256
|
|
get_setup_attribute) into the config file
|
257
|
|
`load_state(name)`: loads the state 'name' from the config file (setup
|
258
|
|
is not called by default)
|
259
|
|
`erase_state(name)`: erases state 'name' from config file
|
260
|
|
`create_widget()`: returns a widget according to widget_class
|
261
|
|
``setup(**kwds)``: first, performs :code:`set_setup_attributes(**kwds)`,
|
262
|
|
then calls _setup() to set the module ready for acquisition. This
|
263
|
|
method is automatically created by ModuleMetaClass and it combines the
|
264
|
|
docstring of individual setup_attributes with the docstring of _setup()
|
265
|
|
`free()`: sets the module owner to None, and brings the module back the
|
266
|
|
state before it was slaved equivalent to module.owner = None)
|
267
|
|
`get_yml(state=None)`: get the yml code representing the state "state'
|
268
|
|
or the current state if state is None
|
269
|
|
`set_yml(yml_content, state=None)`: sets the state "state" with the
|
270
|
|
content of yml_content. If state is None, the state is directly loaded
|
271
|
|
into the module.
|
272
|
|
`name`: attributed based on name at instance creation
|
273
|
|
(also used as a section key in the config file)
|
274
|
|
`states (list)`: the list of states available in the config file
|
275
|
|
`owner (string)`: a module can be owned (reserved) by a user or another
|
276
|
|
module. The module is free if and only if owner is None
|
277
|
|
`pyrpl` (:obj:`Pyrpl`): recursively looks through parent modules until it
|
278
|
|
reaches the Pyrpl instance
|
279
|
|
|
280
|
|
Class attributes to be implemented in derived class:
|
281
|
|
|
282
|
|
- all individual attributes (instances of BaseAttribute)
|
283
|
|
- _setup_attributes: attribute names that are touched by setup(**kwds)/
|
284
|
|
saved/restored upon module creation
|
285
|
|
- _gui_attributes: attribute names to be displayed by the widget
|
286
|
|
- _callback_attributes: attribute_names that triggers a callback when
|
287
|
|
their value is changed in the base class, _callback just calls setup()
|
288
|
|
- _widget_class: class of the widget to use to represent the module in
|
289
|
|
the gui(a child of ModuleWidget)
|
290
|
|
|
291
|
|
Methods to implement in derived class:
|
292
|
|
|
293
|
|
- _setup(): sets the module ready for acquisition/output with the
|
294
|
|
current attribute's values. The metaclass of the module autogenerates a
|
295
|
|
function like this::
|
296
|
|
|
297
|
|
def setup(self, **kwds):
|
298
|
|
\"\"\"
|
299
|
|
_ docstring is the result of the following pseudocode: _
|
300
|
|
print(DOCSTRING_OF_FUNCTION("_setup"))
|
301
|
|
for attribute in self.setup_attributes:
|
302
|
|
print(DOCSTRING_OF_ATTRIBUTE(attribute))
|
303
|
|
\"\"\"
|
304
|
|
self.set_setup_attributes(kwds)
|
305
|
|
return self._setup()
|
306
|
|
|
307
|
|
- _ownership_changed(old, new): this function is called when the module
|
308
|
|
owner changes it can be used to stop the acquisition for instance.
|
309
|
|
"""
|
310
|
|
|
311
|
|
# Change this to provide a custom graphical class
|
312
|
3
|
_widget_class = ModuleWidget
|
313
|
|
|
314
|
|
# the class for the SignalLauncher to be used
|
315
|
|
# a QOBject used to communicate with the widget
|
316
|
3
|
_signal_launcher = SignalLauncher
|
317
|
|
|
318
|
|
# attributes listed here will be saved in the config file everytime they
|
319
|
|
# are updated.
|
320
|
3
|
_setup_attributes = []
|
321
|
|
|
322
|
|
# class inheriting from ModuleWidget can
|
323
|
|
# automatically generate gui from a list of attributes
|
324
|
3
|
_gui_attributes = []
|
325
|
|
|
326
|
|
# This flag is used to desactivate callback during setup
|
327
|
3
|
_setup_ongoing = False
|
328
|
|
|
329
|
|
# internal memory for owner of the module (to avoid conflicts)
|
330
|
3
|
_owner = None
|
331
|
|
|
332
|
|
# name of the module, metaclass automatically assigns one per instance
|
333
|
3
|
name = None
|
334
|
|
|
335
|
3
|
def __init__(self, parent, name=None):
|
336
|
|
"""
|
337
|
|
Creates a module with given name. If name is None, cls.name is
|
338
|
|
assigned by the metaclass.
|
339
|
|
|
340
|
|
Parent is either
|
341
|
|
- a pyrpl instance: config file entry is in
|
342
|
|
(self.__class__.name + 's').(self.name)
|
343
|
|
- or another SoftwareModule: config file entry is in
|
344
|
|
(parent_entry).(self.__class__.name + 's').(self.name)
|
345
|
|
"""
|
346
|
3
|
if name is not None:
|
347
|
3
|
self.name = name
|
348
|
3
|
self.do_setup = DoSetup(self) # ContextManager for _setup_ongoing
|
349
|
3
|
self._flag_autosave_active = True # I would have prefered to use
|
350
|
|
# __autosave_active, but this gets automatically name mangled:
|
351
|
|
# see http://stackoverflow.com/questions/1301346/what-is-the-meaning-of-a-single-and-a-double-underscore-before-an-object-name
|
352
|
3
|
self._logger = logging.getLogger(name=__name__)
|
353
|
3
|
self._logger.addFilter(DuplicateFilter())
|
354
|
|
# create the signal launcher object from its class
|
355
|
3
|
self._signal_launcher = self._signal_launcher(self)
|
356
|
3
|
self.parent = parent
|
357
|
|
# disable autosave during initialization
|
358
|
3
|
self._autosave_active = False
|
359
|
|
# instantiate modules associated with _module_attribute by calling their getter
|
360
|
3
|
for submodule in self._module_attributes:
|
361
|
3
|
getattr(self, submodule)
|
362
|
|
# custom module initialization hook
|
363
|
|
# self._init_module()
|
364
|
|
# enable autosave and load last state from config file
|
365
|
3
|
self._autosave_active = True
|
366
|
|
# Only top level modules should call _load_setup_attributes() since
|
367
|
|
# this call propagates through all child modules
|
368
|
|
##if not isinstance(self.parent, Module):
|
369
|
|
## # attributes are loaded but _setup() is not called
|
370
|
|
## self._load_setup_attributes()
|
371
|
|
|
372
|
3
|
def _init_module(self):
|
373
|
|
"""
|
374
|
|
To implement in child class if needed.
|
375
|
|
"""
|
376
|
0
|
self._logger.warning("Function _init_module is obsolete and will be "
|
377
|
|
"removed soon. Please migrate the corresponding "
|
378
|
|
"code to __init__.")
|
379
|
|
|
380
|
3
|
@property
|
381
|
|
def _autosave_active(self):
|
382
|
|
"""
|
383
|
|
:return: If an ancestor of the current module is NOT autosaving, then
|
384
|
|
the current module is not saving either.
|
385
|
|
"""
|
386
|
3
|
try:
|
387
|
3
|
parent_autosave_active = self.parent._autosave_active
|
388
|
3
|
except AttributeError: # some parents do not implement the autosave flag
|
389
|
3
|
parent_autosave_active = True
|
390
|
3
|
return self._flag_autosave_active and parent_autosave_active
|
391
|
|
|
392
|
3
|
@_autosave_active.setter
|
393
|
|
def _autosave_active(self, val):
|
394
|
|
"""
|
395
|
|
Only takes effect when all ancestor are autosaving
|
396
|
|
:param val:
|
397
|
|
:return:
|
398
|
|
"""
|
399
|
3
|
self._flag_autosave_active = val
|
400
|
|
|
401
|
3
|
@property
|
402
|
|
def _modules(self):
|
403
|
0
|
return dict([(key, getattr(self, key)) for key in
|
404
|
|
self._module_attributes])
|
405
|
|
|
406
|
3
|
@property
|
407
|
|
def pyrpl(self):
|
408
|
|
"""
|
409
|
|
Recursively looks through patent modules untill pyrpl instance is
|
410
|
|
reached.
|
411
|
|
"""
|
412
|
0
|
from .pyrpl import Pyrpl
|
413
|
0
|
parent = self.parent
|
414
|
0
|
while (not isinstance(parent, Pyrpl)):
|
415
|
0
|
parent = parent.parent
|
416
|
0
|
return parent
|
417
|
|
|
418
|
3
|
def get_setup_attributes(self):
|
419
|
|
"""
|
420
|
|
Returns a dict with the current values of the setup attributes.
|
421
|
|
|
422
|
|
Recursively calls get_setup_attributes for sub_modules and assembles
|
423
|
|
a hierarchical dictionary.
|
424
|
|
|
425
|
|
Returns:
|
426
|
|
dict: contains setup_attributes and their current values.
|
427
|
|
"""
|
428
|
0
|
self._logger.warning("get_setup_attributes is deprecated. Use property setup_attributes instead. ")
|
429
|
0
|
return self.setup_attributes
|
430
|
|
|
431
|
3
|
@property
|
432
|
|
def setup_attributes(self):
|
433
|
|
"""
|
434
|
|
:return: a dict with the current values of the setup attributes.
|
435
|
|
Recursively collects setup_attributes for sub_modules.
|
436
|
|
"""
|
437
|
0
|
kwds = OrderedDict()
|
438
|
0
|
for attr in self._setup_attributes:
|
439
|
0
|
val = getattr(self, attr)
|
440
|
0
|
if attr in self._modules:
|
441
|
0
|
val = val.setup_attributes
|
442
|
0
|
kwds[attr] = val
|
443
|
0
|
return kwds
|
444
|
|
|
445
|
3
|
def set_setup_attributes(self, **kwds):
|
446
|
|
"""
|
447
|
|
Sets the values of the setup attributes. Without calling any callbacks
|
448
|
|
"""
|
449
|
0
|
self._logger.warning("set_setup_attributes is deprecated. Use property setup_attributes instead. ")
|
450
|
0
|
self.setup_attributes = kwds
|
451
|
|
|
452
|
3
|
@setup_attributes.setter
|
453
|
|
def setup_attributes(self, kwds):
|
454
|
|
"""
|
455
|
|
Sets the values of the setup attributes.
|
456
|
|
"""
|
457
|
0
|
self.setup(**kwds)
|
458
|
|
|
459
|
3
|
def _load_setup_attributes(self):
|
460
|
|
"""
|
461
|
|
Load and sets all setup attributes from config file
|
462
|
|
"""
|
463
|
|
# self.c = None switches off loading states (e.g. for ModuleManagers).
|
464
|
|
# First part of the if avoids creating an empty branch in the
|
465
|
|
# config file at the call of this function at startup.
|
466
|
0
|
if (self.name in self.parent.c) and (self.c is not None):
|
467
|
|
# pick those elements of the config state that are setup_attributes
|
468
|
0
|
dic = {k: v for k, v in self.c._data.items() if k in self._setup_attributes}
|
469
|
|
# set those elements
|
470
|
0
|
self.setup_attributes = dic
|
471
|
|
|
472
|
3
|
@property
|
473
|
|
def c(self):
|
474
|
|
"""
|
475
|
|
Returns a MemoryBranch object used for storing data in the configuration file.
|
476
|
|
|
477
|
|
The branch corresponding to the module is a subbranch of the parent module's branch with the name of the module.
|
478
|
|
"""
|
479
|
0
|
return self.parent.c._get_or_create(self.name)
|
480
|
|
|
481
|
3
|
@property
|
482
|
|
def _states(self):
|
483
|
|
"""
|
484
|
|
Returns the config file branch corresponding to the saved states of the module.
|
485
|
|
"""
|
486
|
0
|
return self.c._root._get_or_create(self.name + "_states")
|
487
|
|
|
488
|
3
|
@property
|
489
|
|
def states(self):
|
490
|
|
"""
|
491
|
|
Returns the names of all saved states of the module.
|
492
|
|
"""
|
493
|
|
# the if avoids creating an empty states section for all modules
|
494
|
0
|
if (self.name + "_states") in self.parent.c._root._data:
|
495
|
0
|
return list(self._states._keys())
|
496
|
|
else:
|
497
|
0
|
return []
|
498
|
|
|
499
|
3
|
def save_state(self, name=None):
|
500
|
|
"""
|
501
|
|
Saves the current state under the name "name" in the config file. If
|
502
|
|
state_section is left unchanged, uses the normal
|
503
|
|
class_section.states convention.
|
504
|
|
"""
|
505
|
0
|
if name is None:
|
506
|
0
|
self.setup_attributes = self.setup_attributes
|
507
|
|
else:
|
508
|
0
|
self._states[name] = self.setup_attributes
|
509
|
|
|
510
|
3
|
def load_state(self, name=None):
|
511
|
|
"""
|
512
|
|
Loads the state with name "name" from the config file. If
|
513
|
|
state_branch is left unchanged, uses the normal
|
514
|
|
class_section.states convention.
|
515
|
|
"""
|
516
|
0
|
if name is None:
|
517
|
0
|
self.setup_attributes = self.c._data
|
518
|
|
else:
|
519
|
0
|
self.setup_attributes = self._states[name]._data
|
520
|
|
|
521
|
3
|
def erase_state(self, name):
|
522
|
|
"""
|
523
|
|
Removes the state "name' from the config file
|
524
|
|
:param name: name of the state to erase
|
525
|
|
:return: None
|
526
|
|
"""
|
527
|
0
|
self._states[name]._erase()
|
528
|
|
|
529
|
3
|
def get_yml(self, state=None):
|
530
|
|
"""
|
531
|
|
:param state: The name of the state to inspect. If state is None-->
|
532
|
|
then, use the current instrument state.
|
533
|
|
:return: a string containing the yml code
|
534
|
|
"""
|
535
|
0
|
if state is None:
|
536
|
0
|
return self.c._get_yml()
|
537
|
|
else:
|
538
|
0
|
return self._states[state]._get_yml()
|
539
|
|
|
540
|
3
|
def set_yml(self, yml_content, state=None):
|
541
|
|
"""
|
542
|
|
:param yml_content: some yml code to encode the module content.
|
543
|
|
:param state: The name of the state to set. If state is None-->
|
544
|
|
then, use the current instrument state and reloads it immediately
|
545
|
|
:return: None
|
546
|
|
"""
|
547
|
0
|
if state is None:
|
548
|
0
|
self.c._set_yml(yml_content)
|
549
|
0
|
self._load_setup_attributes()
|
550
|
|
else:
|
551
|
0
|
self._states._get_or_create(state)._set_yml(yml_content)
|
552
|
|
|
553
|
3
|
def _save_curve(self, x_values, y_values, **attributes):
|
554
|
|
"""
|
555
|
|
Saves a curve in some database system.
|
556
|
|
To change the database system, overwrite this function
|
557
|
|
or patch Module.curvedb if the interface is identical.
|
558
|
|
|
559
|
|
:param x_values: numpy array with x values
|
560
|
|
:param y_values: numpy array with y values
|
561
|
|
:param attributes: extra curve parameters (such as relevant module
|
562
|
|
settings)
|
563
|
|
"""
|
564
|
0
|
curve = CurveDB.create(x_values,
|
565
|
|
y_values,
|
566
|
|
**attributes)
|
567
|
0
|
return curve
|
568
|
|
|
569
|
3
|
def free(self):
|
570
|
|
"""
|
571
|
|
Change ownership to None
|
572
|
|
"""
|
573
|
0
|
self.owner = None
|
574
|
|
|
575
|
3
|
def _setup(self):
|
576
|
|
"""
|
577
|
|
Sets the module up for acquisition with the current setup attribute
|
578
|
|
values.
|
579
|
|
"""
|
580
|
0
|
pass
|
581
|
|
|
582
|
|
# def help(self, register=''):
|
583
|
|
# """returns the docstring of the specified register name
|
584
|
|
# if register is an empty string, all available docstrings are
|
585
|
|
# returned"""
|
586
|
|
# if register:
|
587
|
|
# string = type(self).__dict__[register].__doc__
|
588
|
|
# return string
|
589
|
|
# else:
|
590
|
|
# string = ""
|
591
|
|
# for key in type(self).__dict__.keys():
|
592
|
|
# if isinstance(type(self).__dict__[key], BaseAttribute):
|
593
|
|
# docstring = self.help(key)
|
594
|
|
# # mute internal registers
|
595
|
|
# if not docstring.startswith('_'):
|
596
|
|
# string += key + ": " + docstring + '\r\n\r\n'
|
597
|
|
# return string
|
598
|
3
|
def help(self, register=''):
|
599
|
0
|
return "Please refer to the docstring of the function setup() or " \
|
600
|
|
"to the manual for further help! "
|
601
|
|
|
602
|
3
|
def _create_widget(self):
|
603
|
|
"""
|
604
|
|
Creates the widget specified in widget_class.
|
605
|
|
"""
|
606
|
0
|
if self._widget_class is None:
|
607
|
0
|
self._logger.warning("Module %s of type %s is trying to create a widget, but no widget_class is defined!",
|
608
|
|
self.name, type(self))
|
609
|
0
|
return None
|
610
|
0
|
try:
|
611
|
0
|
widget = self._widget_class(self.name, self)
|
612
|
|
finally:
|
613
|
0
|
pass
|
614
|
0
|
self._module_widget = widget # For debugging purpose only (all
|
615
|
|
# communications to the widget should happen via signals)
|
616
|
0
|
return widget
|
617
|
|
|
618
|
3
|
@property
|
619
|
|
def owner(self):
|
620
|
0
|
return self._owner
|
621
|
|
|
622
|
3
|
@owner.setter
|
623
|
|
def owner(self, val):
|
624
|
|
"""
|
625
|
|
Changing module ownership automagically:
|
626
|
|
- changes the visibility of the module_widget in the gui
|
627
|
|
- re-setups the module with the module attributes in the config-file
|
628
|
|
if new ownership is None
|
629
|
|
"""
|
630
|
0
|
old = self.owner
|
631
|
0
|
self._owner = val
|
632
|
0
|
if val is None:
|
633
|
0
|
self._autosave_active = True
|
634
|
|
else:
|
635
|
|
# deactivate autosave for slave modules
|
636
|
0
|
self._autosave_active = False
|
637
|
0
|
self._ownership_changed(old, val)
|
638
|
0
|
if val is None:
|
639
|
0
|
self._load_setup_attributes()
|
640
|
|
# self.set_setup_attributes(**self.c._dict)
|
641
|
|
# using the same dict will create a reference (&id) in the
|
642
|
|
# config file for submodules --> That is probably a bug that
|
643
|
|
# could be solved by making a copy of the dict somewhere in
|
644
|
|
# memory.py, but on the other hand we are not supposed to use
|
645
|
|
# anything but the public API of memory.py
|
646
|
0
|
self._signal_launcher.change_ownership.emit()
|
647
|
|
|
648
|
3
|
def _ownership_changed(self, old, new):
|
649
|
0
|
pass
|
650
|
|
|
651
|
3
|
def __enter__(self):
|
652
|
|
"""
|
653
|
|
This function is executed in the context manager construct with
|
654
|
|
... as ... :
|
655
|
|
"""
|
656
|
0
|
return self
|
657
|
|
|
658
|
3
|
def __exit__(self, type, val, traceback):
|
659
|
|
"""
|
660
|
|
To make sure the module will be freed afterwards, use the context
|
661
|
|
manager construct:
|
662
|
|
with pyrpl.module_manager.pop('owner') as mod:
|
663
|
|
mod.do_something()
|
664
|
|
# module automatically freed at this point
|
665
|
|
|
666
|
|
The free operation is performed in this function
|
667
|
|
see http://stackoverflow.com/questions/1369526/what-is-the-python-keyword-with-used-for
|
668
|
|
"""
|
669
|
0
|
self.owner = None
|
670
|
|
|
671
|
3
|
def _clear(self):
|
672
|
|
"""
|
673
|
|
Kill timers and free resources for this module and all submodules.
|
674
|
|
"""
|
675
|
0
|
self._signal_launcher._clear()
|
676
|
0
|
for sub in self._modules:
|
677
|
0
|
getattr(self, sub)._clear()
|
678
|
|
|
679
|
|
|
680
|
3
|
class HardwareModule(Module):
|
681
|
|
"""
|
682
|
|
Module that directly maps a FPGA module. In addition to BaseModule's
|
683
|
|
requirements, HardwareModule classes must have the following class
|
684
|
|
attributes:
|
685
|
|
|
686
|
|
- addr_base (int): the base address of the module, such as 0x40300000
|
687
|
|
"""
|
688
|
|
|
689
|
3
|
parent = None # parent will be redpitaya instance
|
690
|
|
|
691
|
3
|
def __init__(self, parent, name=None):
|
692
|
|
""" Creates the prototype of a RedPitaya Module interface
|
693
|
|
|
694
|
|
if no name provided, will use cls.name
|
695
|
|
"""
|
696
|
0
|
self._client = parent.client
|
697
|
0
|
self._addr_base = self.addr_base
|
698
|
0
|
self._rp = parent
|
699
|
0
|
super(HardwareModule, self).__init__(parent, name=name)
|
700
|
0
|
self.__doc__ = "Available registers: \r\n\r\n" + self.help()
|
701
|
|
|
702
|
3
|
def _ownership_changed(self, old, new):
|
703
|
|
"""
|
704
|
|
This hook is there to make sure any ongoing measurement is stopped when
|
705
|
|
the module gets slaved
|
706
|
|
|
707
|
|
old: name of old owner (eventually None)
|
708
|
|
new: name of new owner (eventually None)
|
709
|
|
"""
|
710
|
0
|
pass
|
711
|
|
|
712
|
3
|
@property
|
713
|
|
def _frequency_correction(self):
|
714
|
|
"""
|
715
|
|
factor to manually compensate 125 MHz oscillator frequency error
|
716
|
|
real_frequency = 125 MHz * _frequency_correction
|
717
|
|
"""
|
718
|
0
|
try:
|
719
|
0
|
return self._rp.frequency_correction
|
720
|
0
|
except AttributeError:
|
721
|
0
|
self._logger.warning("Warning: Parent of %s has no attribute "
|
722
|
|
"'frequency_correction'. ", self.name)
|
723
|
0
|
return 1.0
|
724
|
|
|
725
|
3
|
def _reads(self, addr, length):
|
726
|
0
|
return self._client.reads(self._addr_base + addr, length)
|
727
|
|
|
728
|
3
|
def _writes(self, addr, values):
|
729
|
0
|
self._client.writes(self._addr_base + addr, values)
|
730
|
|
|
731
|
3
|
def _read(self, addr):
|
732
|
0
|
return int(self._reads(addr, 1)[0])
|
733
|
|
|
734
|
3
|
def _write(self, addr, value):
|
735
|
0
|
self._writes(addr, [int(value)])
|
736
|
|
|
737
|
3
|
def _to_pyint(self, v, bitlength=14):
|
738
|
0
|
v = v & (2 ** bitlength - 1)
|
739
|
0
|
if v >> (bitlength - 1):
|
740
|
0
|
v = v - 2 ** bitlength
|
741
|
0
|
return int(v)
|
742
|
|
|
743
|
3
|
def _from_pyint(self, v, bitlength=14):
|
744
|
0
|
v = int(v)
|
745
|
0
|
if v < 0:
|
746
|
0
|
v = v + 2 ** bitlength
|
747
|
0
|
v = (v & (2 ** bitlength - 1))
|
748
|
0
|
return np.uint32(v)
|
749
|
|
|
750
|
|
|
751
|
3
|
class SignalModule(Module):
|
752
|
|
""" any module that can be passed as an input to another module"""
|
753
|
3
|
def signal(self):
|
754
|
0
|
return self.name
|