示例#1
0
class DAQ_Move_PI(DAQ_Move_base):
    """
    Plugin using the Pi wrapper shipped with new hardware. It is compatible with :
    DLLDEVICES = {
    'PI_GCS2_DLL': ['C-413', 'C-663.11', 'C-863.11', 'C-867', 'C-877', 'C-884', 'C-885', 'C-887',
                    'C-891', 'E-517', 'E-518', 'E-545', 'E-709', 'E-712', 'E-723', 'E-725',
                    'E-727', 'E-753', 'E-754', 'E-755', 'E-852B0076', 'E-861', 'E-870', 'E-871',
                    'E-873', 'C-663.12'],
    'C7XX_GCS_DLL': ['C-702', ],
    'C843_GCS_DLL': ['C-843', ],
    'C848_DLL': ['C-848', ],
    'C880_DLL': ['C-880', ],
    'E816_DLL': ['E-621', 'E-625', 'E-665', 'E-816', 'E816', ],
    'E516_DLL': ['E-516', ],
    'PI_Mercury_GCS_DLL': ['C-663.10', 'C-863.10', 'MERCURY', 'MERCURY_GCS1', ],
    'PI_HydraPollux_GCS2_DLL': ['HYDRA', 'POLLUX', 'POLLUX2', 'POLLUXNT', ],
    'E7XX_GCS_DLL': ['DIGITAL PIEZO CONTROLLER', 'E-710', 'E-761', ],
    'HEX_GCS_DLL': ['HEXAPOD', 'HEXAPOD_GCS1', ],
    'PI_G_GCS2_DLL': ['UNKNOWN', ],
    """

    _controller_units = 'mm'  # dependent on the stage type so to be updated accordingly using self.controller_units = new_unit

    GCS_path = ""
    GCS_paths = ["C:\\ProgramData\\PI\\GCSTranslator"]
    devices = []
    #GCS_path = "C:\\Program Files (x86)\\PI\\GCSTranslator"

    dll_name = ''
    for GCS_path_tmp in GCS_paths:
        try:
            #check for installed dlls
            flag = False
            if '64' in platform.machine():
                machine = "64"
            for dll_name_tmp in DLLDEVICES:
                for file in os.listdir(GCS_path_tmp):
                    if dll_name_tmp in file and '.dll' in file and machine in file:
                        dll_name = file
                        flag = True
                    if flag:
                        break
                if flag:
                    break

            gcs_device = GCSDevice(gcsdll=os.path.join(GCS_path_tmp, dll_name))
            devices = gcs_device.EnumerateUSB()
            GCS_path = GCS_path_tmp
        except Exception as e:
            pass

    import serial.tools.list_ports as list_ports
    devices.extend([str(port) for port in list(list_ports.comports())])
    is_multiaxes = False
    stage_names = []

    params = [{
        'title': 'GCS2 library:',
        'name': 'gcs_lib',
        'type': 'browsepath',
        'value': os.path.join(GCS_path_tmp, dll_name),
        'filetype': True
    }, {
        'title': 'Connection_type:',
        'name': 'connect_type',
        'type': 'list',
        'value': 'USB',
        'values': ['USB', 'TCP/IP', 'RS232']
    }, {
        'title': 'Devices:',
        'name': 'devices',
        'type': 'list',
        'values': devices
    }, {
        'title':
        'Daisy Chain Options:',
        'name':
        'dc_options',
        'type':
        'group',
        'children': [{
            'title': 'Use Daisy Chain:',
            'name': 'is_daisy',
            'type': 'bool',
            'value': False
        }, {
            'title': 'Is master?:',
            'name': 'is_daisy_master',
            'type': 'bool',
            'value': False
        }, {
            'title': 'Daisy Master Id:',
            'name': 'daisy_id',
            'type': 'int'
        }, {
            'title': 'Daisy Devices:',
            'name': 'daisy_devices',
            'type': 'list'
        }, {
            'title': 'Index in chain:',
            'name': 'index_in_chain',
            'type': 'int',
            'enabled': True
        }]
    }, {
        'title': 'Use Joystick:',
        'name': 'use_joystick',
        'type': 'bool',
        'value': False
    }, {
        'title': 'Closed loop?:',
        'name': 'closed_loop',
        'type': 'bool',
        'value': True
    }, {
        'title': 'Controller ID:',
        'name': 'controller_id',
        'type': 'str',
        'value': '',
        'readonly': True
    }, {
        'title': 'Stage address:',
        'name': 'axis_address',
        'type': 'list'
    }, {
        'title':
        'MultiAxes:',
        'name':
        'multiaxes',
        'type':
        'group',
        'visible':
        is_multiaxes,
        'children': [
            {
                'title': 'is Multiaxes:',
                'name': 'ismultiaxes',
                'type': 'bool',
                'value': is_multiaxes,
                'default': False
            },
            {
                'title': 'Status:',
                'name': 'multi_status',
                'type': 'list',
                'value': 'Master',
                'values': ['Master', 'Slave']
            },
            {
                'title': 'Axis:',
                'name': 'axis',
                'type': 'list',
                'values': stage_names
            },
        ]
    }] + comon_parameters

    def __init__(self, parent=None, params_state=None):

        super(DAQ_Move_PI, self).__init__(parent, params_state)
        self.settings.child(('epsilon')).setValue(0.01)

        self.is_referencing_function = True

    def commit_settings(self, param):
        """
            | Activate any parameter changes on the PI_GCS2 hardware.
            |
            | Called after a param_tree_changed signal from DAQ_Move_main.

            =============== ================================ ========================
            **Parameters**  **Type**                          **Description**
            *param*         instance of pyqtgraph Parameter  The parameter to update
            =============== ================================ ========================

            See Also
            --------
            daq_utils.ThreadCommand, DAQ_Move_PI.enumerate_devices
        """
        try:
            if param.name() == 'gcs_lib':
                try:
                    self.controller.CloseConnection()
                except Exception as e:
                    self.emit_status(
                        ThreadCommand("Update_Status",
                                      [getLineInfo() + str(e), 'log']))
                self.ini_device()

            elif param.name() == 'connect_type':
                self.enumerate_devices()

            elif param.name() == 'use_joystick':
                axes = self.controller.axes
                for ind, ax in enumerate(axes):
                    try:
                        if param.value():
                            res = self.controller.JAX(1, ind + 1, ax)
                            res = self.controller.JON(ind + 1, True)
                        else:
                            self.controller.JON(ind + 1, False)
                    except Exception as e:
                        pass

                pass
            elif param.name() == 'axis_address':
                self.settings.child(('closed_loop')).setValue(
                    self.controller.qSVO(param.value())[param.value()])
                self.set_referencing(
                    self.settings.child(('axis_address')).value())

            elif param.name() == 'closed_loop':
                axe = self.settings.child(('axis_address')).value()
                if self.controller.qSVO(axe)[axe] != self.settings.child(
                    ('closed_loop')).value():
                    self.controller.SVO(axe, param.value())

        except Exception as e:
            self.emit_status(
                ThreadCommand("Update_Status",
                              [getLineInfo() + str(e), 'log']))

    def enumerate_devices(self):
        """
            Enumerate PI_GCS2 devices from the connection type.

            Returns
            -------
            string list
                The list of the devices port.

            See Also
            --------
            daq_utils.ThreadCommand
        """
        try:
            devices = []
            if self.settings.child(('connect_type')).value() == 'USB':
                devices = self.controller.EnumerateUSB()
            elif self.settings.child(('connect_type')).value() == 'TCP/IP':
                devices = self.controller.EnumerateTCPIPDevices()
            elif self.settings.child(('connect_type')).value() == 'RS232':
                devices = [
                    str(port) for port in list(self.list_ports.comports())
                ]

            self.settings.child(('devices')).setLimits(devices)

            return devices
        except Exception as e:
            self.emit_status(
                ThreadCommand("Update_Status",
                              [getLineInfo() + str(e), 'log']))

    def ini_device(self):
        """
            load the correct dll given the chosen device

            See Also
            --------
            DAQ_Move_base.close
        """

        try:
            self.close()
        except:
            pass

        device = self.settings.child(('devices')).value()
        if self.settings.child(
            ('connect_type')).value() == 'TCP/IP' or self.settings.child(
                ('connect_type')).value() == 'RS232':
            dll_path_tot = self.settings.child(('gcs_lib')).value()

        else:

            # device = self.settings.child(('devices')).value().rsplit(' ')
            # dll = None
            # flag = False
            # for dll_tmp in DLLDEVICES:
            #     for dev in DLLDEVICES[dll_tmp]:
            #         for d in device:
            #             if (d in dev or dev in d) and d != '':
            #                 res=self.check_dll_exist(dll_tmp)
            #                 if res[0]:
            #                     dll = res[1]
            #                     flag = True
            #                     break
            #         if flag:
            #             break
            #     if flag:
            #         break
            #
            # if dll is None:
            #     raise Exception('No valid dll found for the given device')
            # dll_path = os.path.split(self.settings.child(('gcs_lib')).value())[0]
            # dll_path_tot = os.path.join(dll_path,dll)
            #dll_name = get_dll_name(self.settings.child(('devices')).value())
            #dll_path_tot = get_dll_path(dll_name)
            #self.settings.child(('gcs_lib')).setValue(dll_path_tot)
            dll_path_tot = self.settings.child(('gcs_lib')).value()
        self.controller = GCSDevice(gcsdll=dll_path_tot)

    def check_dll_exist(self, dll_name):
        files = os.listdir(
            os.path.split(self.settings.child(('gcs_lib')).value())[0])
        machine = ''
        if '64' in platform.machine():
            machine = '64'
        res = (False, '')
        for file in files:
            if 'dll' in file and machine in file and dll_name in file:
                res = (True, file)
        return res

    def ini_stage(self, controller=None):
        """
            Initialize the controller and stages (axes) with given parameters.

            =============== =========================================== ==========================================================================================
            **Parameters**  **Type**                                     **Description**

            *controller*     instance of the specific controller object  If defined this hardware will use it and will not initialize its own controller instance
            =============== =========================================== ==========================================================================================

            Returns
            -------
            Easydict
                dictionnary containing keys:
                 * *info* : string displaying various info
                 * *controller*: instance of the controller object in order to control other axes without the need to init the same controller twice
                 * *stage*: instance of the stage (axis or whatever) object
                 * *initialized*: boolean indicating if initialization has been done corretly

            See Also
            --------
            DAQ_Move_PI.set_referencing, daq_utils.ThreadCommand
        """

        try:
            device = ""
            # initialize the stage and its controller status
            # controller is an object that may be passed to other instances of DAQ_Move_Mock in case
            # of one controller controlling multiaxes

            self.status.update(
                edict(info="", controller=None, initialized=False))

            #check whether this stage is controlled by a multiaxe controller (to be defined for each plugin)

            # if mutliaxes then init the controller here if Master state otherwise use external controller
            if self.settings.child(
                    'multiaxes',
                    'ismultiaxes').value() and self.settings.child(
                        'multiaxes', 'multi_status').value() == "Slave":
                if controller is None:
                    raise Exception(
                        'no controller has been defined externally while this axe is a slave one'
                    )
                else:
                    self.controller = controller
            else:  #Master stage
                self.ini_device(
                )  #create a fresh and new instance of GCS device (in case multiple instances of DAQ_MOVE_PI are opened)

                device = self.settings.child(('devices')).value()
                if not self.settings.child(
                        'dc_options', 'is_daisy').value():  #simple connection
                    if self.settings.child(('connect_type')).value() == 'USB':
                        self.controller.ConnectUSB(device)
                    elif self.settings.child(
                        ('connect_type')).value() == 'TCP/IP':
                        self.controller.ConnectTCPIPByDescription(device)
                    elif self.settings.child(
                        ('connect_type')).value() == 'RS232':
                        self.controller.ConnectRS232(
                            int(device[3:])
                        )  #in this case device is a COM port, and one should use 1 for COM1 for instance

                else:  #one use a daisy chain connection with a master device and slaves
                    if self.settings.child(
                            'dc_options',
                            'is_daisy_master').value():  #init the master

                        if self.settings.child(
                            ('connect_type')).value() == 'USB':
                            dev_ids = self.controller.OpenUSBDaisyChain(device)
                        elif self.settings.child(
                            ('connect_type')).value() == 'TCP/IP':
                            dev_ids = self.controller.OpenTCPIPDaisyChain(
                                device)
                        elif self.settings.child(
                            ('connect_type')).value() == 'RS232':
                            dev_ids = self.controller.OpenRS232DaisyChain(
                                int(device[3:])
                            )  #in this case device is a COM port, and one should use 1 for COM1 for instance

                        self.settings.child('dc_options',
                                            'daisy_devices').setLimits(dev_ids)
                        self.settings.child('dc_options', 'daisy_id').setValue(
                            self.controller.dcid)

                    self.controller.ConnectDaisyChainDevice(
                        self.settings.child('dc_options',
                                            'index_in_chain').value() + 1,
                        self.settings.child('dc_options', 'daisy_id').value())

            self.settings.child(
                ('controller_id')).setValue(self.controller.qIDN())
            self.settings.child(
                ('axis_address')).setLimits(self.controller.axes)

            self.set_referencing(self.controller.axes[0])

            #check servo status:
            self.settings.child(('closed_loop')).setValue(
                self.controller.qSVO(
                    self.controller.axes[0])[self.controller.axes[0]])

            self.status.controller = self.controller

            self.status.info = "connected on device:{} /".format(
                device) + self.controller.qIDN()
            self.status.controller = self.controller
            self.status.initialized = True
            return self.status

        except Exception as e:
            self.emit_status(
                ThreadCommand('Update_Status',
                              [getLineInfo() + str(e), 'log']))
            self.status.info = getLineInfo() + str(e)
            self.status.initialized = False
            return self.status

    def is_referenced(self, axe):
        """
            Return the referencement statement from the hardware device.

            ============== ========== ============================================
            **Parameters**  **Type**   **Description**

             *axe*          string     Representing a connected axe on controller
            ============== ========== ============================================

            Returns
            -------
            ???

        """
        try:
            if self.controller.HasqFRF():
                return self.controller.qFRF(axe)[axe]
            else:
                return False
        except:
            return False

    def set_referencing(self, axes):
        """
            Set the referencement statement into the hardware device.

            ============== ============== ===========================================
            **Parameters**    **Type**      **Description**
             *axes*           string list  Representing connected axes on controller
            ============== ============== ===========================================
        """
        try:
            if type(axes) is not list:
                axes = [axes]
            for axe in axes:
                #set referencing mode
                if type(axe) is str:
                    if self.is_referenced(axe):
                        if self.controller.HasRON():
                            self.controller.RON(axe, True)
                        self.controller.FRF(axe)
        except Exception as e:
            self.emit_status(
                ThreadCommand('Update_Status', [
                    getLineInfo() + str(e) +
                    " / Referencing not enabled with this dll", 'log'
                ]))

    def close(self):
        """
            close the current instance of PI_GCS2 instrument.
        """
        if not self.settings.child('dc_options',
                                   'is_daisy').value():  #simple connection
            self.controller.CloseConnection()
        else:
            self.controller.CloseDaisyChain()

    def stop_motion(self):
        """
            See Also
            --------
            DAQ_Move_base.move_done
        """
        self.controller.StopAll()
        self.move_done()

    def check_position(self):
        """
            Get the current hardware position with scaling conversion of the PI_GCS2 instrument provided by get_position_with_scaling

            See Also
            --------
            DAQ_Move_base.get_position_with_scaling, daq_utils.ThreadCommand
        """
        self.set_referencing(self.settings.child(('axis_address')).value())
        pos_dict = self.controller.qPOS(
            self.settings.child(('axis_address')).value())
        pos = pos_dict[self.settings.child(('axis_address')).value()]
        pos = self.get_position_with_scaling(pos)
        self.current_position = pos
        self.emit_status(ThreadCommand('check_position', [pos]))
        return pos

    def move_Abs(self, position):
        """
            Make the hardware absolute move of the PI_GCS2 instrument from the given position after thread command signal was received in DAQ_Move_main.

            =============== ========= =======================
            **Parameters**  **Type**   **Description**

            *position*       float     The absolute position
            =============== ========= =======================

            See Also
            --------
            DAQ_Move_PI.set_referencing, DAQ_Move_base.set_position_with_scaling, DAQ_Move_base.poll_moving

        """

        position = self.check_bound(position)
        self.target_position = position

        position = self.set_position_with_scaling(position)
        out = self.controller.MOV(
            self.settings.child(('axis_address')).value(), position)

        self.poll_moving()

    def move_Rel(self, position):
        """
            Make the hardware relative move of the PI_GCS2 instrument from the given position after thread command signal was received in DAQ_Move_main.

            =============== ========= =======================
            **Parameters**  **Type**   **Description**

            *position*       float     The absolute position
            =============== ========= =======================

            See Also
            --------
            DAQ_Move_base.set_position_with_scaling, DAQ_Move_PI.set_referencing, DAQ_Move_base.poll_moving

        """
        position = self.check_bound(self.current_position +
                                    position) - self.current_position
        self.target_position = position + self.current_position

        position = self.set_position_relative_with_scaling(position)

        if self.controller.HasMVR():
            out = self.controller.MVR(
                self.settings.child(('axis_address')).value(), position)
        else:
            self.move_Abs(self.target_position)
        self.poll_moving()

    def move_Home(self):
        """

            See Also
            --------
            DAQ_Move_PI.set_referencing, DAQ_Move_base.poll_moving
        """
        self.set_referencing(self.settings.child(('axis_address')).value())
        if self.controller.HasGOH():
            self.controller.GOH(self.settings.child(('axis_address')).value())
        elif self.controller.HasFRF():
            self.controller.FRF(self.settings.child(('axis_address')).value())
        else:
            self.move_Abs(0)
        self.poll_moving()
