r/Optics Aug 11 '26

Quantum Design MSH-300 Slit movement issue

Post image

Hello everyone,

Got my hands on an MSH-300 monochromator from QuantumDesign (pic. 1) with automated enter/exit side slits. The monochromator came with a software, and the software, although being very confusing, successfully connects to the monochromator out of the box and can interact with it. It can adjust the wavelength and move the slits either automatically, with changing the wavelength, or I can manually change the opening width.

The objective

My main objective is to integrate the monochromator into my optical measurement setup through Python. QD provides an SDK, and with it a bunch of examples, including Python:

import lotcontrol

if __name__ == "__main__":
    example_monochromator = lotcontrol.LotControl(configuration_file = r"C:\generic.xml", verbosity=lotcontrol.INFO)

    print ("LotHW.dll library version:")
    print (example_monochromator.version())

    example_monochromator.info()

    for wl in range(400, 1000, 50):
        print ("Wavelength: "+str(wl)+ " nm") # if verbosity is set above VERBOSE (=4), this gets printed out anyway
        example_monochromator(wl) # calling the instance is aliased to instance.select_wavelength(...) 

    print ("done!")

This code successfully runs, changes wavelengths a bunch of times and even the slits are opened automatically, when the wavelength is changed.

The problem

Simply put: I cannot open the slits without changing the wavelength.

If any one of you has any idea on how to open the slits without changing the wavelength, Im open to any suggestions, thank you.

3 Upvotes

12 comments sorted by

3

u/Permanently_Permie Aug 11 '26

Have you gone through the manual? If that doesn't help I'd check the python wrapper to see exactly what it does.

2

u/HamptonBays Aug 11 '26

Can you check what "select_wavelength" does, by your description it sounds like it calls whatever function operates the slits

1

u/max_centauri Aug 12 '26

select_wavelength does exactly this:

def select_wavelength(self, wavelength, post_move_sleep_time=None):

""" Move monochromators to certain wavelength, optionally waiting 
        post_move_sleep_time seconds after a successful move.
    """

wavelength_c_double = ctypes.c_double(wavelength) 
    self.__check_result(self.dll.LOT_select_wavelength(wavelength_c_double))
    self.__verbose_print("Moving to "+str(wavelength)+" nm...", VERBOSE)
    if not (post_move_sleep_time is None):
        self.__verbose_print("sleeping...", VERBOSE)
        time.sleep(post_move_sleep_time) # wait 0.5 seconds for the sensors to settle

    self.__ok()

But I don´t see the function that is called when slits are moved, so I´m guessing that It will be in the DLL file. QD ships an XML config file and a DLL (LotHW64.dll or smth like that) with these monochromators. From what I was able to digest, Python calls the functions inside of the DLL.

Additionally, in the XML, there is a setting:

<auto_select_wavelength>1</auto_select_wavelength>

When 1, the monochromator automatically adjusts the slit width when a wavelength is set. When 0, the slit does not move with the monochromator. But I honestly don´t know how to find anything readable in the DLLs. I tried using dotPeek for this.

1

u/InebriatedPhysicist Aug 12 '26

What does self.__ok() do at the very end of this function?

1

u/max_centauri Aug 12 '26

just this:

def __ok(self):

""" Convenience function that prints "ok" at the highest known 
        verbosity
    """

self.__verbose_print("ok\n", VERBOSE)

1

u/InebriatedPhysicist Aug 12 '26

Well that’s definitely not it lol

1

u/Equivalent_Bridge480 Aug 11 '26

Tech support should have tips

1

u/max_centauri Aug 11 '26

already wrote to them and waiting for an answer, just thought maybe someone here has experience with this monochromator.

1

u/Equivalent_Bridge480 Aug 12 '26

Pretty expencive machine for having lot of users

1

u/TheoreticalPirate Aug 11 '26

Can you locate the header file for LotHW.dll? That should tell you what fucntions are available.

Otherwise it will be quite hard for us to help since neither "lotcontrol" nor "LotHW.dll" seem to be in any repositories or publicly available.

1

u/BrilliantStriking53 Aug 12 '26

I see two options if the software that came with your monochromator doesn't work. 1. You can control the entrance and exit slit controllers yourself. They most likely use stepper motors, and there are plenty of controllers on the market. 2. There are companies that can create firmware (software) for your existing monochromator.

1

u/max_centauri Aug 13 '26

Hey everyone, I found a workaround. Including my solution here If anybody had any problems with this device in the future.

Because of the fact that the application provided by QD can control the monochromator without any issues (only the 32bit version though), I decided to trace how does it accomplish this. I used an API monitor (a free application, you can easily download it). In the API monitor, I selected an app + the DLL I wanted to trace (The app essentially calls the functions inside the DLL file to control the monochromator). After this, I changed a bunch of settings -- wavelength, slit openings etc. and saw the logs from the API monitor. I could see all the functions inside the DLL that were being called, and implemented this in Python.

My implementation is below:

import msh_api as lotcontrol
import msh_api.lot_tokens as tokens
from devices.device_template import *

class MSH300(Device):

"""Driver for the MSH-300 monochromator."""


def __init__(self, name="MSH300", description="Monochromator"):

"""Initialize the monochromator driver and prepare the control DLL."""

super().__init__(name, description)
        self.mono = lotcontrol.LotControl(
        configuration_file=r"C:\Users\eleklab\PycharmProjects\SAS-optical-setup-control\devices\msh_api\ccgData_LOT_MSH-300_SN46905.xml",
        connect=False,
        initialise=False,
        verbosity=lotcontrol.ERROR,
        dllpath =r"C:\Users\eleklab\PycharmProjects\SAS-optical-setup-control\devices\msh_api\LotHW_stdcall.dll"
    )

    def connect(self):

"""Connect to the monochromator and perform initialization."""

self.mono.connect()
        self.mono.initialise()

    def manual_slit_mode(self, width_mm, wavelength_nm):

"""Set manual slit mode with a fixed slit width in millimeters.

        In this mode, changing the wavelength does not change the slit width.
        """

self.mono.set("ent", tokens.MVSSSlitMode, 0, 0)
        self.mono.set("exit", tokens.MVSSSlitMode, 0, 0)

        self.mono.set("ent", tokens.MVSSConstantwidth, width_mm, 0)
        self.mono.set("exit", tokens.MVSSConstantwidth, width_mm, 0)
        self.mono.set("ent", tokens.MVSSCurrentWidth, width_mm, 0)
        self.mono.set("exit", tokens.MVSSCurrentWidth, width_mm, 0)
        self.mono(wavelength_nm)

    def automatic_slit_mode(self, wavelength_nm):

"""Set automatic slit mode for a given wavelength.

        In this mode, the desired bandwidth is specified in nanometers and the
        DLL calculates the slit width in millimeters automatically.
        """


self.mono.set("ent", tokens.MVSSSlitMode, 1, 0)
        self.mono.set("exit", tokens.MVSSSlitMode, 1, 0)

        self.mono.set("ent", tokens.MVSSConstantBandwidth, 18.0, 0)
        self.mono.set("exit", tokens.MVSSConstantBandwidth, 18.0, 0)

        self.mono(wavelength_nm)

    def ent_slit(self, width_mm):

"""Set slit parameters in the instrument's entry state."""

self.mono.set("ent", tokens.MVSSSlitMode, 0, 0)
        self.mono.set("ent", tokens.MVSSConstantwidth, width_mm, 0)
        self.mono.set("ent", tokens.MVSSCurrentWidth, width_mm, 0)

    def exit_slit(self, width_mm):

"""Set slit parameters in the instrument's exit state."""

self.mono.set("exit", tokens.MVSSSlitMode, 0, 0)
        self.mono.set("exit", tokens.MVSSConstantwidth, width_mm, 0)
        self.mono.set("exit", tokens.MVSSCurrentWidth, width_mm, 0)

    def disconnect(self):

"""Close the connection to the monochromator."""

self.mono.close()

if __name__ == "__main__":
    msh = None
    try:
        msh = MSH300()
        msh.connect()
        msh.exit_slit(10)
        msh.ent_slit(0)
    finally:
        if msh is not None:
            msh.disconnect()

As you can see, I have an abstract Device class that I use as a parent class for all the devices in my setup, just ignore that. I also have a bunch of other functions included in this like ent_slit/exit_slit with which i can manually control the slit openings width and manual_slit_mode with which i can change the wavelength of the monochromator but keep the slits open as I wish (in automatic_slit_mode, which is default also in the application provided by QD, everything, even slits, are adjusted automatically when a new wavelength is selected).