# these imports are the standard imports for required for derived lockboxes
from pyrpl.software_modules.lockbox import *
from pyrpl.software_modules.loop import *
# Any InputSignal must define a class that contains the function "expected_signal(variable)" that returns the expected
# signal value as a function of the variable value. This function ensures that the correct setpoint and a reasonable
# gain is chosen (from the derivative of expected_signal) when this signal is used for feedback.
[docs]class CustomLockbox(Lockbox):
""" A custom lockbox class that can be used to implement customized feedback controllers"""
# this syntax for the definition of inputs and outputs allows to conveniently access inputs in the API
inputs = LockboxModuleDictProperty(custom_input_name1=CustomInputClass,
custom_input_name2=CustomInputClass)
outputs = LockboxModuleDictProperty(slow_output=OutputSignal,
fast_output=OutputSignal,
pwm_output=OutputSignal)
# the name of the variable to be stabilized to a setpoint. inputs.expected_signal(variable) returns the expected
# signal as a function of this variable
variable = 'displacement'
# attributes are defined by descriptors
custom_attribute = FloatProperty(default=1.0, increment=0.01, min=1e-5, max=1e5)
# list of attributes that are mandatory to define lockbox state. setup_attributes of all base classes and of all
# submodules are automatically added to the list by the metaclass of Module
_setup_attributes = ["custom_attribute"]
# attributes that are displayed in the gui. _gui_attributes from base classes are also added.
_gui_attributes = ["custom_attribute"]
# if nonstandard units are to be used to specify the gain of the outputs, their conversion to Volts must be defined
# by a property called _unitname_per_V
_mV_per_V = 1000.0
_units = ["V", "mV"]
# overwrite any lockbox functions here or add new ones
[docs] def custom_function(self):
self.calibrate_all()
self.unlock()
self.lock()
# in loop.py:
#class ExampleLoop(LockboxPlotLoop):
# def loop(self):
# self.plot.append(green=np.sin(time()), red=np.cos(time()))
[docs]class ExampleLoop(LockboxPlotLoop): # or inherit from
def __init__(self, parent, name=None):
super(ExampleLoop, self).__init__(parent, name=name)
self.c.n = 0
self.last_texcess = 0
self.result_ready = "not ready"
[docs] def loop(self):
# attention: self.time() is FPGA time, time() is plot-relevant time
self.c.n += 1
tact = time() - self.plot.plot_start_time
tmin = self.interval * self.c.n
texcess = tact - tmin
dt = texcess - self.last_texcess
self.last_texcess = texcess
self.plot.append(green=np.sin(2.0*np.pi*tact*3), #/self.lockbox.interval),
red=dt)
if self.c.n == 100:
self.result = 42
self.result_ready = True
self._clear()
[docs]class ExampleLoopLockbox(Lockbox):
loop = None
_gui_attributes = ["start", "stop", "interval"]
interval = FloatProperty(default=0.01, min=0)
[docs] def start(self):
self.stop()
self.loop = ExampleLoop(parent=self,
name="example_loop",
interval=self.interval)
[docs] def stop(self):
if self.loop is not None:
self.loop._clear()
self.loop = None
[docs]class GalvanicIsolationLoopLockbox(Lockbox):
""" an example for a loop fully described in the lockbox class definition"""
_gui_attributes = ["start_gi", "stop_gi", "gi_interval"]
gi_interval = FloatProperty(default=0.05, min=0, max=1e10,
doc="Minimum interval at which the loop updates the second redpitaya output")
[docs] def start_gi(self):
self.stop_gi()
# start second redpitaya
if not hasattr(self, 'second_pyrpl') or self.second_pyrpl is None:
from pyrpl import Pyrpl
self.second_pyrpl = Pyrpl("second_redpitaya", hostname="_FAKE_REDPITAYA_")
# start loop
self.galvanic_isolation_loop = LockboxLoop(parent=self,
name="galvanic_isolation_loop",
interval=self.gi_interval,
loop_function=self.galvanic_isolation_loop_function)
[docs] def galvanic_isolation_loop_function(self):
""" the loop function to be executed"""
# read an output value from this lockbox and set it as the output of the second redpitaya
self.second_pyrpl.rp.asg0.offset = self.pyrpl.rp.sampler.pid0
# only for debugging:
# self.second_pyrpl.rp.asg0.offset = np.sin(2 * np.pi * time() - self.galvanic_isolation_loop.loop_start_time)
[docs] def stop_gi(self):
if hasattr(self, 'galvanic_isolation_loop') and self.galvanic_isolation_loop is not None:
self.galvanic_isolation_loop._clear()
self.galvanic_isolation_loop = None
[docs]class ShortLoopLockbox(Lockbox):
""" an example for very short loop description"""
[docs] def plot_sin_and_in1(lockbox_self, loop_self):
""" if you pass an instance_method of the lockbox, it should take two arguments:
the instance of the lockbox (self) and the instance of the loop"""
loop_self.plot.append(green=np.sin(2*np.pi*loop_self.time),
red=lockbox_self.pyrpl.rp.sampler.in1)
if loop_self.n > 100: # auto-stop after 1000 cycles
loop_self._clear()
loop = ModuleProperty(LockboxPlotLoop,
interval=0.05,
autostart=True,
loop_function=plot_sin_and_in1)