示例#2
0
class PIMotorStage(Base, MotorInterface):
    """ Class representing the PI 3 axis positioning motor stage

    Example config for copy-paste:

    pi_stage:
        module.Class: 'motor.motor_pi_3axis_stage.PIMotorStage'
        serialnumber_master:  '0019550121'
        first_axis_controllername: 'C-863'
        second_axis_controllername: 'C-863'
        third_axis_controllername: 'C-863'
        first_axis_label: 'x'
        second_axis_label: 'y'
        third_axis_label: 'z'
        first_axis_daisychain_id: 2  # number of the device in the daisy chain (sorted by increasing serial number of the controller)
        second_axis_daisychain_id: 3
        third_axis_daisychain_id: 1
    """
    _serialnum_master = ConfigOption('serialnumber_master', missing='error')
    _first_axis_controllername = ConfigOption('first_axis_controllername',
                                              missing='error')
    _second_axis_controllername = ConfigOption('second_axis_controllername',
                                               missing='error')
    _third_axis_controllername = ConfigOption('third_axis_controllername',
                                              missing='error')
    _first_axis_label = ConfigOption('first_axis_label', missing='error')
    _second_axis_label = ConfigOption('second_axis_label', missing='error')
    _third_axis_label = ConfigOption('third_axis_label', missing='error')
    _first_axis_daisychain_id = ConfigOption('first_axis_daisychain_id',
                                             missing='error')
    _second_axis_daisychain_id = ConfigOption('second_axis_daisychain_id',
                                              missing='error')
    _third_axis_daisychain_id = ConfigOption('third_axis_daisychain_id',
                                             missing='error')

    def __init__(self, config, **kwargs):
        super().__init__(config=config, **kwargs)

    # def __init__(self, *args, **kwargs):
    #     super().__init__()

    def on_activate(self):
        try:
            self.first_axis_label = self._first_axis_label
            self.second_axis_label = self._second_axis_label
            self.third_axis_label = self._third_axis_label

            # open the daisy chain connection
            self.pidevice_1st_axis = GCSDevice(
                self._first_axis_controllername
            )  # 1st axis controller # master device
            self.pidevice_2nd_axis = GCSDevice(
                self._second_axis_controllername)  # 2nd axis controller
            self.pidevice_3rd_axis = GCSDevice(
                self._third_axis_controllername)  # 3rd axis controller

            self.pidevice_1st_axis.OpenUSBDaisyChain(
                description=self._serialnum_master)
            self.daisychainid = self.pidevice_1st_axis.dcid
            print(f'Daisychainid: {self.daisychainid}')
            # controllers are ordered with increasing serial number in the daisy chain
            # this is why z is connected as first
            # do we need to programmatically sort by nth_axis_daisychain id ??
            self.pidevice_3rd_axis.ConnectDaisyChainDevice(
                self._third_axis_daisychain_id,
                self.daisychainid)  # SN 019550119
            self.pidevice_1st_axis.ConnectDaisyChainDevice(
                self._first_axis_daisychain_id,
                self.daisychainid)  # SN 019550121
            self.pidevice_2nd_axis.ConnectDaisyChainDevice(
                self._second_axis_daisychain_id,
                self.daisychainid)  # SN 019550124
            print('\n{}:\n{}'.format(
                self.pidevice_1st_axis.GetInterfaceDescription(),
                self.pidevice_1st_axis.qIDN()))
            print('\n{}:\n{}'.format(
                self.pidevice_2nd_axis.GetInterfaceDescription(),
                self.pidevice_2nd_axis.qIDN()))
            print('\n{}:\n{}'.format(
                self.pidevice_3rd_axis.GetInterfaceDescription(),
                self.pidevice_3rd_axis.qIDN()))

            # initialization of all axes
            print('Initializing PI stage ...')
            # servo on
            pitools.startup(self.pidevice_1st_axis)
            pitools.startup(self.pidevice_2nd_axis)
            pitools.startup(self.pidevice_3rd_axis)
            print('Please wait ... ')

            # the IDs are needed to address the axes in the dll functions
            self.first_axis_ID = self.pidevice_1st_axis.axes[
                0]  # each controller is connected to one stage; so just take the first element
            # print(self.first_axis_ID)
            self.second_axis_ID = self.pidevice_2nd_axis.axes[0]
            # print(self.second_axis_ID)
            self.third_axis_ID = self.pidevice_3rd_axis.axes[0]
            # print(self.third_axis_ID)

            self.calibrate()
            # RON:
            # FNL: fast move to negative limit
            # self.pidevice_1st_axis.RON(self.first_axis_ID, values=1)
            # self.pidevice_1st_axis.FNL(self.first_axis_ID)
            # self.pidevice_2nd_axis.RON(self.second_axis_ID, values=1)
            # self.pidevice_2nd_axis.FNL(self.second_axis_ID)
            # self.pidevice_3rd_axis.RON(self.third_axis_ID, values=1)
            # self.pidevice_3rd_axis.FNL(self.third_axis_ID)
            # pitools.waitontarget(self.pidevice_1st_axis, axes=self.first_axis_ID)
            # pitools.waitontarget(self.pidevice_2nd_axis, axes=self.second_axis_ID)
            # pitools.waitontarget(self.pidevice_3rd_axis, axes=self.third_axis_ID)
        except Exception as e:
            self.log.error(
                f'Physik Instrumente 3-axes stage: Connection failed: {e}. Check if device is switched on.'
            )

    def on_deactivate(self):
        """ Required deactivation steps
        """
        # set position (0, 0, 0)
        # first move z to default position and wait until reached
        self.pidevice_3rd_axis.MOV(self.third_axis_ID, 0.0)
        pitools.waitontarget(self.pidevice_3rd_axis, axes=self.third_axis_ID)
        # when z is at safety position, xy move can be done
        self.pidevice_1st_axis.MOV(self.first_axis_ID, 0.0)
        self.pidevice_2nd_axis.MOV(self.second_axis_ID, 0.0)
        pitools.waitontarget(self.pidevice_1st_axis, axes=self.first_axis_ID)
        pitools.waitontarget(self.pidevice_2nd_axis, axes=self.second_axis_ID)

        self.pidevice_1st_axis.CloseDaisyChain(
        )  # check if connection always done with controller corresponing to 1st axis
        self.pidevice_1st_axis.CloseConnection()

    def get_constraints(self):
        """ Retrieve the hardware constrains from the motor device.

        @return dict: dict with constraints for the motor hardware

        Provides all the constraints for each axis of a motorized stage
        (like total travel distance, velocity, ...)
        Each axis has its own dictionary, where the label is used as the
        identifier throughout the whole module. The dictionaries for each axis
        are again grouped together in a constraints dictionary in the form

            {'<label_axis0>': axis0 }

        where axis0 is again a dict with the possible values defined below. The
        possible keys in the constraint are defined here in the interface file.
        If the hardware does not support the values for the constraints, then
        insert just None. If you are not sure about the meaning, look in other
        hardware files to get an impression.
        """
        constraints = {}

        # retrieve information from hardware
        pos_min_x = self.pidevice_1st_axis.qTMN()[self.first_axis_ID]
        pos_max_x = self.pidevice_1st_axis.qTMX()[self.first_axis_ID]
        vel_min_x = 0  # self.pidevice_c863_x.q
        vel_max_x = 20  # self.pidevice_c863_x.q  # need to find the command

        axis0 = {}
        axis0[
            'label'] = self.first_axis_label  # it is very crucial that this label coincides with the label set in the config.
        axis0['unit'] = 'm'  # the SI units, only possible m or degree
        axis0['ramp'] = None  # do we need this ?
        axis0['pos_min'] = pos_min_x
        axis0['pos_max'] = pos_max_x
        axis0['pos_step'] = pos_max_x
        axis0['vel_min'] = vel_min_x
        axis0['vel_max'] = vel_max_x
        axis0['vel_step'] = 0.01  # can this also be queried ?
        axis0['acc_min'] = None  # do we need this ?
        axis0['acc_max'] = None
        axis0['acc_step'] = None

        # retrieve information from hardware
        pos_min_y = self.pidevice_2nd_axis.qTMN()[self.second_axis_ID]
        pos_max_y = self.pidevice_2nd_axis.qTMX()[self.second_axis_ID]
        vel_min_y = 0  # self.pidevice_c863_y.q
        vel_max_y = 20  # self.pidevice_c863_y.q  # need to find the command

        axis1 = {}
        axis1[
            'label'] = self.second_axis_label  # it is very crucial that this label coincides with the label set in the config.
        axis1['unit'] = 'm'  # the SI units, only possible m or degree
        axis1['ramp'] = None  # do we need this ?
        axis1['pos_min'] = pos_min_y
        axis1['pos_max'] = pos_max_y
        axis1[
            'pos_step'] = pos_max_y  # allow to go directly from low limit to maximum
        axis1['vel_min'] = vel_min_y
        axis1['vel_max'] = vel_max_y
        axis1['vel_step'] = 0.01  # can this also be queried ?
        axis1['acc_min'] = None  # do we need this ?
        axis1['acc_max'] = None
        axis1['acc_step'] = None

        # retrieve information from hardware
        pos_min_z = self.pidevice_3rd_axis.qTMN()[self.third_axis_ID]
        pos_max_z = self.pidevice_3rd_axis.qTMX()[self.third_axis_ID]
        vel_min_z = 0  # self.pidevice_c863_z.q
        vel_max_z = 20  # self.pidevice_c863_z.q  # need to find the command

        axis2 = {}
        axis2[
            'label'] = self.third_axis_label  # it is very crucial that this label coincides with the label set in the config.
        axis2['unit'] = 'm'  # the SI units, only possible m or degree
        axis2['ramp'] = None  # do we need this ?
        axis2['pos_min'] = pos_min_z
        axis2['pos_max'] = pos_max_z
        axis2[
            'pos_step'] = pos_max_z  # can this also be queried from the hardware ? if not just set a reasonable value
        axis2['vel_min'] = vel_min_z
        axis2['vel_max'] = vel_max_z
        axis2['vel_step'] = 0.01  # can this also be queried ?
        axis2['acc_min'] = None  # do we need this ?
        axis2['acc_max'] = None
        axis2['acc_step'] = None

        # assign the parameter container for each axis to a name which will identify it
        constraints[axis0['label']] = axis0
        constraints[axis1['label']] = axis1
        constraints[axis2['label']] = axis2

        return constraints

    def move_rel(self, param_dict):
        """ Moves stage in given direction (relative movement)

        @param dict param_dict: dictionary, which passes all the relevant
                                parameters, which should be changed. Usage:
                                 {'axis_label': <the-abs-pos-value>}.
                                 'axis_label' must correspond to a label given
                                 to one of the axis.

        A smart idea would be to ask the position after the movement.

        @return int: error code (0:OK, -1:error)
        """
        constraints = self.get_constraints()
        cur_pos = self.get_pos()
        for key, value in param_dict.items(
        ):  # param_dict has the format {'x': 20, 'y': 0, 'z': 10} for example
            if key == self.first_axis_label:
                if value <= constraints[
                        self.first_axis_label]['pos_step'] and constraints[
                            self.first_axis_label]['pos_min'] <= cur_pos[
                                self.first_axis_label] + value <= constraints[
                                    self.first_axis_label]['pos_max']:
                    self.pidevice_1st_axis.MVR(self.first_axis_ID, value)
                    # pitools.waitontarget(self.pidevice_1st_axis, axes=self.first_axis_ID)
                else:
                    print(
                        'Target value not in allowed range. Relative movement not done.'
                    )
            elif key == self.second_axis_label:
                if value <= constraints[
                        self.second_axis_label]['pos_step'] and constraints[
                            self.second_axis_label]['pos_min'] <= cur_pos[
                                self.second_axis_label] + value <= constraints[
                                    self.second_axis_label]['pos_max']:
                    self.pidevice_2nd_axis.MVR(self.second_axis_ID, value)
                    # pitools.waitontarget(self.pidevice_2nd_axis, axes=self.second_axis_ID)
                else:
                    print(
                        'Target value not in allowed range. Relative movement not done.'
                    )
            elif key == self.third_axis_label:
                if value <= constraints[
                        self.third_axis_label]['pos_step'] and constraints[
                            self.third_axis_label]['pos_min'] <= cur_pos[
                                self.third_axis_label] + value <= constraints[
                                    self.third_axis_label]['pos_max']:
                    self.pidevice_3rd_axis.MVR(self.third_axis_ID, value)
                    # pitools.waitontarget(self.pidevice_3rd_axis, axes=self.third_axis_ID)
                else:
                    print(
                        'Target value not in allowed range. Relative movement not done.'
                    )
            else:
                print('Given axis not available.')

        # handle the return statement

    def move_abs(self, param_dict):
        """ Moves stage to absolute position (absolute movement)

        @param dict param_dict: dictionary, which passes all the relevant
                                parameters, which should be changed. Usage:
                                 {'axis_label': <the-abs-pos-value>}.
                                 'axis_label' must correspond to a label given
                                 to one of the axis.

        @return int: error code (0:OK, -1:error)
        """
        constraints = self.get_constraints()
        for key, value in param_dict.items():
            if key == self.first_axis_label:
                if constraints[self.first_axis_label][
                        'pos_min'] <= value <= constraints[
                            self.first_axis_label]['pos_max']:
                    self.pidevice_1st_axis.MOV(self.first_axis_ID, value)
                    # pitools.waitontarget(self.pidevice_1st_axis, axes=self.first_axis_ID)
                else:
                    print(
                        'Target value not in allowed range. Absolute movement not done.'
                    )
            elif key == self.second_axis_label:
                if constraints[self.second_axis_label][
                        'pos_min'] <= value <= constraints[
                            self.second_axis_label]['pos_max']:
                    self.pidevice_2nd_axis.MOV(self.second_axis_ID, value)
                    # pitools.waitontarget(self.pidevice_2nd_axis, axes=self.second_axis_ID)
                else:
                    print(
                        'Target value not in allowed range. Absolute movement not done.'
                    )
            elif key == self.third_axis_label:
                if constraints[self.third_axis_label][
                        'pos_min'] <= value <= constraints[
                            self.third_axis_label]['pos_max']:
                    self.pidevice_3rd_axis.MOV(self.third_axis_ID, value)
                    # pitools.waitontarget(self.pidevice_3rd_axis, axes=self.third_axis_ID)
                else:
                    print(
                        'Target value not in allowed range. Absolute movement not done.'
                    )
            else:
                print('Given axis not available.')

        # handle the return statement

    def abort(self):
        """ Stops movement of the stage

        @return int: error code (0:OK, -1:error)
        """
        self.pidevice_1st_axis.HLT(
            noraise=True)  # noraise option silences GCSerror 10
        self.pidevice_2nd_axis.HLT(noraise=True)
        self.pidevice_3rd_axis.HLT(noraise=True)
        print('Movement aborted.')

        # handle return value

    def get_pos(self, param_list=None):
        """ Gets current position of the stage arms

        @param list param_list: optional, if a specific position of an axis
                                is desired, then the labels of the needed
                                axis should be passed in the param_list.
                                If nothing is passed, then from each axis the
                                position is asked.

        @return dict: with keys being the axis labels and item the current
                      position.
        """
        # # if stage is moving, wait until movement done before reading position
        # pitools.waitontarget(self.pidevice_1st_axis, axes=self.first_axis_ID)
        # pitools.waitontarget(self.pidevice_2nd_axis, axes=self.second_axis_ID)
        # pitools.waitontarget(self.pidevice_3rd_axis, axes=self.third_axis_ID)
        if not param_list:
            x_pos = self.pidevice_1st_axis.qPOS(
            )[self.
              first_axis_ID]  # qPOS returns OrderedDict, we just need the value for the single axis
            y_pos = self.pidevice_2nd_axis.qPOS()[self.second_axis_ID]
            z_pos = self.pidevice_3rd_axis.qPOS()[self.third_axis_ID]
            positions = [x_pos, y_pos, z_pos]
            keys = [
                self.first_axis_label, self.second_axis_label,
                self.third_axis_label
            ]
            pos_dict = dict(zip(keys, positions))
            return pos_dict
        else:
            pos_dict = {}
            for item in param_list:
                if item == self.first_axis_label:
                    x_pos = self.pidevice_1st_axis.qPOS()[self.first_axis_ID]
                    pos_dict[item] = x_pos
                elif item == self.second_axis_label:
                    y_pos = self.pidevice_2nd_axis.qPOS()[self.second_axis_ID]
                    pos_dict[item] = y_pos
                elif item == self.third_axis_label:
                    z_pos = self.pidevice_3rd_axis.qPOS()[self.third_axis_ID]
                    pos_dict[item] = z_pos
                else:
                    print('Given axis not available.')
            return pos_dict

