AlexShkarin / pyLabLib

Python package for device control and experiment automation
http://pylablib.readthedocs.io
GNU General Public License v3.0
130 stars 30 forks source link

Clarification on MPC-XXX device operation #72

Open dymmR opened 8 months ago

dymmR commented 8 months ago

Hi, I am sorry to bother you, but I seem to be encountering an issue when using polarization controllers in the pylablib, namely I cannot get the system to jog() or move_to(). I am fairly sure it is user error, but any insight on how to correctly call the methods would be greatly appreciated.

I am currently running pylablib on a linux (Ubuntu) backend with an MPC-320 device, and forcing detection of _model="MPC" to get the appropriate device variables. I am able invoking the methods just by calling

move_to(position, channel =(int between 0,1,2))

and

jog ("+", channel =(int between 0,1,2))

However with either I cannot get any motion. The motors were double checked on the kinesis gui and appear to be in good order. A little detail can be found below.

from pylablib.devices.Thorlabs import kinesis

con="/dev/ttyUSB0"

pol_test=kinesis.KinesisMotor(con,mpc_ovverride=True)

print(pol_test._get_polctl_parameters()) TPolCtlParams(velocity=10, home_position=649, jog1=25, jog2=25, jog3=25)

print(pol_test.get_all_channels()) [1]

print(pol_test.get_number_of_channels()) 3

print(pol_test.get_position(channel=0)) -989

print(pol_test.get_position(channel=1)) 100 print(pol_test.get_position(channel=2)) 20

print(pol_test.get_position(channel=3)) 648

print(pol_test.get_position(channel=4)) 648

print(pol_test.get_position(channel=5)) 648

print(hex(pol_test._status_comm)) 0x490

print(pol_test.get_homing_parameters()) raise self.Error("read returned less than expected: {} instead of {}".format(len(result),size)) pylablib.devices.Thorlabs.base.ThorlabsBackendError: backend exception: 'read returned less than expected: 0 instead of 1' ('read returned less than expected: 0 instead of 1')

If you have a moment, any thoughts on how to properly call the methods would be greatly appreciate.

Thanks, Liam

AlexShkarin commented 8 months ago

Dear Liam,

It's hard to tell what exactly is going on. Could you give me some more information?

Sincerely,

Alexey.

dymmR commented 8 months ago

Hi Alexey,

Thanks for the quick response.

  1. I am using the most recent pylablib version, taken from github not pip
  2. The attached output is as follows
  3. from pylablib.devices.Thorlabs import kinesis import time con="/dev/ttyUSB0" pol_test=kinesis.KinesisMotor(con,mpc_ovverride=True)

print(pol_test._model) MPC

print(hex(pol_test._get_status_n())) 0x400

print(hex(pol_test._status_comm)) 0x490

print(pol_test._move_by_mode) move_to

print(pol_test.get_full_info()) {'polctl_parameter': TPolCtlParams(velocity=10, home_position=649, jog1=25, jog2=25, jog3=25), 'position': 200, 'status': ['homed'], 'cls': 'KinesisMotor', 'conn': {'port': '/dev/ttyUSB0', 'baudrate': 115200, 'bytesize': 8, 'parity': 'N', 'stopbits': 1, 'xonxoff': 0, 'rtscts': True, 'dsrdtr': 0}, 'device_info': TDeviceInfo(serial_no=38408784, model_no='MPC320', fw_ver='1.1.1', hw_type=16, hw_ver=1, mod_state=0, nchannels=3, notes='3 Paddle Motorised Polarizer Controller'), 'channel': [1], 'scale_units': 'step', 'scale': (1, 1, 1), 'stage': 'step'}

  1. The issue arises as I think how I call the function. I have so far called the move function in the form

pol_test.move_to(position=Y,channel=X)

Where after calling I do not get any physical motor movement, however after calling get_position(channel=X) I observe a change in the motor position for all channels that are not X=0.

print(pol_test._get_polctl_parameters()) TPolCtlParams(velocity=10, home_position=649, jog1=25, jog2=25, jog3=25)

