Skip to content

Commit faf44a9

Browse files
committed
Handle Thorlabs serial weirdness better.
1 parent 975fadb commit faf44a9

File tree

1 file changed

+21
-32
lines changed

1 file changed

+21
-32
lines changed

microscope/filterwheels/thorlabs.py

Lines changed: 21 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,22 @@ def __init__(self, com, baud=115200, timeout=2.0, **kwargs):
4040
"""
4141
super().__init__(**kwargs)
4242
self.eol = '\r'
43-
# The EOL character means the serial connection must be wrapped in a
44-
# TextIOWrapper.
4543
rawSerial = serial.Serial(port=com,
4644
baudrate=baud, timeout=timeout,
4745
stopbits=serial.STOPBITS_ONE,
4846
bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE,
4947
xonxoff=0)
50-
# Use a buffer size of 1 in the BufferedRWPair. Without this,
51-
# either 8192 chars need to be read before data is passed upwards,
52-
# or he buffer needs to be flushed, incurring a serial timeout.
53-
self.connection = io.TextIOWrapper(io.BufferedRWPair(rawSerial, rawSerial, 1))
54-
# Last received wheel position.
48+
# The Thorlabs controller serial implementation is strange.
49+
# Generally, it uses \r as EOL, but error messages use \n.
50+
# A readline after sending a 'pos?\r' command always times out,
51+
# but returns a string terminated by a newline.
52+
# We use TextIOWrapper with newline=None to perform EOL translation
53+
# inbound, but must explicitly append \r to outgoing commands.
54+
# The TextIOWrapper also deals with conversion between unicode
55+
# and bytes.
56+
self.connection = io.TextIOWrapper(rawSerial, newline=None,
57+
line_buffering=True, # flush on write
58+
write_through=True) # write out immediately
5559

5660
def initialize(self):
5761
pass
@@ -62,45 +66,30 @@ def _on_shutdown(self):
6266
def set_position(self, n):
6367
"""Public method to move to position n."""
6468
command = 'pos=%d' % n
65-
self.connection.write(command + self.eol)
66-
# The serial connection will timeout until new position is reached.
67-
# Count timeouts to detect failure to return to responsive state.
68-
count = 0
69-
maxCount = 10
70-
while True:
71-
response = self.connection.readline().strip()
72-
if response == command:
73-
# Command echo received - reset counter.
74-
count = 0
75-
elif response == '>':
76-
# Command input caret received - connection is responsive again.
77-
break
78-
else:
79-
# Increment counter and test against maxCount.
80-
count += 1
81-
if count > maxCount:
82-
self.connection.flush()
83-
raise Exception('fw102c: Communication error.')
84-
time.sleep(0.1)
69+
self._send_command(command)
8570

8671
def get_position(self):
8772
"""Public method to query the current position"""
8873
return int(self._send_command('pos?'))
8974

75+
def _readline(self):
76+
"""A custom _readline to overcome limitations of the serial implementation."""
77+
result = [None]
78+
while result[-1] not in ('\n', ''):
79+
result.append(self.connection.read())
80+
return ''.join(result[1:])
81+
9082
def _send_command(self, command):
9183
"""Send a command and return any result."""
9284
result = None
9385
self.connection.write(command + self.eol)
9486
response = 'dummy'
9587
while response not in [command, '']:
9688
# Read until we receive the command echo.
97-
response = self.connection.readline().strip()
89+
response = self._readline().strip('> \n\r')
9890
if command.endswith('?'):
9991
# Last response was the command. Next is result.
100-
result = self.connection.readline().strip()
101-
while response not in ['>', '']:
102-
# Read until we receive the input caret.
103-
response = self.connection.readline().strip()
92+
result = self._readline().strip()
10493
return result
10594

10695

0 commit comments

Comments
 (0)