from qcodes import Instrument
from qcodes.utils.validators import Ints, Numbers
import ctypes
import os
import sys
import logging
from typing import Optional
logger = logging.getLogger(__name__)
[docs]
class ShamrockCIF:
"""
Wrapper class for the ShamrockCIF.dll library.
The class has been tested for a Shamrock SR750.
Args:
dll_path: Path to the ShamrockCIF.dll file. If not set, a default path is used.
verbose: Flag for the verbose behaviour. If true, successful events are printed.
Attributes:
verbose: Flag for the verbose behaviour.
dll: WinDLL object for ShamrockCIF.dll.
"""
# default dll path
_dll_path = 'C:\\Program Files\\Andor SDK\\Shamrock64\\ShamrockCIF.dll'
# success and error codes
_success_codes = {20202: 'SHAMROCK_SUCCESS'}
_error_codes = {
20201: 'SHAMROCK_COMMUNICATION_ERROR',
20266: 'SHAMROCK_P1INVALID',
20267: 'SHAMROCK_P2INVALID',
20268: 'SHAMROCK_P3INVALID',
20269: 'SHAMROCK_P4INVALID',
20270: 'SHAMROCK_P5INVALID',
20275: 'SHAMROCK_NOT_INITIALIZED',
20292: 'SHAMROCK_NOT_AVAILABLE'
}
def __init__(self, dll_path: Optional[str] = None, verbose: bool = False):
if sys.platform != 'win32':
raise OSError("\"ShamrockCIF\" is only compatible with Microsoft Windows")
# save attributes
self.verbose: bool = verbose
# connect to the DLL
current_path = os.getcwd()
try:
os.chdir(os.path.dirname(self._dll_path))
self.dll = ctypes.windll.LoadLibrary(dll_path or self._dll_path)
finally:
os.chdir(current_path)
[docs]
def error_check(self, code, function_name=''):
if code in self._success_codes.keys():
if self.verbose:
logger.info(f"ShamrockCIF: [{function_name}]: {self._success_codes[code]}")
elif code in self._error_codes.keys():
error_string = f"ShamrockCIF: [{function_name}]: {self._error_codes[code]}"
logger.error(error_string)
raise Exception(error_string)
else:
error_string = f"ShamrockCIF: [{function_name}]: Unknown code: {code}"
logger.error(error_string)
raise Exception(error_string)
# dll functions
[docs]
def close(self):
code = self.dll.ShamrockClose()
self.error_check(code, 'ShamrockClose')
[docs]
def get_calibration(self, device, number_pixels):
c_device = ctypes.c_int(device)
c_calibration = (ctypes.c_float * number_pixels)()
c_number_pixels = ctypes.c_int(number_pixels)
code = self.dll.ShamrockGetCalibration(c_device, c_calibration, c_number_pixels)
self.error_check(code, 'ShamrockGetCalibration')
return [max(0.0, v) for v in c_calibration]
[docs]
def get_grating(self, device):
c_device = ctypes.c_int(device)
c_grating = ctypes.c_int()
code = self.dll.ShamrockGetGrating(c_device, ctypes.byref(c_grating))
self.error_check(code, 'ShamrockGetGrating')
return c_grating.value
[docs]
def get_grating_info(self, device, grating):
c_device = ctypes.c_int(device)
c_grating = ctypes.c_int(grating)
c_lines = ctypes.c_float()
c_blaze = ctypes.create_string_buffer(64)
c_home = ctypes.c_int()
c_offset = ctypes.c_int()
code = self.dll.ShamrockGetGratingInfo(c_device, c_grating, ctypes.byref(c_lines), c_blaze, ctypes.byref(c_home), ctypes.byref(c_offset))
self.error_check(code, 'ShamrockGetGratingInfo')
return c_lines.value, c_blaze.value, c_home.value, c_offset.value
[docs]
def get_number_devices(self):
c_no_devices = ctypes.c_int()
code = self.dll.ShamrockGetNumberDevices(ctypes.byref(c_no_devices))
self.error_check(code, 'ShamrockGetNumberDevices')
return c_no_devices.value
[docs]
def get_number_gratings(self, device):
c_device = ctypes.c_int(device)
c_no_gratings = ctypes.c_int()
code = self.dll.ShamrockGetNumberGratings(c_device, ctypes.byref(c_no_gratings))
self.error_check(code, 'ShamrockGetNumberGratings')
return c_no_gratings.value
[docs]
def get_number_pixels(self, device):
c_device = ctypes.c_int(device)
c_number_pixels = ctypes.c_int()
code = self.dll.ShamrockGetNumberPixels(c_device, ctypes.byref(c_number_pixels))
self.error_check(code, 'ShamrockGetNumberPixels')
return c_number_pixels.value
[docs]
def get_pixel_width(self, device):
c_device = ctypes.c_int(device)
c_width = ctypes.c_float()
code = self.dll.ShamrockGetPixelWidth(c_device, ctypes.byref(c_width))
self.error_check(code, 'ShamrockGetPixelWidth')
return c_width.value
[docs]
def get_serial_number(self, device):
c_device = ctypes.c_int(device)
c_serial = ctypes.create_string_buffer(128)
code = self.dll.ShamrockGetSerialNumber(c_device, c_serial)
self.error_check(code, 'ShamrockGetSerialNumber')
return c_serial.value.decode('ascii')
[docs]
def get_slit(self, device):
c_device = ctypes.c_int(device)
c_width = ctypes.c_float()
code = self.dll.ShamrockGetSlit(c_device, ctypes.byref(c_width))
self.error_check(code, 'ShamrockGetSlit')
return c_width.value
[docs]
def get_wavelength(self, device):
c_device = ctypes.c_int(device)
c_wavelength = ctypes.c_float()
code = self.dll.ShamrockGetWavelength(c_device, ctypes.byref(c_wavelength))
self.error_check(code, 'ShamrockGetWavelength')
return c_wavelength.value
[docs]
def get_wavelength_limits(self, device, grating):
c_device = ctypes.c_int(device)
c_grating = ctypes.c_int(grating)
c_min = ctypes.c_float()
c_max = ctypes.c_float()
code = self.dll.ShamrockGetWavelengthLimits(c_device, c_grating, ctypes.byref(c_min), ctypes.byref(c_max))
self.error_check(code, 'ShamrockGetWavelengthLimits')
return c_min.value, c_max.value
[docs]
def initialize(self):
code = self.dll.ShamrockInitialize("")
self.error_check(code, 'ShamrockInitialize')
[docs]
def set_grating(self, device, grating):
c_device = ctypes.c_int(device)
c_grating = ctypes.c_int(grating)
code = self.dll.ShamrockSetGrating(c_device, c_grating)
self.error_check(code, 'ShamrockSetGrating')
[docs]
def set_number_pixels(self, device, number_pixels):
c_device = ctypes.c_int(device)
c_number_pixels = ctypes.c_int(number_pixels)
code = self.dll.ShamrockSetNumberPixels(c_device, c_number_pixels)
self.error_check(code, 'ShamrockSetNumberPixels')
[docs]
def set_pixel_width(self, device, width):
c_device = ctypes.c_int(device)
c_width = ctypes.c_float(width)
code = self.dll.ShamrockSetPixelWidth(c_device, c_width)
self.error_check(code, 'ShamrockSetPixelWidth')
[docs]
def set_slit(self, device, width):
c_device = ctypes.c_int(device)
c_width = ctypes.c_float(width)
code = self.dll.ShamrockSetSlit(c_device, c_width)
self.error_check(code, 'ShamrockSetSlit')
[docs]
def set_wavelength(self, device, wavelength):
c_device = ctypes.c_int(device)
c_wavelength = ctypes.c_float(wavelength)
code = self.dll.ShamrockSetWavelength(c_device, c_wavelength)
self.error_check(code, 'ShamrockSetWavelength')
[docs]
class Shamrock_SR750(Instrument):
"""
Instrument driver for the Shamrock SR750 spectrometer.
Args:
name: Instrument name.
dll_path: Path to the ShamrockCIF.dll file. If not set, a default path is used.
device_id: ID for the desired spectrometer.
ccd_number_pixels: Number of pixels on the connected CCD.
ccd_pixel_width: Pixel width on the connected CCD.
Attributes:
ShamrockCIF: DLL wrapper for ShamrockCIF.dll
device_id: Spectrometer device ID.
serial_number: Serial number of the spectrometer.
number_gratings: Number of gratings on the spectrometer.
"""
def __init__(self, name: str,
dll_path: Optional[str] = None, device_id: int = 0,
ccd_number_pixels: int = 1024, ccd_pixel_width: int = 26,
**kwargs):
super().__init__(name, **kwargs)
# link to dll
self.ShamrockCIF: ShamrockCIF = ShamrockCIF(dll_path=dll_path)
# store device number
self.device_id: int = device_id
# initialize Shamrock
self.ShamrockCIF.initialize()
# read info from Shamrock
self.serial_number: int = self.ShamrockCIF.get_serial_number(self.device_id)
self.number_gratings: int = self.ShamrockCIF.get_number_gratings(self.device_id)
# send CCD info to Shamrock
self.ShamrockCIF.set_number_pixels(self.device_id, ccd_number_pixels)
self.ShamrockCIF.set_pixel_width(self.device_id, ccd_pixel_width)
# add the instrument parameters
self.add_parameter('blaze',
get_cmd=self._get_blaze,
get_parser=int,
label='Blaze')
self.add_parameter('calibration',
get_cmd=self._get_calibration,
unit='nm',
label='Calibration')
self.add_parameter('grating',
get_cmd=self._get_grating,
set_cmd=self._set_grating,
get_parser=int,
vals=Ints(min_value=1,
max_value=self.number_gratings),
label='Grating')
self.add_parameter('groove_density',
get_cmd=self._get_groove_density,
get_parser=int,
unit='l/mm',
label='Groove density')
self.add_parameter('slit',
get_cmd=self._get_slit,
set_cmd=self._set_slit,
get_parser=int,
vals=Ints(min_value=10,
max_value=2500),
unit=u"\u03BC"+'m',
label='Slit')
min_wavelength, max_wavelength = self.ShamrockCIF.get_wavelength_limits(self.device_id, self._get_grating())
self.add_parameter('wavelength',
get_cmd=self._get_wavelength,
set_cmd=self._set_wavelength,
get_parser=float,
vals=Numbers(min_value=min_wavelength,
max_value=max_wavelength),
unit='nm',
label='Wavelength')
# print connect message
self.connect_message()
# get methods
def _get_blaze(self):
grating = self.ShamrockCIF.get_grating(self.device_id)
grating_info = self.ShamrockCIF.get_grating_info(self.device_id, grating)
return grating_info[1]
def _get_calibration(self):
return self.ShamrockCIF.get_calibration(self.device_id, 1024)
def _get_grating(self):
return self.ShamrockCIF.get_grating(self.device_id)
def _get_groove_density(self):
grating = self.ShamrockCIF.get_grating(self.device_id)
grating_info = self.ShamrockCIF.get_grating_info(self.device_id, grating)
return grating_info[0]
[docs]
def get_idn(self):
return {'vendor': 'Shamrock', 'serial': self.serial_number}
def _get_slit(self):
return self.ShamrockCIF.get_slit(self.device_id)
def _get_wavelength(self):
return self.ShamrockCIF.get_wavelength(self.device_id)
# set methods
def _set_grating(self, grating):
self.ShamrockCIF.set_grating(self.device_id, grating)
min_wavelength, max_wavelength = self.ShamrockCIF.get_wavelength_limits(self.device_id, grating)
self.wavelength.vals = Numbers(min_value=min_wavelength, max_value=max_wavelength)
def _set_slit(self, val):
self.ShamrockCIF.set_slit(self.device_id, val)
def _set_wavelength(self, wavelength):
self.ShamrockCIF.set_wavelength(self.device_id, wavelength)
# further methods
[docs]
def close(self):
self.ShamrockCIF.close()
super().close()