def __init__(self,laser,hostname,config='relocker',gui=False): self.laser = laser self.p = Pyrpl(hostname=hostname,config=config,gui=gui)#,modules=[]) # self.p.hide_gui() self.rp = self.p.rp self.scope = self.rp.scope self.scope_queue = queue.Queue() self.scope_queue_wait = QtCore.QWaitCondition() self.scope_queue_mutex = QtCore.QMutex() self.scope_queuer = ScopeQueuer(self.scope_queue,self.scope_queue_wait,self.scope_queue_mutex) self.scope_queuer.start() #self.scope_queuer.signal.connect(partial(self.set_scope_duration)) self.scope_queuer.signal.connect(self.get_scope_trace)
def init(self): """ The init function does not override __init__. We load 'pyrpl' inside 'init' such that it only exists in worker process and not within BLACS process. """ # Import Leonhard Neuhaus PYRPL for Red Pitaya # https://pyrpl.readthedocs.io/en/latest/ # Import inside 'init' such that PYRPL exists only inside worker process from pyrpl import Pyrpl # Create PYRPL object connecting to Red Pitaya with given IP address # No config file self.connection = Pyrpl(hostname=self.ip_addr, config="") # Turn on LED 0 to indicate that PYRPl is set up self.connection.rp.hk.led = 0b00000001 # Hide PYRPl GUI self.connection.hide_gui() # Prevent timeout of connection self.connection.rp.client.socket.settimeout(None) # print("timeout = ", self.connection.rp.client.socket.timeout) # Set default values for ASG0 attributes self.waveform = 'dc' self.frequency = 0.0 self.amplitude = self.__out_amp_volt_conv(0.0) self.offset = self.__offset_calib(self.__out_amp_volt_conv(0.0)) self.cycles_per_burst = 0 # Initialize analog outputs 'out1' to zero # given that we are using a modified RP with output 0-2V self.connection.rp.asg0.output_direct = 'out1' self.connection.rp.asg0.setup(waveform=self.waveform, frequency=self.frequency, offset=self.offset, trigger_source='immediately', cycles_per_burst=self.cycles_per_burst) # Each shot, we will remember the shot file # for the duration of that shot self.shot_file = None
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)
class rp_lockbox(): """Wrapper class to make PyRPL functions easily accessible""" def __init__(self, hostname, config='relocker', gui=False): self.p = Pyrpl(hostname=hostname, config=config, gui=gui) #,modules=[]) self.p.hide_gui() self.rp = self.p.rp def hide_gui(self): self.p.hide_gui() def show_gui(self): self.p.show_gui() def get_pid_object(self, laser_index): if laser_index == 0: pid = self.rp.pid0 elif laser_index == 1: pid = self.rp.pid1 elif laser_index == 2: pid = self.rp.pid2 else: print("RP does not support pid > 2") return pid def get_asg_object(self, laser_index): if laser_index == 0: asg = self.rp.asg0 elif laser_index == 1: asg = self.rp.asg1 else: print("RP does not support asg > 1") return asg
def __init__(self, host: str = "192.168.1.222"): """ instantiates the object p = Pyrpl that opens a connection to the red Pitaya and choose the configuration. when you install package Pyrpl and instantiate the class Pyrpl, a window will open and ask you to enter on bottom the RP's IP address. The with the arg "config" it will keep your config in the file "global_config" and will use it every time. you do not need to change something. Parameters ---------- host : IP address of the red Pitaya """ self.host: str = host try: self.p = Pyrpl(config="global_config") except: print("error could't connect to redpitaya") # value that calibrate the value entered in the Signal Views # it is depending on the amplifier you're using. self.voltage_calibration: float = 1.2888608208696757 / 11.7
def __init__(self, hostname, config='relocker', gui=False): self.p = Pyrpl(hostname=hostname, config=config, gui=gui) #,modules=[]) self.p.hide_gui() self.rp = self.p.rp
class red_pitaya_pyrpl_asg_worker(Worker): """ The class 'red_pitaya_pyrpl_asg_worker' subclasses 'Worker' and handles all the communication with hardware of the Red Pitaya via the PYRPL API. The worker class is instantiated as separate process, which can be restarted by the user if the RP becomes unresponsive. For more information see: 'labscript_suite/blacs/docs/How\ to\ add\ a\ new\ device' """ def init(self): """ The init function does not override __init__. We load 'pyrpl' inside 'init' such that it only exists in worker process and not within BLACS process. """ # Import Leonhard Neuhaus PYRPL for Red Pitaya # https://pyrpl.readthedocs.io/en/latest/ # Import inside 'init' such that PYRPL exists only inside worker process from pyrpl import Pyrpl # Create PYRPL object connecting to Red Pitaya with given IP address # No config file self.connection = Pyrpl(hostname=self.ip_addr, config="") # Turn on LED 0 to indicate that PYRPl is set up self.connection.rp.hk.led = 0b00000001 # Hide PYRPl GUI self.connection.hide_gui() # Prevent timeout of connection self.connection.rp.client.socket.settimeout(None) # print("timeout = ", self.connection.rp.client.socket.timeout) # Set default values for ASG0 attributes self.waveform = 'dc' self.frequency = 0.0 self.amplitude = self.__out_amp_volt_conv(0.0) self.offset = self.__offset_calib(self.__out_amp_volt_conv(0.0)) self.cycles_per_burst = 0 # Initialize analog outputs 'out1' to zero # given that we are using a modified RP with output 0-2V self.connection.rp.asg0.output_direct = 'out1' self.connection.rp.asg0.setup(waveform=self.waveform, frequency=self.frequency, offset=self.offset, trigger_source='immediately', cycles_per_burst=self.cycles_per_burst) # Each shot, we will remember the shot file # for the duration of that shot self.shot_file = None def __out_amp_volt_conv(self, amp_out): """ Private method to handle the conversion of the output of the amplifier [-peak_volt, peak_volt]V to [-1,1]V output values of PYRPL. The optional -1V offset in the amplifier can compensate for the [0, 2V] output of the modified Red Pitaya. """ gain = self.peak_volt rp_out = amp_out / gain return rp_out def __offset_calib(self, offset): """ Private method to calibrate the RP analog out and introduce a calibration offset voltage. """ rp_calib_offset = offset + self.offset_calib return rp_calib_offset def program_manual(self, values): """ Not used, but needs to be defined. Is used, when a value of a digital, analog or DDS output widget on the front panel is changed. However, we don't use these widgets in the device Tab. """ return {} def transition_to_buffered(self, device_name, hdf5_file, initial_values, fresh): """ State-function 'transition_to_buffered' is called, when Queue Manager requests the RP to move into buffered mode in preparation for executing a buffered sequence. The PYRPL asg0 is set up according to the parameters given as device_properties. """ # Turn on LED 2 to indicate that PYRPl is in # transition_to_buffered_state self.connection.rp.hk.led = 0b00000100 # Set up attributes according to device_properties in shot file self.shot_file = hdf5_file with h5py.File(self.shot_file, 'r') as f: group = f['devices/{}'.format(self.device_name)] if 'out_values' in group: out_values = group['out_values'][:] else: out_values = None if 'total_duration' in group: print(group['total_duration']) frequency = 1 / group['total_duration'][()] else: frequency = None # Frequency is more natural to PYRPL API self.frequency = frequency self.amplitude = self.__out_amp_volt_conv(self.peak_volt) self.offset = self.__offset_calib(self.__out_amp_volt_conv(0.0)) self.cycles_per_burst = 1 self.connection.rp.asg0.setup(frequency=self.frequency, amplitude=self.amplitude, offset=self.offset, trigger_source='ext_positive_edge', cycles_per_burst=self.cycles_per_burst) # Send arbitrary evaporation_ramp to Red Pitaya self.connection.rp.asg0.data = self.__out_amp_volt_conv(out_values) # Return empty dictionary due to empty channels return {} def transition_to_manual(self): """ The state-function 'transition_to_manual' places the device back in the correct mode for operation by the front panel. """ # Turn on LED 1 to indicate that PYRPl is in transition_to_manual self.connection.rp.hk.led = 0b00000010 # Set default values for ASG0 attributes in manual mode # And forget the values in buffered mode self.waveform = 'dc' self.frequency = 0.0 self.amplitude = self.__out_amp_volt_conv(0.0) self.offset = self.__offset_calib(self.__out_amp_volt_conv(0.0)) self.cycles_per_burst = 0 self.connection.rp.asg0.setup(waveform=self.waveform, frequency=self.frequency, amplitude=self.amplitude, offset=self.offset, trigger_source='immediately', cycles_per_burst=self.cycles_per_burst) # Forget the shot file: self.shot_file = None # Modify behaviour of 'transition_to_manual' to also return dict of # initial values in manual mode attr_dict = { 'waveform': self.waveform, 'frequency': self.frequency, 'amplitude': self.amplitude, 'offset': self.offset } # Expected by BLACS. Indicate success. return True, attr_dict def update_asg(self, attr_value, which_attr): """ Update PYRPL asg0 values according to front panel. Called by 'update_attributes' in device Tab. """ # Turn on LED 3 to indicate that PYRPl is updating attributes in # Manual Mode self.connection.rp.hk.led = 0b00001000 if which_attr == 'waveform': self.waveform = str(attr_value) elif which_attr == 'amplitude': self.amplitude = self.__out_amp_volt_conv(attr_value) elif which_attr == 'offset': self.offset = self.__offset_calib( self.__out_amp_volt_conv(attr_value)) elif which_attr == "frequency": self.frequency = attr_value self.connection.rp.asg0.setup(waveform=self.waveform, frequency=self.frequency, amplitude=self.amplitude, offset=self.offset) def shutdown(self): """ Called when BLACS closes. """ # Turn on LED 0 to indicate that PYRPl is in default state self.connection.rp.hk.led = 0b00000001 # Set default values for ASG0 when BLACS closes self.waveform = 'dc' self.frequency = 0.0 self.amplitude = self.__out_amp_volt_conv(0.0) self.offset = self.__offset_calib(self.__out_amp_volt_conv(0.0)) self.cycles_per_burst = 0 self.connection.rp.asg0.setup(waveform=self.waveform, frequency=self.frequency, amplitude=self.amplitude, offset=self.offset, trigger_source='immediately', cycles_per_burst=self.cycles_per_burst) def abort_buffered(self): """ Called when a shot is aborted. In this case, we simply run transition_to_manual. """ return self.transition_to_manual() def abort_transition_to_buffered(self): """ This is called if transition_to_buffered fails with an exception or returns False. """ # Forget the shot file: self.shot_file = None return True # Indicates success
from pyrpl import RedPitaya, Pyrpl from time import sleep import matplotlib.pyplot as plt HOSTNAME = '129.199.115.243' p = Pyrpl('test') r = p.rp asg = r.asg1 s = r.scope s.setup(duration=0.05, trigger_delay=0., input1='in1', ch1_active=True, ch2_active=True, rolling_mode=True, trace_average=1, running_state="stopped") dt = 8 * 1e-9 * s.decimation n = 2**14 s.single() curves = s.save_curve( ) #Quand tu utilises single (et tu devrais), il ne garde que le dernier call de s.single() (enfin j'espère) plt.plot(curves[0].data[0], curves[0].data[1]) print(len(curves[0].data[0])) plt.show()
"config_file_name" """ import sys from pyrpl import APP, Pyrpl # no local import here because of pyinstller error if __name__ == '__main__': if len(sys.argv) > 3: print("usage: python run_pyrpl.py [[config]=config_file_name] " "[source=config_file_template] [hostname=hostname/ip]") kwargs = dict() for i, arg in enumerate(sys.argv): print (i, arg) if i == 0: continue try: k, v = arg.split('=', 1) except ValueError: k, v = arg, "" if v == "": if i == 1: kwargs["config"] = k else: kwargs[k] = v #APP = QtWidgets.QApplication.instance() #if APP is None: # APP = QtWidgets.QApplication(sys.argv) print("Calling Pyrpl(**%s)"%str(kwargs)) PYRPL = Pyrpl(**kwargs) APP.exec_()
class red_pitaya_pyrpl_asg_worker(Worker): """ The class 'red_pitaya_pyrpl_asg_worker' subclasses 'Worker' and handles all the communication with hardware of the Red Pitaya via the PYRPL API. The worker class is instantiated as separate process, which can be restarted by the user if the RP becomes unresponsive. For more information see: 'labscript_suite/blacs/docs/How\ to\ add\ a\ new\ device' """ def init(self): """ The init function does not override __init__. We load 'pyrpl' inside 'init' such that it only exists in worker process and not within BLACS process. """ # Import Leonhard Neuhaus PYRPL for Red Pitaya # https://pyrpl.readthedocs.io/en/latest/ # Import inside 'init' such that PYRPL exists only inside worker process from pyrpl import Pyrpl # Create PYRPL object connecting to Red Pitaya with given IP address # No config file self.connection = Pyrpl(hostname=self.ip_addr, config="") # Turn on LED 0 to indicate that PYRPl is set up self.connection.rp.hk.led = 0b00000001 # Hide PYRPl GUI self.connection.hide_gui() # Prevent timeout of connection self.connection.rp.client.socket.settimeout(None) # print("timeout = ", self.connection.rp.client.socket.timeout) # Set default values for ASG0 attributes self.waveform = 'dc' self.frequency = 0.0 self.amplitude = 0.0 self.offset = -1.0 self.cycles_per_burst = 0 # Initialize analog outputs 'out1' to zero # given that we are using a modified RP with output 0-2V self.connection.rp.asg0.output_direct = 'out1' self.connection.rp.asg0.setup(waveform=self.waveform, frequency=self.frequency, offset=self.offset, trigger_source='immediately', cycles_per_burst=self.cycles_per_burst) # Each shot, we will remember the shot file # for the duration of that shot self.shot_file = None def program_manual(self, values): """ Not used, but needs to be defined. Is used, when a value of a digital, analog or DDS output widget on the front panel is changed. However, we don't use these widgets in the device Tab. """ return {} def transition_to_buffered(self, device_name, h5_file, initial_values, fresh): """ State-function 'transition_to_buffered' is called, when Queue Manager requests the RP to move into buffered mode in preparation for executing a buffered sequence. The PYRPL asg0 is set up according to the parameters given as device_properties. """ # Turn on LED 2 to indicate that PYRPl is in # transition_to_buffered_state self.connection.rp.hk.led = 0b00000100 # Set up attributes according to device_properties in shot file self.shot_file = h5_file with h5py.File(self.shot_file, 'r') as f: device_properties = labscript_utils.properties.get( f, self.device_name, "device_properties") self.waveform = device_properties['waveform'] # Frequency is more natural to PYRPL API self.frequency = 1 / device_properties['cycle_duration'] self.amplitude = device_properties['amplitude'] # Substract -1.0 from Offset due to RP output mod 0-2V self.offset = device_properties['offset'] - 1.0 self.cycles_per_burst = device_properties['cycles_per_burst'] def evaporation_ramp(freq, time_const, ratio_trap_temp): """ This function defines the evaporation ramp used in the experimental shot. See PhD Thesis Florian Huber: "Site-Resolved Imaging with the Fermi Gas Microscope" from 2014 for details. """ # PYRPL expects 2**14 data points ts = np.linspace(0.0, 1 / freq, 2**14 - 1) ratio_trap_temp = (ratio_trap_temp + (ratio_trap_temp - 5) / (ratio_trap_temp - 4)) ramp_exp = 2 * (ratio_trap_temp - 3) / (ratio_trap_temp - 6) out_values = (1 - ts / time_const)**ramp_exp # Start out_values with 0.0 such that after the shot is run # PYRPL outputs 0.0 out_values = np.append([0.0], out_values) return out_values if self.waveform == 'evaporation_ramp': self.time_const = device_properties['time_const'] self.ratio_trap_temp = device_properties['ratio_trap_temp'] self.connection.rp.asg0.setup( frequency=self.frequency, amplitude=self.amplitude, offset=self.offset, trigger_source='immediately', cycles_per_burst=self.cycles_per_burst) # Send arbitrary evaporation_ramp to Red Pitaya self.connection.rp.asg0.data = evaporation_ramp( self.frequency, self.time_const, self.ratio_trap_temp) else: self.connection.rp.asg0.setup( waveform=self.waveform, frequency=self.frequency, amplitude=self.amplitude, offset=self.offset, trigger_source='immediately', cycles_per_burst=self.cycles_per_burst) # Return empty dictionary due to empty channels return {} def transition_to_manual(self): """ The state-function 'transition_to_manual' places the device back in the correct mode for operation by the front panel. """ # Turn on LED 1 to indicate that PYRPl is in transition_to_manual self.connection.rp.hk.led = 0b00000010 # Set default values for ASG0 attributes in manual mode # And forget the values in buffered mode self.waveform = 'dc' self.frequency = 0.0 self.amplitude = 0.0 self.offset = -1.0 self.cycles_per_burst = 0 self.connection.rp.asg0.setup(waveform=self.waveform, frequency=self.frequency, amplitude=self.amplitude, offset=self.offset, trigger_source='immediately', cycles_per_burst=self.cycles_per_burst) # Forget the shot file: self.shot_file = None # Modify behaviour of 'transition_to_manual' to also return dict of # initial values in manual mode attr_dict = { 'waveform': 'dc', 'frequency': self.frequency, 'amplitude': self.amplitude, 'offset': self.offset } # Expected by BLACS. Indicate success. return True, attr_dict def update_asg(self, attr_value, which_attr): """ Update PYRPL asg0 values according to front panel. Called by 'update_attributes' in device Tab. """ # Turn on LED 3 to indicate that PYRPl is updating attributes in # Manual Mode self.connection.rp.hk.led = 0b00001000 if which_attr == 'waveform': self.waveform = str(attr_value) elif which_attr == 'amplitude': self.amplitude = attr_value elif which_attr == 'offset': self.offset = attr_value - 1.0 elif which_attr == "frequency": self.frequency = attr_value self.connection.rp.asg0.setup(waveform=self.waveform, frequency=self.frequency, amplitude=self.amplitude, offset=self.offset) def shutdown(self): """ Called when BLACS closes. """ # Turn on LED 0 to indicate that PYRPl is in default state self.connection.rp.hk.led = 0b00000001 # Set default values for ASG0 when BLACS closes self.waveform = 'dc' self.frequency = 0.0 self.amplitude = 0.0 self.offset = -1.0 self.cycles_per_burst = 0 self.connection.rp.asg0.setup(waveform=self.waveform, frequency=self.frequency, amplitude=self.amplitude, offset=self.offset, trigger_source='immediately', cycles_per_burst=self.cycles_per_burst) def abort_buffered(self): """ Called when a shot is aborted. In this case, we simply run transition_to_manual. """ return self.transition_to_manual() def abort_transition_to_buffered(self): """ This is called if transition_to_buffered fails with an exception or returns False. """ # Forget the shot file: self.shot_file = None return True # Indicates success
class RedPitaya(): """Wrapper class to make PyRPL functions easily accessible""" def __init__(self,laser,hostname,config='relocker',gui=False): self.laser = laser self.p = Pyrpl(hostname=hostname,config=config,gui=gui)#,modules=[]) # self.p.hide_gui() self.rp = self.p.rp self.scope = self.rp.scope self.scope_queue = queue.Queue() self.scope_queue_wait = QtCore.QWaitCondition() self.scope_queue_mutex = QtCore.QMutex() self.scope_queuer = ScopeQueuer(self.scope_queue,self.scope_queue_wait,self.scope_queue_mutex) self.scope_queuer.start() #self.scope_queuer.signal.connect(partial(self.set_scope_duration)) self.scope_queuer.signal.connect(self.get_scope_trace) #self.relock_thread.start() def hide_gui(self): self.p.hide_gui() def show_gui(self): self.p.show_gui() def get_pid_value(self,index,setting): """Passes a pid value from PyRPL.""" print('index',index) if index == 0: pid = self.rp.pid0 elif index == 1: pid = self.rp.pid1 elif index == 2: pid = self.rp.pid2 else: print("RP does not support pid > 2") if setting == 'P': return pid.p elif setting == 'I [Hz]': return pid.i elif setting == 'setpoint [V]': return pid.setpoint elif setting == 'integrator': return pid.ival elif setting == 'input': return pid.input elif setting == 'output': return pid.output_direct elif setting == 'max': return pid.max_voltage elif setting == 'min': return pid.min_voltage def set_pid_value(self,index,setting,value): """Passes a pid value to PyRPL, then requests the value back before returning it (in case PyRPL has rounded it etc.) """ print('index',index) if index == 0: pid = self.rp.pid0 elif index == 1: pid = self.rp.pid1 elif index == 2: pid = self.rp.pid2 else: print("RP does not support pid > 2") if setting == 'P': pid.p = value elif setting == 'I [Hz]': pid.i = value elif setting == 'setpoint [V]': pid.setpoint = value elif setting == 'integrator': pid.ival = value elif setting == 'input': pid.input = value elif setting == 'output': pid.output_direct = value elif setting == 'max': pid.max_voltage = value elif setting == 'min': pid.min_voltage = value elif setting == 'trigger': pid.trigger_source = value return self.get_pid_value(index,setting) def get_asg_value(self,index,setting): """Passes a pid value from PyRPL.""" if index == 0: asg = self.rp.asg0 elif index == 1: asg = self.rp.asg1 else: print("RP does not support asg > 1") if setting == 'offset': return asg.offset elif setting == 'amplitude': return asg.amplitude elif setting == 'frequency': return asg.frequency elif setting == 'waveform': return asg.waveform elif setting == 'output': return asg.output_direct elif setting == 'trigger': return asg.trigger_source def set_asg_value(self,index,setting,value): """Passes a pid value to PyRPL, then requests the value back before returning it (in case PyRPL has rounded it etc.) """ if index == 0: asg = self.rp.asg0 elif index == 1: asg = self.rp.asg1 else: print("RP does not support asg > 1") if setting == 'offset': asg.offset = value elif setting == 'amplitude': asg.amplitude = value elif setting == 'frequency': asg.frequency = value elif setting == 'waveform': asg.waveform = value elif setting == 'output': asg.output_direct = value return self.get_asg_value(index,setting) def queue_scope_trace(self,input1,input2,duration,mode='rolling',trigger='immediately'): """Adds a scope trace request to the scope_getter worker queue. scope_parameters = []""" scope_parameters = [input1,input2,duration,mode,trigger] print('requesting scope trace',scope_parameters) self.scope_queue.put(scope_parameters) def get_scope_trace(self,scope_parameters): input1,input2,duration,mode,trigger = scope_parameters self.scope.input1 = input1 self.scope.input2 = input2 self.scope.duration = duration duration = self.scope.duration self.scope.trigger = trigger if mode == 'rolling': QtTest.QTest.qWait(duration*1000) times, datas = self.scope._get_rolling_curve() print('delivering scope trace',scope_parameters) self.laser.update_scope_trace(times,datas,duration) self.scope_queue_wait.wakeAll()
""" script to test the software, e.g. in PyCharm debugger, without actually having a redpitaya """ from pyrpl import Pyrpl from qtpy import QtWidgets, QtCore APP = QtWidgets.QApplication.instance() p = Pyrpl(config="localtest", hostname="_FAKE_REDPITAYA_") while True: APP.processEvents()
# import pyrpl library from pyrpl import Pyrpl configFile = "/home/andre/pyrpl_user_dir/test1.yml" # create an interface to the Red Pitaya #r = pyrpl.Pyrpl(configFile).redpitaya p = Pyrpl(hostname='10.42.0.60',config=configFile) print(p) p.rp.hk.led = 0b10101010 # change led pattern # measure a few signal values print("Voltage at analog input1: %.3f" % p.rp.sampler.in1) print("Voltage at analog output2: %.3f" % p.rp.sampler.out2) print("Voltage at the digital filter's output: %.3f" % p.rp.sampler.iir) # output a function U(t) = 0.5 V * sin(2 pi * 10 MHz * t) to output2 p.rp.asg0.setup(waveform='sin', amplitude=0.5, frequency=10e6, output_direct='out2') # demodulate the output signal from the arbitrary signal generator r.iq0.setup(input='asg0', # demodulate the signal from asg0 frequency=10e6, # demodulaltion at 10 MHz bandwidth=1e5) # demodulation bandwidth of 100 kHz # set up a PID controller on the demodulated signal and add result to out2 r.pid0.setup(input='iq0', output_direct='out2', # add pid signal to output 2
from qtpy import QtCore, QtWidgets import time from pyrpl.async_utils import sleep as async_sleep """ what is this file for? delete it? """ if False: from pyrpl import Pyrpl pyrpl = Pyrpl(config="nosetests_source.yml", source="nosetests_config.yml") async_sleep(0.5) class ToPasteInNotebook(object): def coucou(self): self.count += 1 if self.count < 1000: self.timer.start() def test_stupid_timer(self): self.timer = QtCore.QTimer() self.timer.setInterval(1) # 1 ms self.timer.setSingleShot(True) self.count = 0 self.timer.timeout.connect(self.coucou) tic = time.time() self.timer.start() while self.count < 10: async_sleep(0.01) duration = time.time() - tic assert (duration < 1), duration # should this not be >1 ???