Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
dd3d93d
Added driver/motor tables for T/KDC001 APT devs.
cgranade Apr 10, 2017
4427bf1
Moved TODO comment to avoid pylint error.
cgranade Apr 11, 2017
0165a3c
Misc Py3k changes for ThorLabs APT
cgranade Apr 11, 2017
6d37eaa
motion_timeout for APT motor cmds, fix scale factor
cgranade Apr 11, 2017
b7e7559
ThorLabsAPT: Example of new config support.
cgranade Apr 12, 2017
9a7bea4
More pylint fixes
cgranade Apr 12, 2017
bccf325
Merge branch 'develop' into feature-apt-tables
cgranade Apr 19, 2017
c6471a2
Fix for line continuation convention.
cgranade Apr 19, 2017
11822f0
Rearranged imports into standard order.
cgranade Apr 19, 2017
0941e4d
Added an APT test. Not working yet.
cgranade Apr 20, 2017
7d4f0b5
Merge branch 'develop' into feature-apt-tables
scasagrande Apr 20, 2017
f493ef6
Merge branch 'feature-apt-tables' of github.com:Galvant/InstrumentKit…
cgranade Apr 20, 2017
68cb90f
Fix linting issues
scasagrande Apr 23, 2017
07f2836
New handling in loopback for empty terminator.
cgranade Jul 12, 2017
389944a
struct.Struct for contents of hw_info packets
cgranade Jul 12, 2017
5f8ce1c
Support for specifying expected apt pkt sizes
cgranade Jul 12, 2017
56585f8
Fixes to APT and APT tests
cgranade Jul 12, 2017
2acbfc1
Merge branch 'feature-apt-tables' of github.com:Galvant/InstrumentKit…
cgranade Jul 12, 2017
3561482
Missed a conflict marker.
cgranade Jul 12, 2017
81bef38
Merge remote-tracking branch 'origin/develop' into feature-apt-tables
cgranade Jul 12, 2017
fefd40b
Fixed bug due to `if size` falling through on size == 0.
cgranade Jul 12, 2017
5bc5a6e
Removed trailing whitespace.
cgranade Jul 12, 2017
f5db0ee
Locked requirements.txt; see #174.
cgranade Jul 12, 2017
0de45d4
Merge branch 'master' into feature-apt-tables
scasagrande Feb 7, 2019
22d9675
Remove numpy version pinning in requirements.txt
scasagrande Feb 7, 2019
da0b6c8
Add tests to cover additional loopback comm behaviour
scasagrande Feb 7, 2019
3cec5a9
Make pylint happy
scasagrande Feb 7, 2019
e2e59f1
Revert changes to size=0 behaviour in loopback comm
scasagrande Feb 7, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions instruments/abstract_instruments/comm/loopback_communicator.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,17 +108,21 @@ def read_raw(self, size=-1):
:rtype: `bytes`
"""
if self._stdin is not None:
if size >= 0:
if size == -1 or size is None:
result = bytes()
if self._terminator:
while result.endswith(self._terminator.encode("utf-8")) is False:
c = self._stdin.read(1)
if c == b'':
break
result += c
return result[:-len(self._terminator)]
return self._stdin.read(-1)

elif size >= 0:
input_var = self._stdin.read(size)
return bytes(input_var)
elif size == -1:
result = bytes()
while result.endswith(self._terminator.encode("utf-8")) is False:
c = self._stdin.read(1)
if c == b'':
break
result += c
return result[:-len(self._terminator)]

else:
raise ValueError("Must read a positive value of characters.")
else:
Expand Down
16 changes: 13 additions & 3 deletions instruments/abstract_instruments/comm/serial_communicator.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,23 @@ def read_raw(self, size=-1):
return resp
elif size == -1:
result = bytes()
while result.endswith(self._terminator.encode("utf-8")) is False:
# If the terminator is empty, we can't use endswith, but must
# read as many bytes as are available.
# On the other hand, if terminator is nonempty, we can check
# that the tail end of the buffer matches it.
c = None
term = self._terminator.encode('utf-8') if self._terminator else None
while not (
result.endswith(term)
if term is not None else
c == b''
):
c = self._conn.read(1)
if c == b'':
if c == b'' and term is not None:
raise IOError("Serial connection timed out before reading "
"a termination character.")
result += c
return result[:-len(self._terminator)]
return result[:-len(term)] if term is not None else result
else:
raise ValueError("Must read a positive value of characters.")

Expand Down
19 changes: 19 additions & 0 deletions instruments/tests/test_comm/test_loopback.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,25 @@ def test_loopbackcomm_read_raw_2char_terminator():
assert mock_stdin.read.call_count == 5


def test_loopbackcomm_read_raw_terminator_is_empty_string():
mock_stdin = mock.MagicMock()
mock_stdin.read.side_effect = [b"abc"]
comm = LoopbackCommunicator(stdin=mock_stdin)
comm._terminator = ""

assert comm.read_raw() == b"abc"
mock_stdin.read.assert_has_calls([mock.call(-1)])
assert mock_stdin.read.call_count == 1


def test_loopbackcomm_read_raw_size_invalid():
with pytest.raises(ValueError):
mock_stdin = mock.MagicMock()
mock_stdin.read.side_effect = [b"abc"]
comm = LoopbackCommunicator(stdin=mock_stdin)
comm.read_raw(size=-2)


def test_loopbackcomm_write_raw():
mock_stdout = mock.MagicMock()
comm = LoopbackCommunicator(stdout=mock_stdout)
Expand Down
82 changes: 82 additions & 0 deletions instruments/tests/test_thorlabs/test_thorlabs_apt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Module containing tests for the Thorlabs TC200
"""