pol_test.move_to(position=0,channel=0) pol_test.move_to(position=0,channel=1) pol_test.move_to(position=0,channel=2)

print(pol_test.get_position(channel=0)) -989 print(pol_test.get_position(channel=1)) 0 print(pol_test.get_position(channel=2)) 0

pol_test.move_to(position=200,channel=0) pol_test.move_to(position=200,channel=1) pol_test.move_to(position=200,channel=2)

print(pol_test.get_position(channel=0)) -988 print(pol_test.get_position(channel=1)) 200 print(pol_test.get_position(channel=2)) 200

  1. For the overrride I have just added a flag in the KinesisMotor class and the KinesisDevice Class. Ie for the motor

class KinesisMotor(KinesisDevice): def init(self, conn, scale="step", default_channel=1, is_rack_system=False,mpc_ovverride=False): super().init(conn,default_channel=default_channel,is_rack_system=is_rack_system,mpc_ovverride=mpc_ovverride)

class KinesisDevice(IMultiaxisStage,BasicKinesisDevice): def init(self, conn, timeout=3., default_channel=1, is_rack_system=False,mpc_ovverride=False): super().init(conn,timeout=timeout,is_rack_system=is_rack_system,default_axis=default_channel) self._remove_device_variable("axes") self._add_info_variable("channel",self.get_all_channels) with self._close_on_error(): if not mpc_ovverride: self._model=self.get_device_info().model_no self._setup_comm_parameters() else: self._model="MPC" self._setup_comm_parameters()

Thank you for the help. Liam

AlexShkarin commented 8 months ago

There's a couple of possibilities:

A couple more comments:

dymmR commented 8 months ago

Hi Alexey,

I appreciate the help. so for posterity on points 2-4:

  1. Yeah they were definitely not moving. I was going up to 900 and still seeing not too much
  2. No worries, I will revert to the original then
  3. Yeah you were absolutely right, it is limited to 1-4

I do have good news to report, the addition of _enable_channel and a wait_for_stop successfully works for 2/3 paddles. I have channels 1,2 working, 3 appears to not be receiving the commands. but I will double check if that is a hardware problem.

If you have any further insight absolutely let me know. I am able to enable and disable the channel, and it does not appear to give any busy status commands.

Once again thank you for the help, sorry for the headache.

SilasEul commented 4 months ago

Hey @dymmR, I had similiar issues as you had. T The third channel is adressed as channel "4". This should solve one of your issues. Eventhough the _move_to() did work quite reliably, I cannot say so about the get_poition function(). Sometimes it needs to be called multiple times and still does not return the correct answer, sometimes it does is straight away.

On another note. I keep having the issue, that at some point the Device stops responding entirely, even in Kinesis. I think it is related to a python exception occuring and not closing the device correctly, however i could not find a close_device/ disconnect routine to execute in case something goes wrong. Any thoughts on that? If i try to start my programm again and try to connect to the Device I get the error File "C:\Local\environments\winpy_3_11_50\python-3.11.5.amd64\Lib\site-packages\qkd_lab_device_apis\thorlabs\_pol_paddles.py", line 36, in __init__ self._device = th.KinesisDevice(device_id) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Local\environments\winpy_3_11_50\python-3.11.5.amd64\Lib\site-packages\pylablib\devices\Thorlabs\kinesis.py", line 281, in __init__ self._model=self.get_device_info().model_no ^^^^^^^^^^^^^^^^^^^^^^ File "C:\Local\environments\winpy_3_11_50\python-3.11.5.amd64\Lib\site-packages\pylablib\devices\Thorlabs\kinesis.py", line 225, in get_device_info data=self.query(0x0005,dest=dest).data ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Local\environments\winpy_3_11_50\python-3.11.5.amd64\Lib\site-packages\pylablib\devices\Thorlabs\kinesis.py", line 185, in query return self.recv_comm(expected_id=replyID) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Local\environments\winpy_3_11_50\python-3.11.5.amd64\Lib\site-packages\pylablib\devices\Thorlabs\kinesis.py", line 136, in recv_comm b=self.instr.read(1) ^^^^^^^^^^^^^^^^^^ File "C:\Local\environments\winpy_3_11_50\python-3.11.5.amd64\Lib\site-packages\pylablib\core\devio\comm_backend.py", line 45, in wrapped return func(self,*args,**kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Local\environments\winpy_3_11_50\python-3.11.5.amd64\Lib\site-packages\pylablib\core\devio\comm_backend.py", line 34, in wrapped return func(self,*args,**kwargs) ^^^^^^^^^^^^^^^^^^^^^^^^^ File "C:\Local\environments\winpy_3_11_50\python-3.11.5.amd64\Lib\site-packages\pylablib\core\devio\comm_backend.py", line 925, in read raise self.Error("read returned less data than expected") pylablib.devices.Thorlabs.base.ThorlabsBackendError: backend exception: 'read returned less data than expected' ('read returned less data than expected') Any thoughts on that? Debugging shows me that result = b''. The only solution is to replug the USB connection :/ PC restart does not seem to do the trick unfortunately. BTW the Kinesis Error is 2024-05-27 09:36:07.197 Error 38308384 Device not responding