# IsControllerReady does eventually not give the right information. Better replace by an information if stage moving / not moving
# use qONT query ? on target ?

    def get_status(self, param_list=None):
        """ Get the status of the position

        @param list param_list: optional, if a specific status of an axis
                                is desired, then the labels of the needed
                                axis should be passed in the param_list.
                                If nothing is passed, then from each axis the
                                status is asked.

        @return dict: with the axis label as key and the on target status as value.
        """
        if not param_list:
            x_status = self.pidevice_1st_axis.qONT()[self.first_axis_ID]
            y_status = self.pidevice_2nd_axis.qONT()[self.second_axis_ID]
            z_status = self.pidevice_3rd_axis.qONT()[self.third_axis_ID]
            on_target = [x_status, y_status, z_status]
            keys = [
                self.first_axis_label, self.second_axis_label,
                self.third_axis_label
            ]
            status_dict = dict(zip(keys, on_target))
            return status_dict
        else:
            status_dict = {}
            for item in param_list:
                if item == self.first_axis_label:
                    x_status = self.pidevice_1st_axis.qONT()[
                        self.first_axis_ID]
                    status_dict[item] = x_status
                elif item == self.second_axis_label:
                    y_status = self.pidevice_2nd_axis.qONT()[
                        self.second_axis_ID]
                    status_dict[item] = y_status
                elif item == self.third_axis_label:
                    z_status = self.pidevice_3rd_axis.qONT()[
                        self.third_axis_ID]
                    status_dict[item] = z_status
                else:
                    print('Given axis not available.')
            return status_dict

    def calibrate(self, param_list=None):
        """ Calibrates the stage.

        @param dict param_list: param_list: optional, if a specific calibration
                                of an axis is desired, then the labels of the
                                needed axis should be passed in the param_list.
                                If nothing is passed, then all connected axis
                                will be calibrated.

        @return int: error code (0:OK, -1:error)

        After calibration the stage moves to home position which will be the
        zero point for the passed axis. The calibration procedure will be
        different for each stage.
        """
        if not param_list:
            # 3rd axis is typically z. Calibrate and move first z to negative limit
            self.pidevice_3rd_axis.RON(self.third_axis_ID, values=1)
            self.pidevice_3rd_axis.FNL(self.third_axis_ID)
            pitools.waitontarget(self.pidevice_3rd_axis,
                                 axes=self.third_axis_ID)

            self.pidevice_1st_axis.RON(self.first_axis_ID, values=1)
            self.pidevice_1st_axis.FNL(self.first_axis_ID)
            self.pidevice_2nd_axis.RON(self.second_axis_ID, values=1)
            self.pidevice_2nd_axis.FNL(self.second_axis_ID)
            pitools.waitontarget(self.pidevice_1st_axis,
                                 axes=self.first_axis_ID)
            pitools.waitontarget(self.pidevice_2nd_axis,
                                 axes=self.second_axis_ID)

        else:
            for item in param_list:
                if item == self.first_axis_label:
                    self.pidevice_1st_axis.RON(self.first_axis_ID, values=1)
                    self.pidevice_1st_axis.FNL(self.first_axis_ID)
                    pitools.waitontarget(self.pidevice_1st_axis,
                                         axes=self.first_axis_ID)
                elif item == self.second_axis_label:
                    self.pidevice_2nd_axis.RON(self.second_axis_ID, values=1)
                    self.pidevice_2nd_axis.FNL(self.second_axis_ID)
                    pitools.waitontarget(self.pidevice_2nd_axis,
                                         axes=self.second_axis_ID)
                elif item == self.third_axis_label:
                    self.pidevice_3rd_axis.RON(self.third_axis_ID, values=1)
                    self.pidevice_3rd_axis.FNL(self.third_axis_ID)
                    pitools.waitontarget(self.pidevice_3rd_axis,
                                         axes=self.third_axis_ID)
                else:
                    print('Given axis not available.')

    def get_velocity(self, param_list=None):
        """ Gets the current velocity for all connected axes.

        @param dict param_list: optional, if a specific velocity of an axis
                                is desired, then the labels of the needed
                                axis should be passed as the param_list.
                                If nothing is passed, then from each axis the
                                velocity is asked.

        @return dict : with the axis label as key and the velocity as item.
        """
        if not param_list:
            x_vel = self.pidevice_1st_axis.qVEL(
            )[self.
              first_axis_ID]  # qVEL returns OrderedDict, we just need the value for the single axis
            y_vel = self.pidevice_2nd_axis.qVEL()[self.second_axis_ID]
            z_vel = self.pidevice_3rd_axis.qVEL()[self.third_axis_ID]
            velocity = [x_vel, y_vel, z_vel]
            keys = [
                self.first_axis_label, self.second_axis_label,
                self.third_axis_label
            ]
            vel_dict = dict(zip(keys, velocity))
            return vel_dict
        else:
            vel_dict = {}
            for item in param_list:
                if item == self.first_axis_label:
                    x_vel = self.pidevice_1st_axis.qVEL()[self.first_axis_ID]
                    vel_dict[item] = x_vel
                elif item == self.second_axis_label:
                    y_vel = self.pidevice_2nd_axis.qVEL()[self.second_axis_ID]
                    vel_dict[item] = y_vel
                elif item == self.third_axis_label:
                    z_vel = self.pidevice_3rd_axis.qVEL()[self.third_axis_ID]
                    vel_dict[item] = z_vel
                else:
                    print('Given axis not available.')
            return vel_dict

    def set_velocity(self, param_dict):
        """ Write new value for velocity.

        @param dict param_dict: dictionary, which passes all the relevant
                                parameters, which should be changed. Usage:
                                 {'axis_label': <the-velocity-value>}.
                                 'axis_label' must correspond to a label given
                                 to one of the axis.

        @return int: error code (0:OK, -1:error)
        """
        constraints = self.get_constraints()
        for key, value in param_dict.items(
        ):  # param_dict has the format {'x': 20, 'y': 0, 'z': 10} for example
            if key == self.first_axis_label:
                if constraints[self.first_axis_label][
                        'vel_min'] <= value <= constraints[
                            self.first_axis_label]['vel_max']:
                    self.pidevice_1st_axis.VEL(self.first_axis_ID, value)
                else:
                    print(
                        'Target value not in allowed range. Velocity not set.')
            elif key == self.second_axis_label:
                if constraints[self.second_axis_label][
                        'vel_min'] <= value <= constraints[
                            self.second_axis_label]['vel_max']:
                    self.pidevice_2nd_axis.VEL(self.second_axis_ID, value)
                else:
                    print(
                        'Target value not in allowed range. Velocity not set.')
            elif key == self.third_axis_label:
                if constraints[self.third_axis_label][
                        'vel_min'] <= value <= constraints[
                            self.third_axis_label]['vel_max']:
                    self.pidevice_3rd_axis.VEL(self.third_axis_ID, value)
                else:
                    print(
                        'Target value not in allowed range. Velocity not set.')
            else:
                print('Given axis not available.')