# IMPORTS ####################################################################

# pylint: disable=unused-import

from __future__ import absolute_import

import struct

import pytest
import quantities as pq

import instruments as ik
from instruments.thorlabs._packets import ThorLabsPacket, hw_info_data
from instruments.thorlabs._cmds import ThorLabsCommands
from instruments.tests import expected_protocol

# TESTS ######################################################################

# pylint: disable=protected-access,unused-argument


def test_apt_hw_info():
with expected_protocol(
ik.thorlabs.ThorLabsAPT,
[
ThorLabsPacket(
message_id=ThorLabsCommands.HW_REQ_INFO,
param1=0x00, param2=0x00,
dest=0x50,
source=0x01,
data=None
).pack()
],
[
ThorLabsPacket(
message_id=ThorLabsCommands.HW_GET_INFO,
dest=0x01,
source=0x50,
data=hw_info_data.pack(
# Serial number
b'\x01\x02\x03\x04',
# Model number
"ABC-123".encode('ascii'),
# HW type
3,
# FW version,
0xa1, 0xa2, 0xa3,
# Notes
"abcdefg".encode('ascii'),
# HW version
42,
# Mod state
43,
# Number of channels
2
)
).pack()
],
sep=""
) as apt:
# Check internal representations.
# NB: we shouldn't do this in some sense, but these fields
# act as an API to the APT subclasses.
assert apt._hw_type == "Unknown type: 3"
assert apt._fw_version == "a1.a2.a3"
assert apt._notes == "abcdefg"
assert apt._hw_version == 42
assert apt._mod_state == 43

# Check external API.
assert apt.serial_number == '01020304'
assert apt.model_number == 'ABC-123'
assert apt.name == (
"ThorLabs APT Instrument model ABC-123, "
"serial 01020304 (HW version 42, FW version a1.a2.a3)"
)
43 changes: 39 additions & 4 deletions instruments/thorlabs/_abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@
from __future__ import absolute_import
from __future__ import division

import time

from instruments.thorlabs import _packets
from instruments.abstract_instruments.instrument import Instrument
from instruments.util_fns import assume_units

from quantities import second

# CLASSES #####################################################################

Expand All @@ -35,10 +40,10 @@ def sendpacket(self, packet):
:param packet: The thorlabs data packet that will be queried
:type packet: `ThorLabsPacket`
"""
self.sendcmd(packet.pack())
self._file.write_raw(packet.pack())

# pylint: disable=protected-access
def querypacket(self, packet, expect=None):
def querypacket(self, packet, expect=None, timeout=None, expect_data_len=None):
"""
Sends a packet to the connected APT instrument, and waits for a packet
in response. Optionally, checks whether the received packet type is
Expand All @@ -52,11 +57,40 @@ def querypacket(self, packet, expect=None):
with the default value of `None` then no checking occurs.
:type expect: `str` or `None`

:param timeout: Sets a timeout to wait before returning `None`, indicating
no packet was received. If the timeout is set to `None`, then the
timeout is inherited from the underlying communicator and no additional
timeout is added. If timeout is set to `False`, then this method waits
indefinitely. If timeout is set to a unitful quantity, then it is interpreted
as a time and used as the timeout value. Finally, if the timeout is a unitless
number (e.g. `float` or `int`), then seconds are assumed.

:param int expect_data_len: Number of bytes to expect as the
data for the returned packet.

:return: Returns the response back from the instrument wrapped up in
a thorlabs packet
a ThorLabs APT packet, or None if no packet was received.
:rtype: `ThorLabsPacket`
"""
resp = self.query(packet.pack())
t_start = time.time()

if timeout:
timeout = assume_units(timeout, second).rescale('second').magnitude

while True:
self._file.write_raw(packet.pack())
resp = self._file.read_raw(
expect_data_len + 6 # the header is six bytes.
if expect_data_len else
6
)
if resp or timeout is None:
break
else:
tic = time.time()
if tic - t_start > timeout:
break

if not resp:
if expect is None:
return None
Expand All @@ -71,4 +105,5 @@ def querypacket(self, packet, expect=None):
raise IOError("APT returned message ID {}, expected {}".format(
pkt._message_id, expect
))

return pkt
14 changes: 13 additions & 1 deletion instruments/thorlabs/_packets.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@

message_header_nopacket = struct.Struct('<HBBBB')
message_header_wpacket = struct.Struct('<HHBB')
hw_info_data = struct.Struct(
'<' # Declare endianness.
'4s' # serial_number
'8s' # model_number
'H' # hw_type_int
'BBBx' # fw_version
'48s' # notes
'12x' # padding
'H' # hw_version
'H' # mod_state
'H' # n_channels
)

# CLASSES #####################################################################

Expand Down Expand Up @@ -166,7 +178,7 @@ def unpack(cls, bytes):

# Check if 0x80 is set on header byte 4. If so, then this packet
# has data.
if struct.unpack("B", header[4])[0] & 0x80:
if struct.unpack("B", header[4:5])[0] & 0x80:
msg_id, length, dest, source = message_header_wpacket.unpack(
header)
dest = dest ^ 0x80 # Turn off 0x80.
Expand Down
Loading