Skip to content

Clarification on MPC-XXX device operation #72

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

Open
dymmR opened this issue Jan 19, 2024 · 13 comments
Open

Clarification on MPC-XXX device operation #72

dymmR opened this issue Jan 19, 2024 · 13 comments

Comments

@dymmR
Copy link

dymmR commented Jan 19, 2024

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
Copy link
Owner

Dear Liam,

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

  • Are you using the most recent pylablib version (1.4.2)?
  • Could you post here the printout of the following code:
from pylablib.devices.Thorlabs import kinesis
con="/dev/ttyUSB0"
pol_test=kinesis.KinesisMotor(con,mpc_ovverride=True)
print(pol_test._model)
print(hex(pol_test._get_status_n()))
print(hex(pol_test._status_comm))
print(pol_test._move_by_mode)
print(pol_test.get_full_info())
  • Could you describe exactly the way in which jog and move_to do not work? Do the methods raise an exception? Does the stage not move? Does the position returned by get_position change for any of the channels? Does the status change right after the move command is called? What's the result of the following code?
from pylablib.devices.Thorlabs import kinesis
con="/dev/ttyUSB0"
pol_test=kinesis.KinesisMotor(con,mpc_ovverride=True)
print(hex(pol_test._get_status_n()))
pol_test.jog("+")
print(hex(pol_test._get_status_n()))
  • As you've mentioned, you've modified the code to override the _model attribute. Could you show here the modified parts?

Sincerely,

Alexey.

@dymmR
Copy link
Author

dymmR commented Jan 22, 2024

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

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
Copy link
Owner

There's a couple of possibilities:

  • It might be that you need to enable the channel first. Try calling pol_test._enable_channel(channel=1) before calling pol_test.move_to(200,channel=1) (and the same for other channels, of course). It could be that the outputs are disabled, which means that the controller still counts the steps it was supposed to send, but the voltage pulses are never actually generated.
  • Are you sure that the paddles are indeed not moving? Keep in mind that the position by default is given in steps, and MPC has 1370 steps for 170 degrees, meaning that 200 steps is only about 25 degrees. And can you also try going to other positions (both positive and negative) to make sure that it's not running into some physical limits?

A couple more comments:

  • I think, mpc_ovverride is unnecessary in this case. The code seems to be correctly identifying the stage as MPC320, so everything should work fine.
  • As far as I understand, the channels might be numbered from 1, not from 0. Hence, you need to supply channel of 1, 2, or 3 instead of 0, 1, or 2.

@dymmR
Copy link
Author

dymmR commented Jan 24, 2024

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
Copy link

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
Copy link
Author

dymmR commented Jun 4, 2024

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
Copy link
Author

dymmR commented Jun 25, 2024

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
Copy link
Owner

@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
Copy link
Owner

@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
Copy link
Author

dymmR commented Jul 7, 2024

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 (#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
Copy link
Author

dymmR commented Jul 7, 2024

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
Copy link
Owner

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
Copy link
Author

dymmR commented Jul 24, 2024

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants