Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 10 additions & 4 deletions hardwarelibrary/communication/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,10 +254,12 @@ def formatResponse(self, result):
return bytearray(formatted.encode('utf-8'))
return super().formatResponse(result)

def send(self, port, params=None) -> bool:
def send(self, port, params=None, **kwargs) -> bool:
try:
if params is not None:
textCommand = self.requestEncoder.format(params)
elif kwargs:
textCommand = self.requestEncoder.format(**kwargs)
else:
textCommand = self.requestEncoder

Expand Down Expand Up @@ -312,10 +314,12 @@ def __init__(self, name,
def payload(self):
return self.requestEncoder

def send(self, port, params=None) -> bool:
def send(self, port, params=None, **kwargs) -> bool:
try:
if params is not None:
textCommand = self.requestEncoder.format(params)
elif kwargs:
textCommand = self.requestEncoder.format(**kwargs)
else:
textCommand = self.requestEncoder

Expand Down Expand Up @@ -475,12 +479,14 @@ def unpackReply(self, replyBytes):
return replyBytes
return struct.unpack(self.replyDecoder.format, replyBytes)

def send(self, port) -> bool:
def send(self, port, **params) -> bool:
try:
port.writeData(data=self.data, endPoint=self.endPoints[0])
data = self.buildSendData(**params)
port.writeData(data=data, endPoint=self.endPoints[0])
self.isSent = True
if self.replyDecoder is not None and self.replyDecoder.length > 0:
self.reply = port.readData(length=self.replyDecoder.length)
self.matchGroups = self.unpackReply(self.reply)
self.isSentSuccessfully = True
except Exception as err:
self.exceptions.append(err)
Expand Down
7 changes: 6 additions & 1 deletion hardwarelibrary/communication/serialport.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class SerialPort(CommunicationPort):
or the find_url.py script from the distribution. More info: https://eblot.github.io/pyftdi/api/usbtools.html
You have to add any custom VID/PID when using tools (but they are added here in SerialPort) @line 30.
"""
def __init__(self, idVendor=None, idProduct=None, serialNumber=None, portPath=None, port=None):
def __init__(self, idVendor=None, idProduct=None, serialNumber=None, portPath=None, port=None, delay=0):
CommunicationPort.__init__(self)

try:
Expand All @@ -46,6 +46,7 @@ def __init__(self, idVendor=None, idProduct=None, serialNumber=None, portPath=No
port.close()

self.port = None # direct port, must be closed.
self.delay = delay # seconds to wait before each read/write (Sutter MP-285 needs ~0.1)

@classmethod
def matchSinglePort(cls, idVendor=None, idProduct=None, serialNumber=None):
Expand Down Expand Up @@ -195,6 +196,8 @@ def readString(self, endPoint=0):

def readData(self, length, endPoint=0) -> bytearray:
with self.portLock:
if self.delay > 0:
time.sleep(self.delay)
data = self.port.read(length)
if len(data) != length:
raise CommunicationReadTimeout("Only obtained {0}".format(data))
Expand All @@ -203,6 +206,8 @@ def readData(self, length, endPoint=0) -> bytearray:

def writeData(self, data, endPoint=0) -> int:
with self.portLock:
if self.delay > 0:
time.sleep(self.delay)
nBytesWritten = self.port.write(data)
if nBytesWritten != len(data):
raise IOError("Not all bytes written to port")
Expand Down
67 changes: 15 additions & 52 deletions hardwarelibrary/motion/sutterdevice.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,15 @@ def doInitializeDevice(self):
if portPath is None:
raise PhysicalDevice.UnableToInitialize("No Sutter Device connected")

self.port = SerialPort(portPath=portPath)
self.port = SerialPort(portPath=portPath, delay=0.1)
self.port.open(baudRate=128000, timeout=10)

if self.port is None:
raise PhysicalDevice.UnableToInitialize("Cannot allocate port for serial '{0}'".format(self.serialNumber))

self.positionInMicrosteps()
# Verify the device is talking. Direct .send() bypasses sendCommand's
# state check, which would reject because state is not yet Ready here.
self.commands["GET_POSITION"].send(port=self.port)

except Exception as error:
if self.port is not None:
Expand All @@ -86,60 +88,21 @@ def doShutdownDevice(self):
self.port.close()
self.port = None

def sendCommandBytes(self, commandBytes):
""" The function to write a command to the endpoint. It will initialize the device
if it is not alread initialized. On failure, it will warn and shutdown."""
if self.port is None:
self.initializeDevice()

time.sleep(0.1)
nBytesWritten = self.port.writeData(commandBytes)
if nBytesWritten != len(commandBytes):
raise Exception(f"Unable to send command {commandBytes} to device.")

def readReply(self, size, format) -> tuple:
""" The function to read a reply from the endpoint. It will initialize the device
if it is not already initialized. On failure, it will warn and shutdown.
It will unpack the reply into a tuple.
"""
if format is None:
raise Exception("You must provide a format for unpacking in readReply")

if self.port is None:
self.initializeDevice()

time.sleep(0.1)
replyBytes = self.port.readData(size)
if len(replyBytes) != size:
raise Exception(f"Not enough bytes read in readReply {replyBytes}")

# print(replyBytes, format)
# print(unpack(format, replyBytes))
return unpack(format, replyBytes)

def sendCommand(self, name, **params):
"""Send a command from the commands dict and return the unpacked reply."""
cmd = self.commands[name]
data = cmd.buildSendData(**params)
self.sendCommandBytes(data)
if cmd.replyDecoder is not None and cmd.replyDecoder.length > 0:
return self.readReply(cmd.replyDecoder.length, cmd.replyDecoder.format)
return None

def positionInMicrosteps(self) -> (int, int, int): # for compatibility
return self.doGetPosition()

def doGetPosition(self) -> (int, int, int):
""" Returns the position in microsteps """
(x, y, z) = self.sendCommand("GET_POSITION")
cmd = self.sendCommand("GET_POSITION")
(x, y, z) = cmd.matchGroups
return (x, y, z)

def doMoveTo(self, position):
""" Move to a position in microsteps """
x, y, z = position
reply = self.sendCommand("MOVE", x=int(x), y=int(y), z=int(z))
if reply != (b'\r',):
raise Exception(f"Expected carriage return, but got '{reply}' instead.")
cmd = self.sendCommand("MOVE", x=int(x), y=int(y), z=int(z))
if cmd.matchGroups != (b'\r',):
raise Exception(f"Expected carriage return, but got '{cmd.matchGroups}' instead.")

def doMoveBy(self, displacement):
dx, dy, dz = displacement
Expand All @@ -150,15 +113,15 @@ def doMoveBy(self, displacement):
raise Exception("Unable to read position from device")

def doHome(self):
reply = self.sendCommand("HOME")
if reply != (b'\r',):
raise Exception(f"Expected carriage return, but got {reply} instead.")
cmd = self.sendCommand("HOME")
if cmd.matchGroups != (b'\r',):
raise Exception(f"Expected carriage return, but got {cmd.matchGroups} instead.")

def work(self):
self.home()
reply = self.sendCommand("WORK")
if reply != (b'\r',):
raise Exception(f"Expected carriage return, but got {reply} instead.")
cmd = self.sendCommand("WORK")
if cmd.matchGroups != (b'\r',):
raise Exception(f"Expected carriage return, but got {cmd.matchGroups} instead.")


class DebugSerialPort(TableDrivenDebugPort):
Expand Down
14 changes: 12 additions & 2 deletions hardwarelibrary/physicaldevice.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,11 +181,21 @@ def stopBackgroundStatusUpdates(self):
else:
raise RuntimeError("No status loop running")

def sendCommand(self, command):
def sendCommand(self, name, **params):
"""Look up the named command in self.commands, send it through
self.port, and return the Command object so callers can read
.reply / .matchGroups / .exceptions / .isSentSuccessfully.

Params are passed through to Command.send: TextCommand uses them
for .format(**params) substitution into text_format; DataCommand
uses them for buildSendData(**params) when sendFormat is set.
"""
if self.state != DeviceState.Ready:
raise PhysicalDevice.NotInitialized

command.send(port=self.port)
command = self.commands[name]
command.send(port=self.port, **params)
return command

@classmethod
def any(cls):
Expand Down
4 changes: 2 additions & 2 deletions hardwarelibrary/tests/testConnectSutter.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class TestConnectSutter(unittest.TestCase):
def testConnectDebugSutter(self):
sutter = SutterDevice(serialNumber="debug")
# The port is not open until initializeDevice()
sutter.doInitializeDevice()
sutter.initializeDevice()

position = sutter.position()

Expand All @@ -19,7 +19,7 @@ def testConnectDebugSutter(self):

def testMoveToWithDebugSutter(self):
sutter = SutterDevice("debug")
sutter.doInitializeDevice()
sutter.initializeDevice()

sutter.moveTo((0, 100, 4000))
position = sutter.position()
Expand Down
8 changes: 4 additions & 4 deletions hardwarelibrary/tests/testPhysicalDevice.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,9 +263,9 @@ def testEchoCommands(self):
self.device.initializeDevice()
for name, command in self.device.commands.items():
try:
self.device.sendCommand(command)
self.device.sendCommand(name)
except Exception as err:
self.fail("Unable to send command {0} to device {1}: {2}".format(command.name, self.device, err))
self.fail("Unable to send command {0} to device {1}: {2}".format(name, self.device, err))
self.device.shutdownDevice()

class TestDebugEchoPhysicalDevice(BaseTestCases.TestPhysicalDeviceBase):
Expand All @@ -277,9 +277,9 @@ def testEchoCommands(self):
self.device.initializeDevice()
for name, command in self.device.commands.items():
try:
self.device.sendCommand(command)
self.device.sendCommand(name)
except Exception as err:
self.fail("Unable to send command {0} to device {1}: {2}".format(command.name, self.device, err))
self.fail("Unable to send command {0} to device {1}: {2}".format(name, self.device, err))
self.device.shutdownDevice()


Expand Down