dymmR commented 4 months ago

Hi Silas, Appreciate the catch, I did not pick up the channel 4, and was resigned to using two polarizations. I will give it a try

For the error problem, it appears that at least for the MPC it really cannot stand having any error happen on the pc or the controller, it just hangs. I believe there is a close method hidden in the core/devio/interface methods, as it's invoked in the local _close_on_error function during the device init. it might be worth doing a quick test with a try-except code and adding a device.close() on the except case, if thats what you are looking for.

dymmR commented 3 months ago

So if there is any insight, I still have the problem of one paddle not moving. In Kinesis it is not a problem, however in python it is limited to only channels 1 and 2. For the third paddle, when addressing channel 4, the time between sending a signal and waiting for stop is equivalent to what we see on channels 1 and 2, but the paddle just does not move. It seems like a ridiculously simple problem but we are getting absolutely cooked by it

AlexShkarin commented 2 months ago

@dymmR I've added some small changes in the newer version of the library (from about 2 months ago). Could you try to update it and see if it works any better?

AlexShkarin commented 2 months ago

@SilasEul Regarding the device not repsonding: there's a couple of possiblities. One, as you said, is that the device is not closed in Python properly, and it is still occupying the device connection (these devices can only be controlled from one software process at a time). However, it does not seem to be the case in your situation, since you say that even restarting the PC does not help. I've seens sometimes Thorlabs devices freezing to the point where you could only fix it by power cycling, but that usually involved pretty specific malformed messages, so I'm surprized that it happens randomly in the course of normal communication. Is there some particular command that prompts it? Does the device work fine from Kinesis alone?

dymmR commented 2 months ago

Hi Alexey,

Apologies, we found the solution a couple days ago but I did not update fast enough. So we did a fresh pull about a week and a half back and still had the same issue. We found a solution, taking inspiration from another issue (https://github.com/AlexShkarin/pyLabLib/issues/27) where we simply removed the decorator on top of the polmotor enable functions (@interface.use_parameters(channel="channel_id")) and instead we simply pass the full bit representation of the integers (0x01, 0x02, 0x04) instead of (1,2,4) when we call the enable and move functions . This works flawlessly. The specific change is noted below.

Line 796, Kinesis.py

@interface.use_parameters(channel="channel_id")

def _enable_channel(self, enabled=True, channel=None):  
    """Enable or disable the given channel"""
    self.send_comm(0x0210,self._make_channel(channel),0x01 if enabled else 0x02,dest=("channel",channel))
    return self._wip._is_channel_enabled(channel)
dymmR commented 2 months ago

I don''t really have a good reason why this is the solution to our problems, as I would think the EnumParameterClass should not really pose a big obstacle. May just be an added feature of trying to run it on a linux system

AlexShkarin commented 2 months ago

Hmm, that's strange. I would expect device._enable_channel(0x04) in your version of the code to do exactly the same as device._enable_channel(3) in the unmodified (i.e., currently released) version. Can you confirm, that these indeed give different results?

dymmR commented 2 months ago

Apologies for the delay. I will give it a check in the next day and let you know