Source code for blacs.output_classes

#####################################################################
#                                                                   #
# /hardware_interfaces/output_classes.py                            #
#                                                                   #
# Copyright 2013, Monash University                                 #
#                                                                   #
# This file is part of the program BLACS, in the labscript suite    #
# (see http://labscriptsuite.org), and is licensed under the        #
# Simplified BSD License. See the license.txt file in the root of   #
# the project for the full license.                                 #
#                                                                   #
#####################################################################
import logging
import math
import sys

from qtutils.qt.QtCore import *
from qtutils.qt.QtGui import *
from qtutils.qt.QtWidgets import *


from labscript_utils.qtwidgets.analogoutput import AnalogOutput
from labscript_utils.qtwidgets.digitaloutput import DigitalOutput, InvertedDigitalOutput
from labscript_utils.qtwidgets.ddsoutput import DDSOutput
from labscript_utils.qtwidgets.imageoutput import ImageOutput
from labscript_utils.unitconversions import get_unit_conversion_class


[docs] class AO(object):
[docs] def __init__(self, hardware_name, connection_name, device_name, program_function, settings, calib_class, calib_params, default_units, min, max, step, decimals): self._connection_name = connection_name self._hardware_name = hardware_name self._device_name = device_name self._locked = False self._comboboxmodel = QStandardItemModel() self._widgets = [] self._current_units = default_units self._base_unit = default_units self._program_device = program_function # All of these are in base units ALWAYS self._current_value = 0 # value in base units self._current_step_size = step # step size in current units self._step_size = step # step size in base units self._limits = [min,max] self._decimals = decimals self._logger = logging.getLogger('BLACS.%s.%s'%(self._device_name,hardware_name)) # Initialise Calibrations self._comboboxmodel.appendRow(QStandardItem(self._base_unit)) if calib_class is not None: try: cls = get_unit_conversion_class(calib_class) # ImportError if module doesn't exist. Attribute error if it does but the # class name does not exist within it. KeyError if the unit conversion class # is an old-style unqualified class name expected to be in the globals() # dict of the unitconversions module, but does not exist. except (ImportError, AttributeError, KeyError): cls = None if cls is None or not isinstance(calib_params, dict) or cls.base_unit != default_units: # log an error: reason = '' if cls is None: reason = f'The unit conversion class {calib_class} could not be imported. Ensure it is available on the computer running BLACS.' elif not isinstance(calib_params, dict): reason = 'The parameters for the unit conversion class are not a dictionary. Check your connection table code for errors and recompile it' elif cls.base_unit != default_units: reason = f'The base unit of your unit conversion class does not match this hardware channel. The hardware channel has base units {default_units} while your unit conversion class uses {cls.base_unit}' self._logger.error('The unit conversion class (%s) could not be loaded. Reason: %s'%(calib_class,reason)) # Use default units self._calibration = None else: try: # initialise calibration class self._calibration = cls(calib_params) self._logger.debug('unit conversion class instantiated') for unit in self._calibration.derived_units: try: self._comboboxmodel.appendRow(QStandardItem(unit)) except Exception: self._logger.exception('Error while trying to add unit "%s"'%unit) except Exception: self._logger.exception('Error while trying to instantiate unit conversion class') self._calibration = None else: # use default units self._calibration = None self._logger.debug('No unit conversion class specified') self._update_from_settings(settings,program=False)
def _update_from_settings(self,settings,program=True): # Build up the settings dictionary if it isn't already if not isinstance(settings,dict): settings = {} if 'front_panel_settings' not in settings or not isinstance(settings['front_panel_settings'],dict): settings['front_panel_settings'] = {} if self._hardware_name not in settings['front_panel_settings'] or not isinstance(settings['front_panel_settings'][self._hardware_name],dict): settings['front_panel_settings'][self._hardware_name] = {} # Set default values if they are not already saved in the settings dictionary if 'base_value' not in settings['front_panel_settings'][self._hardware_name]: settings['front_panel_settings'][self._hardware_name]['base_value'] = False if 'locked' not in settings['front_panel_settings'][self._hardware_name]: settings['front_panel_settings'][self._hardware_name]['locked'] = False if 'base_step_size' not in settings['front_panel_settings'][self._hardware_name]: settings['front_panel_settings'][self._hardware_name]['base_step_size'] = self._step_size if 'current_units' not in settings['front_panel_settings'][self._hardware_name]: settings['front_panel_settings'][self._hardware_name]['current_units'] = self._base_unit if 'name' not in settings['front_panel_settings'][self._hardware_name]: settings['front_panel_settings'][self._hardware_name]['name'] = self._connection_name # only keep a reference to the part of the settings dictionary relevant to this DO self._settings = settings['front_panel_settings'][self._hardware_name] # Update the state of the button self.set_value(self._settings['base_value'],program=program) # Update the lock state self._update_lock(self._settings['locked']) # Update the step size self.set_step_size(self._settings['base_step_size'],self._base_unit) # Update the unit selection if self._calibration and self._settings['current_units'] in self._calibration.derived_units: self.change_unit(self._settings['current_units'],program=program) else: self.change_unit(self._base_unit,program=program)
[docs] def convert_value_to_base(self, value, unit): if unit != self._base_unit: if self._calibration and unit in self._calibration.derived_units: return getattr(self._calibration,unit+"_to_base")(value) # TODO: include device name somehow, and also the calibration class name raise RuntimeError('The value %s (%s) could not be converted to base units because the hardware channel %s, named %s, either does not have a unit conversion class or the unit specified was invalid'%(str(value),unit,self._hardware_name,self._connection_name)) else: return value
[docs] def convert_value_from_base(self, value, unit): if unit != self._base_unit: if self._calibration and unit in self._calibration.derived_units: return getattr(self._calibration,unit+"_from_base")(value) # TODO: include device name somehow, and also the calibration class name raise RuntimeError('The value %s (%s) could not be converted to base units because the hardware channel %s, named %s, either does not have a unit conversion class or the unit specified was invalid'%(str(value),unit,self._hardware_name,self._connection_name)) else: return value
# handles the conversion of a range centered on value in units to base units # In other words, how big is "range" in base units assuming that you care about what range is # between value-range/2 and value+range/2 # # If value+range/2 or value-range/2 is outside of the limits, then we will shift the fraction of range # used on the offending side of value # If range is greater than the difference of the limits, we will return the difference between the limits
[docs] def convert_range_to_base(self,value,range,unit): self._logger.debug('convert_range_to_base called. value: %f, range: %f, unit: %s'%(value,range,unit)) # Do we need to convert the limits? if unit != self._base_unit: limits = [self.convert_value_from_base(self._limits[0],unit),self.convert_value_from_base(self._limits[1],unit)] if limits[0] > limits[1]: limits[0],limits[1] = limits[1],limits[0] else: limits = self._limits self._logger.debug('limits in unit: %s), limits=[%f,%f]'%(unit,limits[0],limits[1])) # limits are now in the units given to the function! # (As are range and value) # If range is bigger than the difference of the limits, return the difference of the limits # in base units if range >= abs(limits[0]-limits[1]): limits = [self.convert_value_to_base(limits[0],unit),self.convert_value_to_base(limits[1],unit)] self._logger.debug('range bigger than range of limits, returning difference of limits') return abs(limits[0]-limits[1]) # At this point, the range must fit inside the limits, so if we find we are out of bounds on one side, # we can be certain shifting the fractions will not cause us to go out of bounds on the other side positive_fraction = range/2.0 negative_fraction = range/2.0 self._logger.debug('fractions are.... positive_fraction: %f, negative_fraction: %f'%(positive_fraction, negative_fraction)) # If the value+range/2 is greater than the upper limit, shift the fraction if value+positive_fraction > limits[1]: positive_fraction = abs(limits[1]-value) negative_fraction = abs(range-positive_fraction) self._logger.debug('outside upper limit. positive_fraction: %f, negative_fraction: %f'%(positive_fraction, negative_fraction)) # Similarly if value-range/2 is less than the lower limit, shift the fraction elif value-negative_fraction < limits[0]: negative_fraction = abs(limits[0]-value) positive_fraction = abs(range-negative_fraction) self._logger.debug('outside lower limit. positive_fraction: %f, negative_fraction: %f'%(positive_fraction, negative_fraction)) self._logger.debug('converting values to base units') # Now do the conversion! bound1 = self.convert_value_to_base(value+positive_fraction,unit) bound2 = self.convert_value_to_base(value-negative_fraction,unit) self._logger.debug('range in base units is: %f'%(abs(bound1-bound2))) return abs(bound1-bound2)
# This does the reverse of teh above function, with the same rules
[docs] def convert_range_from_base(self,value,range,unit): # limits are always in base units limits = self._limits # limits are now in base units! # (As are range and value) # If range is bigger than the difference of the limits, return the difference of the limits # in the specified units units if range >= abs(limits[0]-limits[1]): limits = [self.convert_value_from_base(limits[0],unit),self.convert_value_from_base(limits[1],unit)] return abs(limits[0]-limits[1]) # At this point, the range must fit inside the limits, so if we find we are out of bounds on one side, # we can be certain shifting the fractions will not cause us to go out of bounds on the other side positive_fraction = range/2.0 negative_fraction = range/2.0 # If the value+range/2 is greater than the upper limit, shift the fraction if value+positive_fraction > limits[1]: positive_fraction = abs(limits[1]-value) negative_fraction = abs(range-positive_fraction) # Similarly if value-range/2 is less than the lower limit, shift the fraction elif value-negative_fraction < limits[0]: negative_fraction = abs(limits[0]-value) positive_fraction = abs(range-negative_fraction) # Now do the conversion! bound1 = self.convert_value_from_base(value+positive_fraction,unit) bound2 = self.convert_value_from_base(value-negative_fraction,unit) return abs(bound1-bound2)
[docs] def create_widget(self,display_name=None, horizontal_alignment=False, parent=None): widget = AnalogOutput(self._hardware_name,self._connection_name,display_name, horizontal_alignment, parent) self.add_widget(widget) return widget
[docs] def add_widget(self, widget): if widget in self._widgets: return False self._widgets.append(widget) # make sure the widget knows about this AO. widget.set_AO(self,notify_old_AO=True,notify_new_AO=False) # Now connect this widgets signal to the AO slot widget.connect_value_change(self.set_value) # set the properties of the widgets... # set comboboxmodel widget.block_combobox_signals() widget.set_combobox_model(self._comboboxmodel) widget.unblock_combobox_signals() # This will set the min/max/value/num.decimals/stepsie and current_unit of ALL widgets # including the one just added! self.change_unit(self._current_units,program=False) # don't need to program though! # This will update the lock state of ALL widgets, including the one just added! self._update_lock(self._locked) return True
# If calling this method directly from outside the set_AO function in the analog widget # you should NOT specify a value for new_AO.
[docs] def remove_widget(self,widget,call_set_AO = True,new_AO = None): if widget not in self._widgets: raise RuntimeError('The widget cannot be removed because it is not registered with this AO object') #TODO: Make the above error message better! self._widgets.remove(widget) if call_set_AO: widget.set_AO(new_AO,True,True) # Further cleanup widget.disconnect_value_change() widget.set_combobox_model(QStandardItemModel())
[docs] def change_unit(self,unit,program=True): # These values are always stored in base units! property_value_list = [self._current_value,self._limits[0],self._limits[1]] property_range_list = [self._step_size] self._logger.debug('changing unit to %s'%unit) self._logger.debug('Values in base units are: value: %f, lower_limit: %f, upper_limit: %f'%(property_value_list[0],property_value_list[1],property_value_list[2])) self._logger.debug('ranges in base units are: step_size: %f'%(property_range_list[0])) # Now convert to the new unit if unit != self._base_unit: for index,param in enumerate(property_value_list): #convert each to base units property_value_list[index] = self.convert_value_from_base(param,unit) for index,param in enumerate(property_range_list): #convert each to base units property_range_list[index] = self.convert_range_from_base(self._current_value,param,unit) self._logger.debug('Values in new unit are: value: %f, lower_limit: %f, upper_limit: %f'%(property_value_list[0],property_value_list[1],property_value_list[2])) self._logger.debug('ranges in new unit are: step_size: %f'%(property_range_list[0])) # figure out how many decimal points we need in the new unit smallest_step = 10**(-self._decimals) self._logger.debug('Smallest step size in base units: %f'%smallest_step) smallest_step_in_new_unit = self.convert_range_from_base(self._current_value+smallest_step,smallest_step,unit) self._logger.debug('Smallest step size in new_unit: %f'%smallest_step_in_new_unit) try: if smallest_step_in_new_unit > 1: if smallest_step_in_new_unit > 10: num_decimals = 0 else: num_decimals = 1 else: num_decimals = abs(math.floor(math.log10(smallest_step_in_new_unit))-2) except Exception: self._logger.warning('Failed to convert number of significant figures to new unit. Loss of precision likely (in manual mode) for this unit. Probably cause is a unit conversion class that imposes limits on the converted values.') num_decimals = self._decimals else: num_decimals = self._decimals # Store the current units self._current_units = unit self._settings['current_units'] = unit # Check to see if the upper/lower bound has switched if property_value_list[1] > property_value_list[2]: property_value_list[1], property_value_list[2] = property_value_list[2], property_value_list[1] # Now update all the widgets for widget in self._widgets: # Update the combo box widget.block_combobox_signals() widget.set_selected_unit(unit) widget.unblock_combobox_signals() # block the spinbox from emitting a signal widget.block_spinbox_signals() # Update the limits widget.set_limits(property_value_list[1],property_value_list[2]) # Update the step size widget.set_step_size(property_range_list[0]) # Update the decimals widget.set_num_decimals(num_decimals) # Update the value - This should be the last thing you do, # otherwise it might get truncated or # limited in a bad way widget.set_spinbox_value(property_value_list[0],unit) # unblock the spinbox signals widget.unblock_spinbox_signals()
@property def value(self): return self._current_value
[docs] def set_value(self, value, unit=None, program=True): # conversion to float means a string can be passed in too: value = float(value) if unit is not None and unit != self._base_unit: self._current_value = self.convert_value_to_base(value,unit) else: self._current_value = value # Update the saved value in the settings dictionary self._settings['base_value'] = self._current_value if program: self._logger.debug('program device called') self._program_device() for widget in self._widgets: # block signals widget.block_spinbox_signals() # update widget widget.set_spinbox_value(value,unit if unit is not None else self._base_unit) # unblock signals widget.unblock_spinbox_signals()
[docs] def set_step_size(self,step_size,unit): self._logger.debug('set_step_size called. step_size: %f, unit: %s'%(step_size,unit)) if unit != self._base_unit: # convert and store! value = self.convert_value_from_base(self._current_value,unit) self._step_size = self.convert_range_to_base(value,step_size,unit) else: # This check is usually performed when converting the range to base units # But since we are already in base units we should do it here if abs(self._limits[0]-self._limits[1]) <= step_size: step_size = abs(self._limits[0]-self._limits[1]) self._step_size = step_size self._logger.debug('step_size in base units: %f'%self._step_size) #self._current_step_size = self._step_size self._settings['base_step_size'] = self._step_size # now convert to current units self._current_step_size = self.get_step_size(self._current_units) self._logger.debug('step_size in current units (%s): %f'%(self._current_units,self._current_step_size)) # Update the step size for all widgets for widget in self._widgets: widget.set_step_size(self._current_step_size)
[docs] def get_step_size(self,unit): if unit != self._base_unit: # we should convert it return self.convert_range_from_base(self._current_value,self._step_size,unit) else: return self._step_size
[docs] def lock(self): self._update_lock(True)
[docs] def unlock(self): self._update_lock(False)
def _update_lock(self, locked): self._locked = locked self._settings['locked'] = locked # Lock all widgets if they are not already locked for widget in self._widgets: if locked: widget.lock(False) else: widget.unlock(False) @property def name(self): return self._hardware_name + ' - ' + self._connection_name
[docs] class DO(object):
[docs] def __init__(self, hardware_name, connection_name, device_name, program_function, settings): self._hardware_name = hardware_name self._connection_name = connection_name self._widget_list = [] self._device_name = device_name self._logger = logging.getLogger('BLACS.%s.%s'%(self._device_name,hardware_name)) # Note that while we could store self._current_state and self._locked in the # settings dictionary, this dictionary is available to other parts of BLACS # and using separate variables avoids those parts from being able to directly # influence behaviour (the worst they can do is change the value used on initialisation) self._locked = False self._current_state = False self._program_device = program_function self._update_from_settings(settings)
def _update_from_settings(self,settings): # Build up the settings dictionary if it isn't already if not isinstance(settings,dict): settings = {} if 'front_panel_settings' not in settings or not isinstance(settings['front_panel_settings'],dict): settings['front_panel_settings'] = {} if self._hardware_name not in settings['front_panel_settings'] or not isinstance(settings['front_panel_settings'][self._hardware_name],dict): settings['front_panel_settings'][self._hardware_name] = {} # Set default values if they are not already saved in the settings dictionary if 'base_value' not in settings['front_panel_settings'][self._hardware_name]: settings['front_panel_settings'][self._hardware_name]['base_value'] = False if 'locked' not in settings['front_panel_settings'][self._hardware_name]: settings['front_panel_settings'][self._hardware_name]['locked'] = False if 'name' not in settings['front_panel_settings'][self._hardware_name]: settings['front_panel_settings'][self._hardware_name]['name'] = self._connection_name # only keep a reference to the part of the settings dictionary relevant to this DO self._settings = settings['front_panel_settings'][self._hardware_name] # Update the state of the button self.set_value(self._settings['base_value'],program=False) # Update the lock state self._update_lock(self._settings['locked'])
[docs] def create_widget(self, *args, **kwargs): inverted = kwargs.pop("inverted", False) if not inverted: widget = DigitalOutput('%s\n%s'%(self._hardware_name,self._connection_name),*args,**kwargs) else: widget = InvertedDigitalOutput('%s\n%s'%(self._hardware_name,self._connection_name),*args,**kwargs) self.add_widget(widget, inverted=inverted) return widget
[docs] def add_widget(self, widget, inverted=False): if widget not in self._widget_list: widget.set_DO(self,True,False) widget.toggled.connect(self.set_value if not inverted else lambda state: self.set_value(not state)) self._widget_list.append(widget) self.set_value(self._current_state,False) self._update_lock(self._locked) return True return False
[docs] def remove_widget(self,widget): if widget not in self._widget_list: # TODO: Make this error better! raise RuntimeError('The widget specified was not part of the DO object') widget.toggled.disconnect(self.set_value) self._widget_list.remove(widget)
@property def value(self): return bool(self._current_state)
[docs] def lock(self): self._update_lock(True)
[docs] def unlock(self): self._update_lock(False)
def _update_lock(self,locked): self._locked = locked for widget in self._widget_list: if locked: widget.lock(False) else: widget.unlock(False) # update the settings dictionary if it exists, to maintain continuity on tab restarts self._settings['locked'] = locked
[docs] def set_value(self,state,program=True): # conversion to integer, then bool means we can safely pass in # either a string '1' or '0', True or False or 1 or 0 state = bool(int(state)) # We are programatically setting the state, so break the check lock function logic self._current_state = state # update the settings dictionary if it exists, to maintain continuity on tab restarts self._settings['base_value'] = state if program: self._logger.debug('program device called') self._program_device() for widget in self._widget_list: if state != widget.state: widget.blockSignals(True) widget.state = state widget.blockSignals(False)
@property def name(self): return self._hardware_name + ' - ' + self._connection_name
[docs] class Image(object):
[docs] def __init__(self, hardware_name, connection_name, device_name, program_function, settings, width, height, x = None, y = None): self._hardware_name = hardware_name self._connection_name = connection_name self._widget_list = [] if x is not None and y is not None: self.location = 'Location: (%d, %d) '%(x,y) else: self.location = "" self.width = width self.height = height self._device_name = device_name self._logger = logging.getLogger('BLACS.%s.%s'%(self._device_name,hardware_name)) # Note that while we could store self._current_state and self._locked in the # settings dictionary, this dictionary is available to other parts of BLACS # and using separate variables avoids those parts from being able to directly # influence behaviour (the worst they can do is change the value used on initialisation) self._locked = False self._current_value = "" self._program_device = program_function self._update_from_settings(settings)
def _update_from_settings(self, settings): # Build up the settings dictionary if it isn't already if not isinstance(settings,dict): settings = {} if 'front_panel_settings' not in settings or not isinstance(settings['front_panel_settings'],dict): settings['front_panel_settings'] = {} if self._hardware_name not in settings['front_panel_settings'] or not isinstance(settings['front_panel_settings'][self._hardware_name],dict): settings['front_panel_settings'][self._hardware_name] = {} # Set default values if they are not already saved in the settings dictionary if 'base_value' not in settings['front_panel_settings'][self._hardware_name]: settings['front_panel_settings'][self._hardware_name]['base_value'] = "" if 'locked' not in settings['front_panel_settings'][self._hardware_name]: settings['front_panel_settings'][self._hardware_name]['locked'] = False if 'name' not in settings['front_panel_settings'][self._hardware_name]: settings['front_panel_settings'][self._hardware_name]['name'] = self._connection_name # only keep a reference to the part of the settings dictionary relevant to this DO self._settings = settings['front_panel_settings'][self._hardware_name] # Update the state of the button self.set_value(self._settings['base_value'],program=False) # Update the lock state self._update_lock(self._settings['locked'])
[docs] def create_widget(self, *args, **kwargs): widget = ImageOutput('%s (%s)\n%sDimensions(%d, %d)'%(self._hardware_name,self._connection_name, self.location, self.width, self.height),self.width, self.height, *args,**kwargs) self.add_widget(widget) return widget
[docs] def add_widget(self, widget): if widget not in self._widget_list: widget.set_Image(self,True,False) widget.imageUpdated.connect(self.set_value) self._widget_list.append(widget) self.set_value(self._current_value,False) self._update_lock(self._locked) return True return False
[docs] def remove_widget(self, widget): if widget not in self._widget_list: # TODO: Make this error better! raise RuntimeError('The widget specified was not part of the Image object') widget.imageUpdated.disconnect(self.set_value) self._widget_list.remove(widget)
@property def value(self): return str(self._current_value)
[docs] def lock(self): self._update_lock(True)
[docs] def unlock(self): self._update_lock(False)
def _update_lock(self, locked): self._locked = locked for widget in self._widget_list: if locked: widget.lock(False) else: widget.unlock(False) # update the settings dictionary if it exists, to maintain continuity on tab restarts self._settings['locked'] = locked
[docs] def set_value(self, value, program = True): value = str(value) # We are programatically setting the value, so break the check lock function logic self._current_value = value # update the settings dictionary if it exists, to maintain continuity on tab restarts self._settings['base_value'] = value if program: self._logger.debug('program device called') self._program_device() for widget in self._widget_list: if value != widget.value: widget.blockSignals(True) widget.value = value widget.blockSignals(False)
@property def name(self): return self._hardware_name + ' - ' + self._connection_name
[docs] class DDS(object):
[docs] def __init__(self, hardware_name, connection_name, output_list): self._hardware_name = hardware_name self._connection_name = connection_name self._sub_channel_list = ['freq','amp','phase','gate'] self._widget_list = [] for subchnl in self._sub_channel_list: value = None if subchnl in output_list: value = output_list[subchnl] setattr(self,subchnl,value)
[docs] def create_widget(self,*args,**kwargs): widget = DDSOutput(self._hardware_name,self._connection_name,*args,**kwargs) self.add_widget(widget) return widget
[docs] def add_widget(self, widget): if widget in self._widget_list: return False # Check that the widget has a method for getting/showin/hiding subwidgets for subchnl in self._sub_channel_list: widget.get_sub_widget(subchnl) widget.hide_sub_widget(subchnl) widget.show_sub_widget(subchnl) self._widget_list.append(widget) for subchnl in self._sub_channel_list: if hasattr(self,subchnl): getattr(self,subchnl).add_widget(widget.get_sub_widget(subchnl)) widget.show_sub_widget(subchnl) else: widget.hide_sub_widget(subchnl) return True
[docs] def remove_widget(self,widget): if widget not in self._widget_list: # TODO: Make this error better! raise RuntimeError('The widget specified was not part of the DDS object') for subchnl in self._sub_channel_list: if hasattr(self,subchnl): getattr(self,subchnl).remove_widget(widget.get_sub_widget(subchnl)) self._widget_list.remove(widget)
[docs] def get_subchnl_list(self): subchnls = [] for subchnl in self._sub_channel_list: if hasattr(self,subchnl): subchnls.append(subchnl) return subchnls
[docs] def get_unused_subchnl_list(self): return list(set(self._sub_channel_list).difference(set(self.get_subchnl_list())))
@property def value(self): value = {} for subchnl in self._sub_channel_list: if hasattr(self,subchnl): value[subchnl] = getattr(self,subchnl).value return value
[docs] def set_value(self,value,program=True): for subchnl in self._sub_channel_list: if subchnl in value: if hasattr(self,subchnl): getattr(self,subchnl).set_value(value[subchnl],program=program)
@property def name(self): return self._hardware_name + ' - ' + self._connection_name
if __name__ == '__main__': from labscript_utils.qtwidgets.toolpalette import ToolPaletteGroup import sys qapplication = QApplication(sys.argv) window = QWidget() layout = QVBoxLayout(window) widget = QWidget() layout.addWidget(widget) tpg = ToolPaletteGroup(widget) toolpalette = tpg.append_new_palette('Digital Outputs') toolpalette2 = tpg.append_new_palette('Analog Outputs') layout.addItem(QSpacerItem(0,0,QSizePolicy.Minimum,QSizePolicy.MinimumExpanding)) # create settings dictionary settings = {'front_panel_settings':{ 'do0':{ 'base_value':False, 'locked':False, }, 'ao0':{ 'base_value':3.0, 'locked':False, 'base_step_size':0.1, 'current_units':'V', } } } def print_something(): print('program_function called') # Create a DO object my_DO = DO(hardware_name='do0', connection_name='my first digital output', program_function=print_something, settings=settings) # Link in two DO widgets button1 = DigitalOutput('do0\nmy first digital output') button2 = DigitalOutput('a linked do0') toolpalette.addWidget(button1) toolpalette.addWidget(button2) my_DO.add_widget(button1) my_DO.add_widget(button2) # Create an AO object my_AO = AO(hardware_name = 'ao0', connection_name='my ao', device_name='ni_blah', program_function=print_something, settings=settings, calib_class=None, calib_params=None, default_units='V', min=-10.0, max=10.0, step=0.01, decimals=3) # link in two AO widgets analog1 = AnalogOutput('AO1') analog2 = AnalogOutput('AO1 copy') my_AO.add_widget(analog1) my_AO.add_widget(analog2) toolpalette2.addWidget(analog1) toolpalette2.addWidget(analog2) # TODO: Add in test case for DDS window.show() sys.exit(qapplication.exec_())