-
Notifications
You must be signed in to change notification settings - Fork 86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Thorlabs kinesis drivers #262
base: main
Are you sure you want to change the base?
Thorlabs kinesis drivers #262
Conversation
Thorlabs APT is marked as "legacy": (https://www.thorlabs.com/newgrouppage9.cfm?objectgroup_id=10285) To Do: - add actual functionality to the drivers. - add documentation
A lot of dll functions of different devices have the same signature but just a different prefix.
Where two different drivers are available, there is now a file for each named after the driver type (tlpm, visa, kinesis, apt) which contains the instrument class.
Doesn't work for a K10CR1 on my end (yet). The LoadSettings function returns an unspecified error and so do setting/getting the position.
Stacking classmethod and property is deprecated in CPython
@thangleiter since the other PR got merged, there are merge conflicts now - could you resovle those?
that's a good point. However, if you're not sure about it yet, it could be OK to merge different solutions now, provided that later a unification will arrive and the impact on the user-facing API of the driver is minimal (to keep compatibility where possible and reasonable) |
The problem is that both versions go about the common infrastructure differently, so I don't think it makes sense to resolve merge conflicts before we agreed on what that should look like. Unfortunatley, (almost) every Thorlabs Kinesis device has their own DLL, yet many of them share common functions (which each have names differing by a device-specific prefix...). Solving conflicts now would introduce a lot of functionally duplicitous code (everything in |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #262 +/- ##
==========================================
- Coverage 10.97% 10.56% -0.42%
==========================================
Files 132 140 +8
Lines 17540 18224 +684
==========================================
Hits 1925 1925
- Misses 15615 16299 +684 ☔ View full report in Codecov by Sentry. |
Thanks for approving this @einsmein. Before merging, it should still be discussed with @julienbarrier and @DCEM how to proceed with Kinesis drivers though, as there are now three different approaches; main, this PR, and #267. |
I might not get everything correct in the following comment - it has been a while since I have been working on this. When I started working on implementing the Thorlabs_BSCxxx_DotNet implementation I was only aware of @julienbarrier approach. I ran into issues with the C# implementation. For example I think i was unable to get the instrument model name (for me: BSC203). I am not sure how the C# API will behave, but for the .Net one I liked that you can change the motor parameters in the Thorlabs Kinesis GUI and than load it via the .Net API. Because the Inheritance structure mimics the Thorlabs one, if you add functionality to a base class, all instruments inheriting from it will have it right away. I don't know if this true (or even possible) for the C# one with all the prefixes. In my opinion it can be acceptable for the .Net and the C# to live beside each other and that is why I took some care to allow for it. I also wrote some documentation on how to add instruments/functionality. I do not claim/know that my approach is better than the others provided and am not sure how to proceed. I'm not dogmatic, and if it is decided that the .Net API way (or my implementation of it) is undesirable I won't feel offended. |
Same here, it's been a while.
I never had this problem with our devices since they have dedicated classes in the C API. I would expect it's either in
I implemented this by decorating base class methods with the prefix (see, #262 (comment)).
Can you connect to the same instrument concurrently? Otherwise what do you mean by loading the settings? Are they persistent?
See above.
Agreed. For me it would also be fine if the drivers live side-by-side (also with the old APT drivers). Lastly, an idea: if you have the time, we could each try to implement one of the instrument drivers the other wrote in our version of the driver infrastructure. It could help make one or another decision depending on how easy or difficult it is. That's where I see the most benefit of choosing one over the other; ease of implementing new drivers. |
If I remember correctly I tried that, but it returned something different (someting like "Benchtop Stepper Device").
No, I don't think it is possible to connect to an instrument concurrently.
If I saw it correctly you chose to have a folder for each instrument and then a filename by implementation type?
I do agree, that ease of driver implementation is a good metric. I would love to try that and I will try to find the time, but I unfortunately cannot make strong commitment to it at the moment. My schedule is a bit crazy right now. If you want to have a look there is a Practical Implementation Example in the: [readme.md](https://github.com/QCoDeS/Qcodes_contrib_drivers/blob/9b67d6db4efc212d7d956a4e648f932646b9f6df/qcodes_contrib_drivers/drivers/Thorlabs/private/DotNetAPI/README.md) How easy it is will depend a lot on what features are missing in the base classes of the .Net implementation. |
Since I would argue for retaining the APT versions of the drivers for backwards compatibility, the This is something where input from the maintainers would be welcome @astafan8. What's the policy on multiple driver interfaces? Do you have a stance on the naming scheme?
Agreed. I will try my best to put something together in the next two weeks but also cannot promise anything. On another note, does this PR supercede @julienbarrier's version currently in |
In qcodes we have indeed tried to follow this schema and not have the drivers nested too deeply. However, if there is a good reason to have multiple implementation I think the current schema is acceptable. |
@thangleiter @DCEM Trying to catch up on the status of this pr and #267 We would be happy to merge improvements if we can agree what is the most suitable to merge but I have lost track the various versions of the drivers. |
@jenshnielsen Thank you for taking the time to catch up on the status. Here’s my understanding of the current situation: We currently have two C# implementations and my .Net implementation. The key difference between the C# ones is that @thangleiter implementation uses decorators, which should simplify the process of implementing new instruments compared to @julienbarrier approach. Please note that I’m still somewhat new to QCoDeS and don’t consider myself a programming expert, so my preference for the .Net implementation might be partly due to my familiarity with it as the one I worked on. Advantages of the .Net Implementation:
Drawbacks:
Common Challenge:In both C# and .Net implementations, there’s the issue of handling the Decimal type, which is suggested by the Thorlabs API. Even when converting this to Python’s Decimal type, it often leads to practical issues. A potential solution could be an automatic type conversion feature that can be toggled off via a parameter if necessary. Key Questions:
These are the initial questions we need to address to move forward. Looking forward to your thoughts. Best regards |
Hi both, apologies for the radio silence on my end. I'm afraid that I do not have the time to migrate the drivers from my branch to @DCEM's at the moment. That means if we chose the .NET version over this PR, I'd stay on our fork so that I can continue my experiments (fine by me though). In general and in an ideal world, I think maintaining both the .NET and the C-API versions in parallel is a bad idea. However, if neither @DCEM nor me have time to migrate the already implemented drivers, not doing so means fewer supported devices. Hence, at least for now we could go the parallel route. If at any point other people implement additional devices, they can choose from the two APIs and the issue can go the evolutionary way... If we go down that road, the naming scheme I'd go for is |
@thangleiter I created a basic driver for the KDC101 for you to look at or test if you can find time to do so: @jenshnielsen I do not mind either way. I think taking into account which outcome is prefered when/if we manage to settle on one approach would be nice. @julienbarrier do you have any input here? |
@DCEM Didn't have a lot of time to debug, but this is as far as I get: rotator = K10CR1('rotator', 55001014)
2024-12-06 10:55:25,086 ¦ qcodes_contrib_drivers.drivers.Thorlabs.private.DotNetAPI.qcodes_thorlabs_integration ¦ ERROR ¦ qcodes_thorlabs_integration ¦ _import_dll_class ¦ 451 ¦ An unexpected error occurred while importing the DLL class.
Traceback (most recent call last):
File "~\qcodes_contrib_drivers\src\qcodes_contrib_drivers\drivers\Thorlabs\private\DotNetAPI\qcodes_thorlabs_integration.py", line 442, in _import_dll_class
module = importlib.import_module(namespace)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "~\miniforge3\envs\lab_main\Lib\importlib\__init__.py", line 90, in import_module
return _bootstrap._gcd_import(name[level:], package, level)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
File "<frozen importlib._bootstrap>", line 1310, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
File "<frozen importlib._bootstrap>", line 1310, in _find_and_load_unlocked
File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
File "<frozen importlib._bootstrap>", line 1324, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'ThorLabs'
2024-12-06 10:55:25,088 ¦ qcodes.instrument.instrument_base ¦ ERROR ¦ qcodes_thorlabs_integration ¦ __init__ ¦ 508 ¦ [rotator(K10CR1)] Failed to import DLLs for Thorlabs device with serial number 55001014
Traceback (most recent call last):
Cell In[10], line 1
rotator = K10CR1('rotator', 55001014)
File ~\Documents\Qcodes\src\qcodes\instrument\instrument_meta.py:36 in __call__
new_inst = super().__call__(*args, **kwargs)
File ~\Documents\qcodes_contrib_drivers\src\qcodes_contrib_drivers\drivers\Thorlabs\Thorlabs_K10CR1_DotNet.py:48 in __init__
super().__init__(
File ~\Documents\qcodes_contrib_drivers\src\qcodes_contrib_drivers\drivers\Thorlabs\private\DotNetAPI\IntegratedStepperMotorsCLI_CageRotator.py:24 in __init__
super().__init__(*args, **kwargs)
File ~\Documents\qcodes_contrib_drivers\src\qcodes_contrib_drivers\drivers\Thorlabs\private\DotNetAPI\IntegratedStepperMotorsCLI_IntegratedStepperMotor.py:13 in __init__
super().__init__(*args, **kwargs)
File ~\Documents\qcodes_contrib_drivers\src\qcodes_contrib_drivers\drivers\Thorlabs\private\DotNetAPI\GenericMotorCLI_AdvancedMotor.py:40 in __init__
super().__init__(*args, **kwargs)
File ~\Documents\qcodes_contrib_drivers\src\qcodes_contrib_drivers\drivers\Thorlabs\private\DotNetAPI\GenericMotorCLI.py:35 in __init__
super().__init__(*args, **kwargs)
File ~\Documents\qcodes_contrib_drivers\src\qcodes_contrib_drivers\drivers\Thorlabs\private\DotNetAPI\DeviceManagerCLI.py:124 in __init__
super().__init__(*args, **kwargs)
File ~\Documents\qcodes_contrib_drivers\src\qcodes_contrib_drivers\drivers\Thorlabs\private\DotNetAPI\qcodes_thorlabs_integration.py:506 in __init__
self._import_device_dll()
File ~\Documents\qcodes_contrib_drivers\src\qcodes_contrib_drivers\drivers\Thorlabs\Thorlabs_K10CR1_DotNet.py:66 in _import_device_dll
self._import_dll_class('ThorLabs.MotionControl.IntegratedStepperMotorsCLI', 'CageRotator')
File ~\Documents\qcodes_contrib_drivers\src\qcodes_contrib_drivers\drivers\Thorlabs\private\DotNetAPI\qcodes_thorlabs_integration.py:452 in _import_dll_class
raise e
File ~\Documents\qcodes_contrib_drivers\src\qcodes_contrib_drivers\drivers\Thorlabs\private\DotNetAPI\qcodes_thorlabs_integration.py:442 in _import_dll_class
module = importlib.import_module(namespace)
File ~\miniforge3\envs\lab_main\Lib\importlib\__init__.py:90 in import_module
return _bootstrap._gcd_import(name[level:], package, level)
File <frozen importlib._bootstrap>:1387 in _gcd_import
File <frozen importlib._bootstrap>:1360 in _find_and_load
File <frozen importlib._bootstrap>:1310 in _find_and_load_unlocked
File <frozen importlib._bootstrap>:488 in _call_with_frames_removed
File <frozen importlib._bootstrap>:1387 in _gcd_import
File <frozen importlib._bootstrap>:1360 in _find_and_load
File <frozen importlib._bootstrap>:1310 in _find_and_load_unlocked
File <frozen importlib._bootstrap>:488 in _call_with_frames_removed
File <frozen importlib._bootstrap>:1387 in _gcd_import
File <frozen importlib._bootstrap>:1360 in _find_and_load
File <frozen importlib._bootstrap>:1324 in _find_and_load_unlocked
ModuleNotFoundError: No module named 'ThorLabs' I applied the following patches to get this far:
|
Thank you for taking the time @thangleiter, i think the uppercase L in the Namespace might be the issue here, sorry for that: diff --git forkSrcPrefix/src/qcodes_contrib_drivers/drivers/Thorlabs/Thorlabs_K10CR1_DotNet.py forkDstPrefix/src/qcodes_contrib_drivers/drivers/Thorlabs/Thorlabs_K10CR1_DotNet.py
index 195e19ef755a962093f1f5da629c06c53382d1dd..ec9fbdc6987089eecbb51f251a260061f5f02db6 100644
--- forkSrcPrefix/src/qcodes_contrib_drivers/drivers/Thorlabs/Thorlabs_K10CR1_DotNet.py
+++ forkDstPrefix/src/qcodes_contrib_drivers/drivers/Thorlabs/Thorlabs_K10CR1_DotNet.py
@@ -62,8 +62,8 @@ class K10CR1(CageRotator, ThorlabsQcodesInstrument):
def _import_device_dll(self):
"""Import the device-specific DLLs and classes from the .NET API."""
self._add_dll('Thorlabs.MotionControl.GenericMotorCLI.dll')
- self._add_dll('ThorLabs.MotionControl.IntegratedStepperMotorsCLI.dll')
- self._import_dll_class('ThorLabs.MotionControl.IntegratedStepperMotorsCLI', 'CageRotator')
+ self._add_dll('Thorlabs.MotionControl.IntegratedStepperMotorsCLI.dll')
+ self._import_dll_class('Thorlabs.MotionControl.IntegratedStepperMotorsCLI', 'CageRotator')
def _get_api_interface_from_dll(self, serial_number: str):
"""Retrieve the API interface for the Thorlabs device using its serial number.""" Also note that I did not impliment any of the features that are specifict to the motor, i am willing the help with that if you want - just let me know. |
The serial still needs to be converted to strings -- maybe also in other places: diff --git a/src/qcodes_contrib_drivers/drivers/Thorlabs/Thorlabs_K10CR1_DotNet.py b/src/qcodes_contrib_drivers/drivers/Thorlabs/Thorlabs_K10CR1_DotNet.py
index ec9fbdc6..655887c0 100644
--- a/src/qcodes_contrib_drivers/drivers/Thorlabs/Thorlabs_K10CR1_DotNet.py
+++ b/src/qcodes_contrib_drivers/drivers/Thorlabs/Thorlabs_K10CR1_DotNet.py
@@ -67,7 +67,7 @@ class K10CR1(CageRotator, ThorlabsQcodesInstrument):
def _get_api_interface_from_dll(self, serial_number: str):
"""Retrieve the API interface for the Thorlabs device using its serial number."""
- return self._dll.CageRotator.CreateCageRotator(serial_number)
+ return self._dll.CageRotator.CreateCageRotator(str(serial_number))
def _post_connection(self):
"""
@@ -86,7 +86,7 @@ class K10CR1(CageRotator, ThorlabsQcodesInstrument):
Will run after polling has started and the device/channel is enabled.
"""
# Load any configuration settings needed by the device/channel
- serial = self._serial_number
+ serial = str(self._serial_number)
mode = self._startup_mode
self._api_interface.LoadMotorConfiguration(serial, mode)
self._configuration = self._api_interface.LoadMotorConfiguration(serial)
diff --git a/src/qcodes_contrib_drivers/drivers/Thorlabs/private/DotNetAPI/DeviceManagerCLI.py b/src/qcodes_contrib_drivers/drivers/Thorlabs/private/DotNetAPI/DeviceManagerCLI.py
index 7746435e..90d4935b 100644
--- a/src/qcodes_contrib_drivers/drivers/Thorlabs/private/DotNetAPI/DeviceManagerCLI.py
+++ b/src/qcodes_contrib_drivers/drivers/Thorlabs/private/DotNetAPI/DeviceManagerCLI.py
@@ -32,7 +32,7 @@ class IGenericCoreDeviceCLI():
def connect(self):
"""Connects to the device using its serial number."""
try:
- self._api_interface.Connect(self._serial_number)
+ self._api_interface.Connect(str(self._serial_number))
except Exception as e:
self.log.exception(f"Failed to connect to instrument with serial number {self._serial_number}")
raise This patch makes initialization work. However, implementing functionality as parameters got me stuck. An example: from what I understand from the readme, there is no way to implement the |
@thangleiter thank you for keeping up your efforts in testing this code. I'm a bit unsure on how to handle the string serial number issue. To be honest i didn't even consider not using a string, that is why the typehint in K10CR1 is also str. Maybe because the pythonnet example from thorlabs does use the string type right away (K10CR1_pythonnet.py) I would prefer to have the thorlabs implementation mimic the .Net API - and that will expect a string. I think it could be acceptable to have an automatic converstion as you suggested but that would be a convinice feature. I tried to stay with the original type, because automatically changing a type might cause issues. That is also why i converted the .Net decimal into a python decimal and not a float. This unfortuantely will cause issues when you want to do math with a value read from an insturement. A solution for this (the decimal issue) might be to conver to a float by default, but still get the decimal if you need it. I don't think there are huge implications and I think I don't mind any of the solutions. Regarding the feature implementation - I guess I didn't do a manual or even give a hint - sorry about that. The idea is once again to mimic the thorlabs inheritance structure. Most of the movement stage related stuff will be in: GenericMotorCLI_AdvancedMotor.py and that should be accessilbe to K10CR1 right away (via: GenericAdvancedMotorCLI -> IntegratedStepperMotor -> CageRotator -> K10CR1 ) Imho this should result in K10CR1 already having a move_to method. Can you try a dir(K10CR1_instance) to see if is there. There should be a lot of parameters/methodes already implemented. (use help() to access docstrinigs at runtime) if your device need a different behaviour the _set_position can be overwritten but at least per .Net API it should work right away. if a feature is missing, it should be implemented in the respective class to allow the mimic to work. some functions like: GetMotorConfiguration are explicit marked as overwritten in the .Net help. but when you look at the definition, i don't think anything needs to be done on the qcodes implementation side. All of this makes this implementation to be less accessible in the beginning. Also code redundency is minimized and if functionallity is missing in GenericAdvancedMotorCLI for example basically all instruments will get it as soon as it is implemented. |
There is no way around supporting integer serial numbers because an instrument should also be loadable from a station yaml: instruments:
rotator:
type: qcodes_contrib_drivers.drivers.Thorlabs.Thorlabs_K10CR1_DotNet.K10CR1
init:
serial: 55001014 Here, the serial will be parsed as an integer. In any case, I don't see why casting to
Oh, I see, that was not clear to me. Knowing this, the following patches are required to get the diff --git a/src/qcodes_contrib_drivers/drivers/Thorlabs/private/DotNetAPI/GenericMotorCLI_AdvancedMotor.py b/src/qcodes_contrib_drivers/drivers/Thorlabs/private/DotNetAPI/GenericMotorCLI_AdvancedMotor.py
index 999a6403..11f968e8 100644
--- a/src/qcodes_contrib_drivers/drivers/Thorlabs/private/DotNetAPI/GenericMotorCLI_AdvancedMotor.py
+++ b/src/qcodes_contrib_drivers/drivers/Thorlabs/private/DotNetAPI/GenericMotorCLI_AdvancedMotor.py
@@ -1,4 +1,5 @@
import logging
+from decimal import Decimal as PyDecimal
from typing import Optional
from qcodes.instrument.channel import InstrumentBase
@@ -90,6 +91,7 @@ class GenericAdvancedMotorCLI(GenericMotorCLI):
'position',
get_cmd=lambda: self._get_thorlabs_decimal('Position'),
set_cmd=self._set_position,
+ set_parser=PyDecimal,
vals=self._get_position_vals(),
unit=self.advanced_limits.length_unit(),
docstring='Position of the stage. '
@@ -178,9 +180,9 @@ class GenericAdvancedMotorCLI(GenericMotorCLI):
if self.wait_for_movement():
# Calculate required time for the movement
time_needed = self._compute_move_time_ms(
- max_distance,
- self.advanced_limits.acceleration_max(),
- self.home_parameters.velocity()
+ float(max_distance),
+ float(self.advanced_limits.acceleration_max()),
+ float(self.home_parameters.velocity())
)
timeout_ms = 2000 + time_needed
else:
@@ -237,15 +239,15 @@ class GenericAdvancedMotorCLI(GenericMotorCLI):
if self.wait_for_movement():
# Calculate time required for movement based on the shortest distance
time_needed = self._compute_move_time_ms(
- shortest_distance,
- self.advanced_limits.acceleration_max(),
- self.home_parameters.velocity()
+ float(shortest_distance),
+ float(self.advanced_limits.acceleration_max()),
+ float(self.home_parameters.velocity())
)
timeout_ms = 1000 + time_needed
else:
timeout_ms = 0
- self.move_to(position, timeout_ms)
+ self.move_to(float(position), timeout_ms)
def move_to(self, position: DotNetDecimal, timeout_ms=0) -> None:
"""
@@ -570,4 +572,3 @@ class RotationalStageValidator(PyDecimalNumbers):
def __repr__(self) -> str:
return f"<RotationalStageValidator wrapping at {self.wrap_at} with range ({self._min_value}, {self._max_value})>"
-
Two more issues came up during testing:
In [14]: rotator.close()
In [15]: rotator = K10CR1('rotator', 55001014)
2024-12-16 10:12:16,122 ¦ qcodes.instrument.instrument_base ¦ ERROR ¦ DeviceManagerCLI ¦ connect ¦ 37 ¦ [rotator(K10CR1)] Failed to connect to instrument with serial number 55001014
Traceback (most recent call last):
File "~\Documents\qcodes_contrib_drivers\src\qcodes_contrib_drivers\drivers\Thorlabs\private\DotNetAPI\DeviceManagerCLI.py", line 35, in connect
self._api_interface.Connect(str(self._serial_number))
Thorlabs.MotionControl.DeviceManagerCLI.DeviceNotReadyException: Device is not connected
at Thorlabs.MotionControl.DeviceManagerCLI.ThorlabsGenericCoreDeviceCLI.VerifyDeviceConnected(Int32 functionDepth)
at Thorlabs.MotionControl.GenericMotorCLI.GenericMotorCLI.Connect(String serialNo)
2024-12-16 10:12:16,124 ¦ qcodes.instrument.instrument_base ¦ ERROR ¦ qcodes_thorlabs_integration ¦ __init__ ¦ 523 ¦ [rotator(K10CR1)] Failed to connect to Thorlabs device with serial number 55001014
---------------------------------------------------------------------------
DeviceNotReadyException Traceback (most recent call last)
Cell In[15], line 1
----> 1 rotator = K10CR1('rotator', 55001014)
File ~\Documents\Qcodes\src\qcodes\instrument\instrument_meta.py:36, in InstrumentMeta.__call__(cls, *args, **kwargs)
31 def __call__(cls, *args: Any, **kwargs: Any) -> Any:
32 """
33 Overloads `type.__call__` to add code that runs only if __init__ completes
34 successfully.
35 """
---> 36 new_inst = super().__call__(*args, **kwargs)
37 is_abstract = new_inst._is_abstract()
38 if is_abstract:
File ~\Documents\qcodes_contrib_drivers\src\qcodes_contrib_drivers\drivers\Thorlabs\Thorlabs_K10CR1_DotNet.py:48, in K10CR1.__init__(self, name, serial_number, startup_mode_value, simulation, polling_rate_ms, dll_directory, **kwargs)
37 def __init__(
38 self,
39 name: str,
(...)
45 **kwargs
46 ):
---> 48 super().__init__(
49 name, # Instrument (Qcodes)
50 serial_number=serial_number, # ThorlabsQcodesInstrument
51 startup_mode_value=startup_mode_value,
52 simulation=simulation, # ThorlabsQcodesInstrument
53 polling_rate_ms=polling_rate_ms, # GenericMotoCLI
54 dll_directory=dll_directory, # ThorlabsDLLMixin
55 **kwargs)
57 if '(Simulated)' not in self.model():
58 self.snapshot(True)
File ~\Documents\qcodes_contrib_drivers\src\qcodes_contrib_drivers\drivers\Thorlabs\private\DotNetAPI\IntegratedStepperMotorsCLI_CageRotator.py:24, in CageRotator.__init__(self, *args, **kwargs)
23 def __init__(self, *args, **kwargs):
---> 24 super().__init__(*args, **kwargs)
File ~\Documents\qcodes_contrib_drivers\src\qcodes_contrib_drivers\drivers\Thorlabs\private\DotNetAPI\IntegratedStepperMotorsCLI_IntegratedStepperMotor.py:13, in IntegratedStepperMotor.__init__(self, *args, **kwargs)
12 def __init__(self, *args, **kwargs):
---> 13 super().__init__(*args, **kwargs)
File ~\Documents\qcodes_contrib_drivers\src\qcodes_contrib_drivers\drivers\Thorlabs\private\DotNetAPI\GenericMotorCLI_AdvancedMotor.py:41, in GenericAdvancedMotorCLI.__init__(self, *args, **kwargs)
40 def __init__(self, *args, **kwargs):
---> 41 super().__init__(*args, **kwargs)
43 advanced_limits = AdvancedMotorLimits(self, 'advanced_limits')
44 self.add_submodule('advanced_limits', advanced_limits)
File ~\Documents\qcodes_contrib_drivers\src\qcodes_contrib_drivers\drivers\Thorlabs\private\DotNetAPI\GenericMotorCLI.py:35, in GenericMotorCLI.__init__(self, *args, **kwargs)
31 if not isinstance(self, ThorlabsMixin):
32 raise TypeError("GenericMotorCLI should only be mixed with "
33 "subclasses of ThorlabsMixin")
---> 35 super().__init__(*args, **kwargs)
37 motor_position_limits = LimitsData(self, 'motor_position_limits')
38 self.add_submodule('motor_position_limits', motor_position_limits)
File ~\Documents\qcodes_contrib_drivers\src\qcodes_contrib_drivers\drivers\Thorlabs\private\DotNetAPI\DeviceManagerCLI.py:124, in IGenericDeviceCLI.__init__(self, polling_rate_ms, api_interface, startup_mode_value, *args, **kwargs)
121 self._set_api_interface(kwargs.pop('api_interface', api_interface))
122 self._set_startup_mode_value(kwargs.pop('startup_mode_value', startup_mode_value))
--> 124 super().__init__(*args, **kwargs)
126 if hasattr(self, '_edit_startup_mode'):
127 self._edit_startup_mode()
File ~\Documents\qcodes_contrib_drivers\src\qcodes_contrib_drivers\drivers\Thorlabs\private\DotNetAPI\qcodes_thorlabs_integration.py:521, in ThorlabsQcodesInstrument.__init__(self, simulation, *args, **kwargs)
517 self._dll.DeviceManagerCLI.BuildDeviceList()
519 self._set_api_interface(self._get_api_interface_from_dll(str(self._serial_number)))
--> 521 self.connect()
522 except Exception as e:
523 self.log.error(f"Failed to connect to Thorlabs device with serial number {self._serial_number}")
File ~\Documents\qcodes_contrib_drivers\src\qcodes_contrib_drivers\drivers\Thorlabs\private\DotNetAPI\DeviceManagerCLI.py:35, in IGenericCoreDeviceCLI.connect(self)
33 """Connects to the device using its serial number."""
34 try:
---> 35 self._api_interface.Connect(str(self._serial_number))
36 except Exception as e:
37 self.log.exception(f"Failed to connect to instrument with serial number {self._serial_number}")
DeviceNotReadyException: Device is not connected
at Thorlabs.MotionControl.DeviceManagerCLI.ThorlabsGenericCoreDeviceCLI.VerifyDeviceConnected(Int32 functionDepth)
at Thorlabs.MotionControl.GenericMotorCLI.GenericMotorCLI.Connect(String serialNo) |
We are using it in a station.yml: optical_stage:
type: qcodes_contrib_drivers.drivers.Thorlabs.Thorlabs_BSCxxx_DotNet.ThorlabsBSCxxx
init:
serial_number: '70233194' but I think it is acceptable to cast it as a string. diff --git forkSrcPrefix/src/qcodes_contrib_drivers/drivers/Thorlabs/private/DotNetAPI/DeviceManagerCLI.py forkDstPrefix/src/qcodes_contrib_drivers/drivers/Thorlabs/private/DotNetAPI/DeviceManagerCLI.py
index 7746435ef41812477a888e61a965dac73146bb5d..1f44e4d06e306165971a442e41c1cb5c34ef63b4 100644
--- forkSrcPrefix/src/qcodes_contrib_drivers/drivers/Thorlabs/private/DotNetAPI/DeviceManagerCLI.py
+++ forkDstPrefix/src/qcodes_contrib_drivers/drivers/Thorlabs/private/DotNetAPI/DeviceManagerCLI.py
@@ -19,7 +19,7 @@ class IGenericCoreDeviceCLI():
def __init__(self, *args, serial_number: str, **kwargs):
super().__init__(*args, **kwargs)
- self._serial_number = kwargs.pop('serial_number', serial_number)
+ self._serial_number = str(kwargs.pop('serial_number', serial_number))
self.add_parameter(
'busy', The decimal issue is a thing. I am not sure if/when one might run into issues here. But I agree, using decimal is really anoying. I do remember reading in the thorlabs documentation that there are issues that can occur. Maybe it is a good idea to have an instrument attribute like auto_decimal_conversion that could be True per default an then will output float, and accept float as input for all the instances where decimal is used. I'm happy to look into that at some point, but I would like to take the "bigger picture" question regarding the thorlabs implementation into account. Regarding the destructor - i think adding shut_down to it should be enough, but cannot test it right now. diff --git a/src/qcodes_contrib_drivers/drivers/Thorlabs/private/DotNetAPI/qcodes_thorlabs_integration.py b/src/qcodes_contrib_drivers/drivers/Thorlabs/private/DotNetAPI/qcodes_thorlabs_integration.py
--- a/src/qcodes_contrib_drivers/drivers/Thorlabs/private/DotNetAPI/qcodes_thorlabs_integration.py
+++ b/src/qcodes_contrib_drivers/drivers/Thorlabs/private/DotNetAPI/qcodes_thorlabs_integration.py
@@ -594,4 +594,8 @@
return dict(zip(("vendor", "model", "serial", "firmware"), idparts))
+ def close(self) -> None:
+ self.shut_down()
+ super().close()
+
numbertypes = Union[float, int, np.floating, np.integer, PyDecimal] Edit: I managed to get a simulation running with the code. (still with issues) diff --git forkSrcPrefix/src/qcodes_contrib_drivers/drivers/Thorlabs/private/DotNetAPI/qcodes_thorlabs_integration.py forkDstPrefix/src/qcodes_contrib_drivers/drivers/Thorlabs/private/DotNetAPI/qcodes_thorlabs_integration.py
index fe2b2ea3e25605e0405e645e5ed6c7eb58ab5130..2639be4099fefff190f2cfc20b6f89efe3f3df0f 100644
--- forkSrcPrefix/src/qcodes_contrib_drivers/drivers/Thorlabs/private/DotNetAPI/qcodes_thorlabs_integration.py
+++ forkDstPrefix/src/qcodes_contrib_drivers/drivers/Thorlabs/private/DotNetAPI/qcodes_thorlabs_integration.py
@@ -1,4 +1,3 @@
-import atexit
import importlib
import logging
import math
@@ -491,8 +490,6 @@ class ThorlabsQcodesInstrument(IGenericCoreDeviceCLI, ThorlabsDLLMixin, Thorlabs
self._simulation = kwargs.pop('simulation', simulation)
super().__init__(*args, **kwargs)
- atexit.register(self._exit_generic_device)
-
# Import common DLLs.
try:
self._add_dll('Thorlabs.MotionControl.DeviceManagerCLI.dll')
@@ -538,16 +535,6 @@ class ThorlabsQcodesInstrument(IGenericCoreDeviceCLI, ThorlabsDLLMixin, Thorlabs
self.log.error(f"Failed to initialize Thorlabs {self._model()} with serial number {self._serial_number}")
raise
- def _exit_generic_device(self) -> None:
- """
- Safely terminate operations at exit by:
- * disconnect the device
- * stop the simulation if needed
- """
- self.disconnect()
- if self._simulation:
- self._dll.SimulationManager.Instance.UninitializeSimulations()
-
def _import_device_dll(self):
"""Import the device-specific DLLs and classes from the .NET API."""
raise NotImplementedError("This method should be implemented by subclasses.")
@@ -599,7 +586,12 @@ class ThorlabsQcodesInstrument(IGenericCoreDeviceCLI, ThorlabsDLLMixin, Thorlabs
return dict(zip(("vendor", "model", "serial", "firmware"), idparts))
def close(self) -> None:
+ self.disconnect()
self.shut_down()
+
+ if self._simulation:
+ self._dll.SimulationManager.Instance.UninitializeSimulations()
+
super().close() |
@thangleiter Thanks for raising the JSON serialization issue. This is just one challenge when dealing with decimals. The Thorlabs .NET API uses decimals for real-world units. Converting these to floats can cause precision loss. Returning them as strings before serialization would preserve the exact numeric values. Right now, having decimal parameters is not ideal. The helper functions that convert from DotNetDecimal to PyDecimal should convert to either a string or a float. There should be a straightforward way to choose the desired conversion at both the instrument and parameter levels. I suggest making floats the default. This should be a keyword argument so that users can easily see it is an option. Handling validators might become an issue when done like this. Maybe a validator that accepts strings and uses decimal internally could be an option. I’d like input from @jenshnielsen on this decimal issue. I’m willing to implement these changes, but I’d appreciate a decision on whether this code will be included so that the effort is well spent. |
I just realize I lied, the >>> json.dumps(decimal.Decimal(), cls=qcodes.utils.NumpyJSONEncoder)
'"0"' |
Ok., since json serialisation works, i thought about how to proceed. I introduced : To test and understand how to add more Instruments i wanted to try adding the ones with existing implementation.
Now the following devices are supported: I think now all existing kinesis instruments are supported. |
This is another backlog driver project that started before the recent (#244) Thorlabs merge. Unfortunately, this and #244 diverge in the design of the underlying glue code and also have finite overlap in the devices that are implemented using the Kinesis API.
Since Thorlabs deprecated the APT interface (which still work though) I opted for supporting both APT and Kinesis drivers in parallel.
Additionally, there is a new driver for the PM100 powermeter, which, in the newest version of the software, also uses a dll instead of VISA.
It might be worth discussing how to proceed (ping @julienbarrier) to provide a common base architecture for Thorlabs drivers in qcodes.