Blackened everything
This commit is contained in:
parent
e213647217
commit
5966ce29be
57 changed files with 1938 additions and 1414 deletions
|
|
@ -1,3 +1,3 @@
|
|||
__all__ = ['base', 'mock', 'sanga']
|
||||
__all__ = ["base", "mock", "sanga"]
|
||||
|
||||
from . import base, mock, sanga
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ class BaseStage(metaclass=ABCMeta):
|
|||
lock (:py:class:`openflexure_microscope.lock.StrictLock`): Strict lock controlling thread
|
||||
access to camera hardware
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.lock = StrictLock(timeout=5)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from openflexure_microscope.utilities import axes_to_array
|
|||
from collections.abc import Iterable
|
||||
import numpy as np
|
||||
|
||||
|
||||
class MockStage(BaseStage):
|
||||
def __init__(self, port=None, **kwargs):
|
||||
BaseStage.__init__(self)
|
||||
|
|
@ -15,13 +16,13 @@ class MockStage(BaseStage):
|
|||
def state(self):
|
||||
"""The general state dictionary of the board."""
|
||||
state = {
|
||||
'position': {
|
||||
'x': self.position[0],
|
||||
'y': self.position[1],
|
||||
'z': self.position[2],
|
||||
"position": {
|
||||
"x": self.position[0],
|
||||
"y": self.position[1],
|
||||
"z": self.position[2],
|
||||
},
|
||||
'board': None,
|
||||
'version': '0'
|
||||
"board": None,
|
||||
"version": "0",
|
||||
}
|
||||
return state
|
||||
|
||||
|
|
@ -29,21 +30,15 @@ class MockStage(BaseStage):
|
|||
"""Update settings from a config dictionary"""
|
||||
|
||||
# Set backlash. Expects a dictionary with axis labels
|
||||
if 'backlash' in config:
|
||||
if "backlash" in config:
|
||||
# Construct backlash array
|
||||
backlash = axes_to_array(config['backlash'], ['x', 'y', 'z'], [0, 0, 0])
|
||||
backlash = axes_to_array(config["backlash"], ["x", "y", "z"], [0, 0, 0])
|
||||
self.backlash = backlash
|
||||
|
||||
def read_config(self) -> dict:
|
||||
"""Return the current settings as a dictionary"""
|
||||
blsh = self.backlash.tolist()
|
||||
config = {
|
||||
'backlash': {
|
||||
'x': blsh[0],
|
||||
'y': blsh[1],
|
||||
'z': blsh[2],
|
||||
}
|
||||
}
|
||||
config = {"backlash": {"x": blsh[0], "y": blsh[1], "z": blsh[2]}}
|
||||
return config
|
||||
|
||||
@property
|
||||
|
|
@ -69,7 +64,7 @@ class MockStage(BaseStage):
|
|||
assert len(blsh) == self.n_axes
|
||||
self._backlash = np.array(blsh)
|
||||
else:
|
||||
self._backlash = np.array([int(blsh)]*self.n_axes, dtype=np.int)
|
||||
self._backlash = np.array([int(blsh)] * self.n_axes, dtype=np.int)
|
||||
|
||||
def move_rel(self, displacement, axis=None, backlash=True):
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -18,26 +18,29 @@ class SangaStage(BaseStage):
|
|||
board (:py:class:`openflexure_microscope.stage.sangaboard.Sangaboard`): Parent Sangaboard object.
|
||||
_backlash (list): 3-element (element-per-axis) list of backlash compensation in steps.
|
||||
"""
|
||||
|
||||
def __init__(self, port=None, **kwargs):
|
||||
"""Class managing serial communications with the motors for an Openflexure stage"""
|
||||
BaseStage.__init__(self)
|
||||
|
||||
self.board = Sangaboard(port, **kwargs)
|
||||
|
||||
self._backlash = None # Initialise backlash storage, used by property setter/getter
|
||||
self.axis_names = ['x', 'y', 'z'] # Assume all sangaboards are 3 axis
|
||||
self._backlash = (
|
||||
None
|
||||
) # Initialise backlash storage, used by property setter/getter
|
||||
self.axis_names = ["x", "y", "z"] # Assume all sangaboards are 3 axis
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""The general state dictionary of the board."""
|
||||
state = {
|
||||
'position': {
|
||||
'x': self.position[0],
|
||||
'y': self.position[1],
|
||||
'z': self.position[2],
|
||||
"position": {
|
||||
"x": self.position[0],
|
||||
"y": self.position[1],
|
||||
"z": self.position[2],
|
||||
},
|
||||
'board': self.board.board,
|
||||
'firmware': self.board.firmware
|
||||
"board": self.board.board,
|
||||
"firmware": self.board.firmware,
|
||||
}
|
||||
return state
|
||||
|
||||
|
|
@ -80,27 +83,21 @@ class SangaStage(BaseStage):
|
|||
assert len(blsh) == self.n_axes
|
||||
self._backlash = np.array(blsh)
|
||||
else:
|
||||
self._backlash = np.array([int(blsh)]*self.n_axes, dtype=np.int)
|
||||
self._backlash = np.array([int(blsh)] * self.n_axes, dtype=np.int)
|
||||
|
||||
def apply_config(self, config: dict):
|
||||
"""Update settings from a config dictionary"""
|
||||
|
||||
# Set backlash. Expects a dictionary with axis labels
|
||||
if 'backlash' in config:
|
||||
if "backlash" in config:
|
||||
# Construct backlash array
|
||||
backlash = axes_to_array(config['backlash'], ['x', 'y', 'z'], [0, 0, 0])
|
||||
backlash = axes_to_array(config["backlash"], ["x", "y", "z"], [0, 0, 0])
|
||||
self.backlash = backlash
|
||||
|
||||
def read_config(self) -> dict:
|
||||
"""Return the current settings as a dictionary"""
|
||||
blsh = self.backlash.tolist()
|
||||
config = {
|
||||
'backlash': {
|
||||
'x': blsh[0],
|
||||
'y': blsh[1],
|
||||
'z': blsh[2],
|
||||
}
|
||||
}
|
||||
config = {"backlash": {"x": blsh[0], "y": blsh[1], "z": blsh[2]}}
|
||||
|
||||
return config
|
||||
|
||||
|
|
@ -117,7 +114,9 @@ class SangaStage(BaseStage):
|
|||
if axis is not None:
|
||||
# backlash correction is easier if we're always in 3D
|
||||
# so this code just converts single-axis moves into all-axis moves.
|
||||
assert axis in self.axis_names, "axis must be one of {}".format(self.axis_names)
|
||||
assert axis in self.axis_names, "axis must be one of {}".format(
|
||||
self.axis_names
|
||||
)
|
||||
move = np.zeros(self.n_axes, dtype=np.int)
|
||||
move[np.argmax(np.array(self.axis_names) == axis)] = int(displacement)
|
||||
displacement = move
|
||||
|
|
@ -134,7 +133,7 @@ class SangaStage(BaseStage):
|
|||
initial_move -= np.where(
|
||||
self.backlash * displacement < 0,
|
||||
self.backlash,
|
||||
np.zeros(self.n_axes, dtype=self.backlash.dtype)
|
||||
np.zeros(self.n_axes, dtype=self.backlash.dtype),
|
||||
)
|
||||
self.board.move_rel(initial_move)
|
||||
if np.any(displacement - initial_move != 0):
|
||||
|
|
@ -160,7 +159,9 @@ class SangaStage(BaseStage):
|
|||
"""
|
||||
starting_position = self.position
|
||||
rel_positions = np.array(rel_positions)
|
||||
assert rel_positions.shape[1] == 3, ValueError("Positions should be 3 elements long.")
|
||||
assert rel_positions.shape[1] == 3, ValueError(
|
||||
"Positions should be 3 elements long."
|
||||
)
|
||||
try:
|
||||
self.move_rel(rel_positions[0], backlash=backlash)
|
||||
yield 0
|
||||
|
|
@ -186,7 +187,7 @@ class SangaStage(BaseStage):
|
|||
|
||||
def close(self):
|
||||
"""Cleanly close communication with the stage"""
|
||||
if hasattr(self, 'board'):
|
||||
if hasattr(self, "board"):
|
||||
self.board.close()
|
||||
|
||||
# Methods specific to Sangaboard
|
||||
|
|
@ -205,12 +206,16 @@ class SangaStage(BaseStage):
|
|||
need to worry about that here.
|
||||
"""
|
||||
if type is not None:
|
||||
print("An exception occurred inside a with block, resetting position \
|
||||
to its value at the start of the with block")
|
||||
print(
|
||||
"An exception occurred inside a with block, resetting position \
|
||||
to its value at the start of the with block"
|
||||
)
|
||||
try:
|
||||
time.sleep(0.5)
|
||||
self.move_abs(self._position_on_enter)
|
||||
except Exception as e:
|
||||
print("A further exception occurred when resetting position: {}".format(e))
|
||||
print(
|
||||
"A further exception occurred when resetting position: {}".format(e)
|
||||
)
|
||||
print("Move completed, raising exception...")
|
||||
raise value # Propagate the exception
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import time
|
|||
import logging
|
||||
import warnings
|
||||
|
||||
|
||||
class ExtensibleSerialInstrument(object):
|
||||
"""
|
||||
An instrument that communicates by sending strings back and forth over serial
|
||||
|
|
@ -49,8 +50,13 @@ class ExtensibleSerialInstrument(object):
|
|||
blocked for a long time. The lock is reentrant so there's no issue with
|
||||
acquiring it twice.
|
||||
"""
|
||||
termination_character = "\n" #: All messages to or from the instrument end with this character.
|
||||
termination_line = None #: If multi-line responses are recieved, they must end with this string
|
||||
|
||||
termination_character = (
|
||||
"\n"
|
||||
) #: All messages to or from the instrument end with this character.
|
||||
termination_line = (
|
||||
None
|
||||
) #: If multi-line responses are recieved, they must end with this string
|
||||
ignore_echo = False
|
||||
port_settings = {}
|
||||
|
||||
|
|
@ -61,7 +67,7 @@ class ExtensibleSerialInstrument(object):
|
|||
logging.info("Updating ESI port settings")
|
||||
self.port_settings.update(kwargs)
|
||||
logging.info("Opening ESI connection to port {}".format(port))
|
||||
self.open(port, False) # Eventually this shouldn't rely on init...
|
||||
self.open(port, False) # Eventually this shouldn't rely on init...
|
||||
logging.info("Opened ESI connection to port {}".format(port))
|
||||
|
||||
def open(self, port=None, quiet=True):
|
||||
|
|
@ -71,17 +77,22 @@ class ExtensibleSerialInstrument(object):
|
|||
then we don't warn when ports are opened multiple times.
|
||||
"""
|
||||
with self.communications_lock:
|
||||
if hasattr(self,'_ser') and self._ser.isOpen():
|
||||
if not quiet: logging.warning("Attempted to open an already-open port!")
|
||||
if hasattr(self, "_ser") and self._ser.isOpen():
|
||||
if not quiet:
|
||||
logging.warning("Attempted to open an already-open port!")
|
||||
return
|
||||
if port is None:
|
||||
port=self.find_port()
|
||||
assert port is not None, "We don't have a serial port to open, meaning you didn't specify a valid port. Are you sure the instrument is connected?"
|
||||
self._ser = serial.Serial(port,**self.port_settings)
|
||||
#the block above wraps the serial IO layer with a text IO layer
|
||||
#this allows us to read/write in neat lines. NB the buffer size must
|
||||
#be set to 1 byte for maximum responsiveness.
|
||||
assert self.test_communications(), "The instrument doesn't seem to be responding. Did you specify the right port?"
|
||||
if port is None:
|
||||
port = self.find_port()
|
||||
assert (
|
||||
port is not None
|
||||
), "We don't have a serial port to open, meaning you didn't specify a valid port. Are you sure the instrument is connected?"
|
||||
self._ser = serial.Serial(port, **self.port_settings)
|
||||
# the block above wraps the serial IO layer with a text IO layer
|
||||
# this allows us to read/write in neat lines. NB the buffer size must
|
||||
# be set to 1 byte for maximum responsiveness.
|
||||
assert (
|
||||
self.test_communications()
|
||||
), "The instrument doesn't seem to be responding. Did you specify the right port?"
|
||||
|
||||
def close(self):
|
||||
"""Cleanly close the device. Includes proper logging statements."""
|
||||
|
|
@ -95,11 +106,12 @@ class ExtensibleSerialInstrument(object):
|
|||
|
||||
def __del__(self):
|
||||
"""Emergency close the device. Try to avoid having to use this."""
|
||||
if hasattr(self, '_ser') and self._ser.isOpen():
|
||||
if hasattr(self, "_ser") and self._ser.isOpen():
|
||||
with self.communications_lock:
|
||||
print(
|
||||
"Closing an open serial communication has been triggered by garbage collection!\n"\
|
||||
"Please close this device more sensibly in future.")
|
||||
"Closing an open serial communication has been triggered by garbage collection!\n"
|
||||
"Please close this device more sensibly in future."
|
||||
)
|
||||
try:
|
||||
self._ser.close()
|
||||
except Exception as e:
|
||||
|
|
@ -111,30 +123,39 @@ class ExtensibleSerialInstrument(object):
|
|||
def __exit__(self, type, value, traceback):
|
||||
"""Cleanly close down the instrument at end of a with block."""
|
||||
self.close()
|
||||
|
||||
def write(self,query_string):
|
||||
|
||||
def write(self, query_string):
|
||||
"""Write a string to the serial port"""
|
||||
with self.communications_lock:
|
||||
assert self._ser.isOpen(), "Attempted to write to the serial port before it was opened. Perhaps you need to call the 'open' method first?"
|
||||
#TODO: Check if this code is needed and if not kill it
|
||||
# try:
|
||||
# if self._ser.outWaiting()>0: self._ser.flushOutput() #ensure there's nothing waiting
|
||||
# except AttributeError:
|
||||
# if self._ser.out_waiting>0: self._ser.flushOutput() #ensure there's nothing waiting
|
||||
data=query_string+self.termination_character
|
||||
data=data.encode()
|
||||
assert (
|
||||
self._ser.isOpen()
|
||||
), "Attempted to write to the serial port before it was opened. Perhaps you need to call the 'open' method first?"
|
||||
# TODO: Check if this code is needed and if not kill it
|
||||
# try:
|
||||
# if self._ser.outWaiting()>0: self._ser.flushOutput() #ensure there's nothing waiting
|
||||
# except AttributeError:
|
||||
# if self._ser.out_waiting>0: self._ser.flushOutput() #ensure there's nothing waiting
|
||||
data = query_string + self.termination_character
|
||||
data = data.encode()
|
||||
self._ser.write(data)
|
||||
|
||||
def flush_input_buffer(self):
|
||||
"""Make sure there's nothing waiting to be read, and clear the buffer if there is."""
|
||||
with self.communications_lock:
|
||||
if self._ser.inWaiting()>0: self._ser.flushInput()
|
||||
if self._ser.inWaiting() > 0:
|
||||
self._ser.flushInput()
|
||||
|
||||
def readline(self, timeout=None):
|
||||
"""Read one line from the serial port."""
|
||||
with self.communications_lock:
|
||||
return self._ser.readline().decode('utf8').replace(self.termination_character,"\n")
|
||||
return (
|
||||
self._ser.readline()
|
||||
.decode("utf8")
|
||||
.replace(self.termination_character, "\n")
|
||||
)
|
||||
|
||||
_communications_lock = None
|
||||
|
||||
@property
|
||||
def communications_lock(self):
|
||||
"""A lock object used to protect access to the communications bus"""
|
||||
|
|
@ -143,7 +164,7 @@ class ExtensibleSerialInstrument(object):
|
|||
if self._communications_lock is None:
|
||||
self._communications_lock = threading.RLock()
|
||||
return self._communications_lock
|
||||
|
||||
|
||||
def read_multiline(self, termination_line=None, timeout=None):
|
||||
"""Read one line from the underlying bus. Must be overriden.
|
||||
|
||||
|
|
@ -152,15 +173,19 @@ class ExtensibleSerialInstrument(object):
|
|||
with self.communications_lock:
|
||||
if termination_line is None:
|
||||
termination_line = self.termination_line
|
||||
assert isinstance(termination_line, str), "If you perform a multiline query, you must specify a termination line either through the termination_line keyword argument or the termination_line property of the NPSerialInstrument."
|
||||
assert isinstance(
|
||||
termination_line, str
|
||||
), "If you perform a multiline query, you must specify a termination line either through the termination_line keyword argument or the termination_line property of the NPSerialInstrument."
|
||||
response = ""
|
||||
last_line = "dummy"
|
||||
while termination_line not in last_line and len(last_line) > 0: #read until we get the termination line.
|
||||
while (
|
||||
termination_line not in last_line and len(last_line) > 0
|
||||
): # read until we get the termination line.
|
||||
last_line = self.readline(timeout)
|
||||
response += last_line
|
||||
return response
|
||||
|
||||
def query(self,queryString,multiline=False,termination_line=None,timeout=None):
|
||||
|
||||
def query(self, queryString, multiline=False, termination_line=None, timeout=None):
|
||||
"""
|
||||
Write a string to the stage controller and return its response.
|
||||
|
||||
|
|
@ -170,22 +195,31 @@ class ExtensibleSerialInstrument(object):
|
|||
with self.communications_lock:
|
||||
self.flush_input_buffer()
|
||||
self.write(queryString)
|
||||
if self.ignore_echo == True: # Needs Implementing for a multiline read!
|
||||
if self.ignore_echo == True: # Needs Implementing for a multiline read!
|
||||
first_line = self.readline(timeout).strip()
|
||||
if first_line == queryString:
|
||||
return self.readline(timeout).strip()
|
||||
else:
|
||||
logging.info('This command did not echo!!!')
|
||||
logging.info("This command did not echo!!!")
|
||||
return first_line
|
||||
|
||||
|
||||
if termination_line is not None:
|
||||
multiline = True
|
||||
if multiline:
|
||||
return self.read_multiline(termination_line)
|
||||
else:
|
||||
return self.readline(timeout).strip() #question: should we strip the final newline?
|
||||
|
||||
def parsed_query(self, query_string, response_string=r"%d", re_flags=0, parse_function=None, **kwargs):
|
||||
return self.readline(
|
||||
timeout
|
||||
).strip() # question: should we strip the final newline?
|
||||
|
||||
def parsed_query(
|
||||
self,
|
||||
query_string,
|
||||
response_string=r"%d",
|
||||
re_flags=0,
|
||||
parse_function=None,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
Perform a query, returning a parsed form of the response.
|
||||
|
||||
|
|
@ -204,46 +238,81 @@ class ExtensibleSerialInstrument(object):
|
|||
"""
|
||||
|
||||
response_regex = response_string
|
||||
noop = lambda x: x #placeholder null parse function
|
||||
placeholders = [ #tuples of (regex matching placeholder, regex to replace it with, parse function)
|
||||
noop = lambda x: x # placeholder null parse function
|
||||
placeholders = [ # tuples of (regex matching placeholder, regex to replace it with, parse function)
|
||||
(r"%c", r".", noop),
|
||||
(r"%(\d+)c", r".{\1}", noop), #TODO support %cn where n is a number of chars
|
||||
(
|
||||
r"%(\d+)c",
|
||||
r".{\1}",
|
||||
noop,
|
||||
), # TODO support %cn where n is a number of chars
|
||||
(r"%d", r"[-+]?\\d+", int),
|
||||
(r"%[eEfg]", r"[-+]?(?:\\d+(?:\.\\d*)?|\.\\d+)(?:[eE][-+]?\\d+)?", float),
|
||||
(r"%i", r"[-+]?(?:0[xX][\\dA-Fa-f]+|0[0-7]*|\\d+)", lambda x: int(x, 0)), #0=autodetect base
|
||||
(r"%o", r"[-+]?[0-7]+", lambda x: int(x, 8)), #8 means octal
|
||||
(
|
||||
r"%i",
|
||||
r"[-+]?(?:0[xX][\\dA-Fa-f]+|0[0-7]*|\\d+)",
|
||||
lambda x: int(x, 0),
|
||||
), # 0=autodetect base
|
||||
(r"%o", r"[-+]?[0-7]+", lambda x: int(x, 8)), # 8 means octal
|
||||
(r"%s", r"\\s+", noop),
|
||||
(r"%u", r"\\d+", int),
|
||||
(r"%[xX]", r"[-+]?(?:0[xX])?[\\dA-Fa-f]+", lambda x: int(x, 16)), #16 forces hexadecimal
|
||||
(
|
||||
r"%[xX]",
|
||||
r"[-+]?(?:0[xX])?[\\dA-Fa-f]+",
|
||||
lambda x: int(x, 16),
|
||||
), # 16 forces hexadecimal
|
||||
]
|
||||
matched_placeholders = []
|
||||
for placeholder, regex, parse_fun in placeholders:
|
||||
response_regex = re.sub(placeholder, '('+regex+')', response_regex) #substitute regex for placeholder
|
||||
matched_placeholders.extend([(parse_fun, m.start()) for m in re.finditer(placeholder, response_string)]) #save the positions of the placeholders
|
||||
response_regex = re.sub(
|
||||
placeholder, "(" + regex + ")", response_regex
|
||||
) # substitute regex for placeholder
|
||||
matched_placeholders.extend(
|
||||
[
|
||||
(parse_fun, m.start())
|
||||
for m in re.finditer(placeholder, response_string)
|
||||
]
|
||||
) # save the positions of the placeholders
|
||||
if parse_function is None:
|
||||
parse_function = [f for f, s in sorted(matched_placeholders, key=lambda m: m[1])] #order parse functions by their occurrence in the original string
|
||||
if not hasattr(parse_function,'__iter__'):
|
||||
parse_function = [parse_function] #make sure it's a list.
|
||||
|
||||
reply = self.query(query_string, **kwargs) #do the query
|
||||
#if match this could be because another response entered the buffer between write and read. Sleep for short while then
|
||||
#check if something is now in the buffer, while the buffer is not empty repeat regex
|
||||
waited=False
|
||||
res=re.search(response_regex, reply, flags=re_flags)
|
||||
parse_function = [
|
||||
f for f, s in sorted(matched_placeholders, key=lambda m: m[1])
|
||||
] # order parse functions by their occurrence in the original string
|
||||
if not hasattr(parse_function, "__iter__"):
|
||||
parse_function = [parse_function] # make sure it's a list.
|
||||
|
||||
reply = self.query(query_string, **kwargs) # do the query
|
||||
# if match this could be because another response entered the buffer between write and read. Sleep for short while then
|
||||
# check if something is now in the buffer, while the buffer is not empty repeat regex
|
||||
waited = False
|
||||
res = re.search(response_regex, reply, flags=re_flags)
|
||||
while res is None:
|
||||
if not waited:
|
||||
time.sleep(.1)
|
||||
waited=True
|
||||
time.sleep(0.1)
|
||||
waited = True
|
||||
original_reply = reply
|
||||
if self._ser.inWaiting():
|
||||
reply = self.readline().strip()
|
||||
res=re.search(response_regex, reply, flags=re_flags)
|
||||
res = re.search(response_regex, reply, flags=re_flags)
|
||||
if res is not None:
|
||||
warnings.warn("Query suceeded after initially receieving unmatched response ('%s') to '%s'. Match pattern /%s/ (generated regex /%s/)"%(original_reply, query_string, response_string, response_regex), RuntimeWarning)
|
||||
warnings.warn(
|
||||
"Query suceeded after initially receieving unmatched response ('%s') to '%s'. Match pattern /%s/ (generated regex /%s/)"
|
||||
% (
|
||||
original_reply,
|
||||
query_string,
|
||||
response_string,
|
||||
response_regex,
|
||||
),
|
||||
RuntimeWarning,
|
||||
)
|
||||
else:
|
||||
raise ValueError("Stage response to '%s' ('%s') wasn't matched by /%s/ (generated regex /%s/)" % (query_string, original_reply, response_string, response_regex))
|
||||
raise ValueError(
|
||||
"Stage response to '%s' ('%s') wasn't matched by /%s/ (generated regex /%s/)"
|
||||
% (query_string, original_reply, response_string, response_regex)
|
||||
)
|
||||
try:
|
||||
parsed_result= [f(g) for f, g in zip(parse_function, res.groups())] #try to apply each parse function to its argument
|
||||
parsed_result = [
|
||||
f(g) for f, g in zip(parse_function, res.groups())
|
||||
] # try to apply each parse function to its argument
|
||||
if len(parsed_result) == 1:
|
||||
return parsed_result[0]
|
||||
else:
|
||||
|
|
@ -251,10 +320,15 @@ class ExtensibleSerialInstrument(object):
|
|||
except ValueError:
|
||||
logging.info("Matched Groups: {}".format(res.groups()))
|
||||
logging.info("Parsing Functions {}:".format(parse_function))
|
||||
raise ValueError("Stage response to %s ('%s') couldn't be parsed by the supplied function" % (query_string, reply))
|
||||
raise ValueError(
|
||||
"Stage response to %s ('%s') couldn't be parsed by the supplied function"
|
||||
% (query_string, reply)
|
||||
)
|
||||
|
||||
def int_query(self, query_string, **kwargs):
|
||||
"""Perform a query and return the result(s) as integer(s) (see parsedQuery)"""
|
||||
return self.parsed_query(query_string, "%d", **kwargs)
|
||||
|
||||
def float_query(self, query_string, **kwargs):
|
||||
"""Perform a query and return the result(s) as float(s) (see parsedQuery)"""
|
||||
return self.parsed_query(query_string, "%f", **kwargs)
|
||||
|
|
@ -273,7 +347,13 @@ class ExtensibleSerialInstrument(object):
|
|||
if our instrument is there."""
|
||||
with self.communications_lock:
|
||||
success = False
|
||||
for port_name, _, _ in serial.tools.list_ports.comports(): #loop through serial ports, apparently 256 is the limit?!
|
||||
for (
|
||||
port_name,
|
||||
_,
|
||||
_,
|
||||
) in (
|
||||
serial.tools.list_ports.comports()
|
||||
): # loop through serial ports, apparently 256 is the limit?!
|
||||
try:
|
||||
logging.info("Trying port {}".format(port_name))
|
||||
self.open(port_name)
|
||||
|
|
@ -285,14 +365,15 @@ class ExtensibleSerialInstrument(object):
|
|||
try:
|
||||
self.close()
|
||||
except:
|
||||
pass #we don't care if there's an error closing the port...
|
||||
pass # we don't care if there's an error closing the port...
|
||||
if success:
|
||||
break #again, make sure this happens *after* closing the port
|
||||
break # again, make sure this happens *after* closing the port
|
||||
if success:
|
||||
return port_name
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class OptionalModule(object):
|
||||
"""This allows a `ExtensibleSerialInstrument` to have optional features.
|
||||
|
||||
|
|
@ -300,32 +381,42 @@ class OptionalModule(object):
|
|||
for interfacing with optional modules which may or may not be included with
|
||||
the serial instrument, and can be added or removed at run-time.
|
||||
"""
|
||||
|
||||
def __init__(self,available,parent=None,module_type="Undefined",model="Generic"):
|
||||
assert type(available) is bool, 'Option module availablity should be a boolean not a {}'.format(type(available))
|
||||
self._available=available
|
||||
self._parent=parent
|
||||
assert type(module_type) is str, 'Option module type should be a string not a {}'.format(type(module_typ))
|
||||
self.module_type=module_type
|
||||
|
||||
def __init__(
|
||||
self, available, parent=None, module_type="Undefined", model="Generic"
|
||||
):
|
||||
assert (
|
||||
type(available) is bool
|
||||
), "Option module availablity should be a boolean not a {}".format(
|
||||
type(available)
|
||||
)
|
||||
self._available = available
|
||||
self._parent = parent
|
||||
assert (
|
||||
type(module_type) is str
|
||||
), "Option module type should be a string not a {}".format(type(module_typ))
|
||||
self.module_type = module_type
|
||||
if available:
|
||||
assert type(model) is str, 'Option module type should be a string not a {}'.format(type(model))
|
||||
self.model=model
|
||||
assert (
|
||||
type(model) is str
|
||||
), "Option module type should be a string not a {}".format(type(model))
|
||||
self.model = model
|
||||
else:
|
||||
self.model=None
|
||||
|
||||
self.model = None
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
return self._available
|
||||
|
||||
|
||||
def confirm_available(self):
|
||||
"""Check if module is available, no return, will raise exception if not available!"""
|
||||
assert self._available, "No \"{}\" supported on firmware".format(self.module_type)
|
||||
|
||||
assert self._available, 'No "{}" supported on firmware'.format(self.module_type)
|
||||
|
||||
def describe(self):
|
||||
"""Consistently spaced desciption for listing modules"""
|
||||
return self.module_type+" "*(25-len(self.module_type))+"- "+self.model
|
||||
|
||||
|
||||
return self.module_type + " " * (25 - len(self.module_type)) + "- " + self.model
|
||||
|
||||
|
||||
class QueriedProperty(object):
|
||||
"""A Property interface that reads and writes from the instrument on the bus.
|
||||
|
||||
|
|
@ -359,8 +450,18 @@ class QueriedProperty(object):
|
|||
:ack_writes:
|
||||
set to "readline" to discard a line of input after writing.
|
||||
"""
|
||||
def __init__(self, get_cmd=None, set_cmd=None, validate=None, valrange=None,
|
||||
fdel=None, doc=None, response_string=None, ack_writes="no"):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
get_cmd=None,
|
||||
set_cmd=None,
|
||||
validate=None,
|
||||
valrange=None,
|
||||
fdel=None,
|
||||
doc=None,
|
||||
response_string=None,
|
||||
ack_writes="no",
|
||||
):
|
||||
self.response_string = response_string
|
||||
self.get_cmd = get_cmd
|
||||
self.set_cmd = set_cmd
|
||||
|
|
@ -369,48 +470,54 @@ class QueriedProperty(object):
|
|||
self.fdel = fdel
|
||||
self.ack_writes = ack_writes
|
||||
self.__doc__ = doc
|
||||
|
||||
|
||||
# TODO: standardise the return (single value only vs parsed result), consider bool
|
||||
def __get__(self, obj, objtype=None):
|
||||
if issubclass(type(obj),OptionalModule):
|
||||
if issubclass(type(obj), OptionalModule):
|
||||
obj.confirm_available()
|
||||
obj=obj._parent
|
||||
obj = obj._parent
|
||||
if obj is None:
|
||||
return self
|
||||
assert issubclass(type(obj),ExtensibleSerialInstrument)
|
||||
assert issubclass(type(obj), ExtensibleSerialInstrument)
|
||||
if self.get_cmd is None:
|
||||
raise AttributeError("unreadable attribute")
|
||||
# Allow certain "magic" values to set the response string
|
||||
for key, val in [('float',r"%f"),
|
||||
('int',r"%d"),]:
|
||||
for key, val in [("float", r"%f"), ("int", r"%d")]:
|
||||
if self.response_string == key:
|
||||
self.response_string = val
|
||||
if self.response_string in ['bool', 'raw', None]:
|
||||
if self.response_string in ["bool", "raw", None]:
|
||||
value = obj.query(self.get_cmd)
|
||||
if self.response_string == 'bool':
|
||||
if self.response_string == "bool":
|
||||
value = bool(value)
|
||||
else:
|
||||
value = obj.parsed_query(self.get_cmd, self.response_string)
|
||||
return value
|
||||
|
||||
def __set__(self, obj, value):
|
||||
if issubclass(type(obj),OptionalModule):
|
||||
if issubclass(type(obj), OptionalModule):
|
||||
obj.confirm_available()
|
||||
obj=obj._parent
|
||||
assert issubclass(type(obj),ExtensibleSerialInstrument)
|
||||
obj = obj._parent
|
||||
assert issubclass(type(obj), ExtensibleSerialInstrument)
|
||||
if self.set_cmd is None:
|
||||
raise AttributeError("can't set attribute")
|
||||
if self.validate is not None:
|
||||
if value not in self.validate:
|
||||
raise ValueError('invalid value supplied - value must be one of {}'.format(self.validate))
|
||||
raise ValueError(
|
||||
"invalid value supplied - value must be one of {}".format(
|
||||
self.validate
|
||||
)
|
||||
)
|
||||
if self.valrange is not None:
|
||||
if value < min(self.valrange) or value > max(self.valrange):
|
||||
raise ValueError('invalid value supplied - value must be in the range {}-{}'.format(*self.valrange))
|
||||
raise ValueError(
|
||||
"invalid value supplied - value must be in the range {}-{}".format(
|
||||
*self.valrange
|
||||
)
|
||||
)
|
||||
message = self.set_cmd
|
||||
if '{0' in message:
|
||||
if "{0" in message:
|
||||
message = message.format(value)
|
||||
elif '%' in message:
|
||||
elif "%" in message:
|
||||
message = message % value
|
||||
obj.write(message)
|
||||
if self.ack_writes == "readline":
|
||||
|
|
|
|||
|
|
@ -9,12 +9,20 @@ It is (c) Richard Bowman & Julian Stirling 2019 and released under GNU GPL v3
|
|||
"""
|
||||
from __future__ import print_function, division
|
||||
import time
|
||||
from .extensible_serial_instrument import ExtensibleSerialInstrument, OptionalModule, QueriedProperty, EIGHTBITS, PARITY_NONE, STOPBITS_ONE
|
||||
from .extensible_serial_instrument import (
|
||||
ExtensibleSerialInstrument,
|
||||
OptionalModule,
|
||||
QueriedProperty,
|
||||
EIGHTBITS,
|
||||
PARITY_NONE,
|
||||
STOPBITS_ONE,
|
||||
)
|
||||
import serial, serial.tools.list_ports
|
||||
import re
|
||||
import warnings
|
||||
import logging
|
||||
|
||||
|
||||
class Sangaboard(ExtensibleSerialInstrument):
|
||||
"""Class managing serial communications with a Sangaboard
|
||||
|
||||
|
|
@ -39,34 +47,45 @@ class Sangaboard(ExtensibleSerialInstrument):
|
|||
"""
|
||||
|
||||
# List of valid and deprecated firmwares, for communication checks
|
||||
valid_firmwares = [
|
||||
(0, 5),
|
||||
(0, 4)
|
||||
]
|
||||
deprecated_firmwares = [
|
||||
(0, 4)
|
||||
]
|
||||
valid_firmwares = [(0, 5), (0, 4)]
|
||||
deprecated_firmwares = [(0, 4)]
|
||||
|
||||
# These are the settings for the sangaboards serial port, and can usually be left as default.
|
||||
port_settings = {'baudrate':115200, 'bytesize':EIGHTBITS, 'parity':PARITY_NONE, 'stopbits':STOPBITS_ONE}
|
||||
|
||||
port_settings = {
|
||||
"baudrate": 115200,
|
||||
"bytesize": EIGHTBITS,
|
||||
"parity": PARITY_NONE,
|
||||
"stopbits": STOPBITS_ONE,
|
||||
}
|
||||
|
||||
# position, step time and ramp time are get/set using simple serial commands.
|
||||
position = QueriedProperty(get_cmd="p?", response_string=r"%d %d %d",
|
||||
doc="Get the position of the axes as a tuple of 3 integers.")
|
||||
step_time = QueriedProperty(get_cmd="dt?", set_cmd="dt %d", response_string="minimum step delay %d",
|
||||
doc="Get or set the minimum time between steps of the motors in microseconds.\n\n"
|
||||
"The step time is ``1000000/max speed`` in steps/second. It is saved to EEPROM on "
|
||||
"the sangaboard, so it will be persistent even if the motor controller is turned off.")
|
||||
ramp_time = QueriedProperty(get_cmd="ramp_time?", set_cmd="ramp_time %d", response_string="ramp time %d",
|
||||
doc="Get or set the acceleration time in microseconds.\n\n"
|
||||
"The motors will accelerate/decelerate between stationary and maximum speed over `ramp_time` "
|
||||
"microseconds. Zero means the motor runs at full speed initially, with no accleration "
|
||||
"control. Small moves may last less than `2*ramp_time`, in which case the acceleration "
|
||||
"will be the same, but the motor will never reach full speed. It is saved to EEPROM on "
|
||||
"the sangaboard, so it will be persistent even if the motor controller is turned off.")
|
||||
position = QueriedProperty(
|
||||
get_cmd="p?",
|
||||
response_string=r"%d %d %d",
|
||||
doc="Get the position of the axes as a tuple of 3 integers.",
|
||||
)
|
||||
step_time = QueriedProperty(
|
||||
get_cmd="dt?",
|
||||
set_cmd="dt %d",
|
||||
response_string="minimum step delay %d",
|
||||
doc="Get or set the minimum time between steps of the motors in microseconds.\n\n"
|
||||
"The step time is ``1000000/max speed`` in steps/second. It is saved to EEPROM on "
|
||||
"the sangaboard, so it will be persistent even if the motor controller is turned off.",
|
||||
)
|
||||
ramp_time = QueriedProperty(
|
||||
get_cmd="ramp_time?",
|
||||
set_cmd="ramp_time %d",
|
||||
response_string="ramp time %d",
|
||||
doc="Get or set the acceleration time in microseconds.\n\n"
|
||||
"The motors will accelerate/decelerate between stationary and maximum speed over `ramp_time` "
|
||||
"microseconds. Zero means the motor runs at full speed initially, with no accleration "
|
||||
"control. Small moves may last less than `2*ramp_time`, in which case the acceleration "
|
||||
"will be the same, but the motor will never reach full speed. It is saved to EEPROM on "
|
||||
"the sangaboard, so it will be persistent even if the motor controller is turned off.",
|
||||
)
|
||||
|
||||
# The names of the sangaboard's axes. NB this also defines the number of axes
|
||||
axis_names = ('x', 'y', 'z')
|
||||
axis_names = ("x", "y", "z")
|
||||
|
||||
# Once initialised, `firmware` is a string that identifies the firmware version
|
||||
firmware = None
|
||||
|
|
@ -88,30 +107,38 @@ class Sangaboard(ExtensibleSerialInstrument):
|
|||
# Make absolutely sure that whatever port we're on is valid
|
||||
self.check_valid_firmware()
|
||||
|
||||
#Bit messy: Defining all valid modules as not available, then overwriting with available information if available.
|
||||
# Bit messy: Defining all valid modules as not available, then overwriting with available information if available.
|
||||
self.light_sensor = LightSensor(False)
|
||||
|
||||
for module in self.list_modules():
|
||||
module_type=module.split(':')[0].strip()
|
||||
module_model=module.split(':')[1].strip()
|
||||
if module_type.startswith('Light Sensor'):
|
||||
module_type = module.split(":")[0].strip()
|
||||
module_model = module.split(":")[1].strip()
|
||||
if module_type.startswith("Light Sensor"):
|
||||
if module_model in self.supported_light_sensors:
|
||||
self.light_sensor = LightSensor(True,parent=self,model=module_model)
|
||||
self.light_sensor = LightSensor(
|
||||
True, parent=self, model=module_model
|
||||
)
|
||||
else:
|
||||
logging.warning("Light sensor model \"%s\" not recognised."%(module_model))
|
||||
elif module_type.startswith('Endstops'):
|
||||
logging.warning(
|
||||
'Light sensor model "%s" not recognised.' % (module_model)
|
||||
)
|
||||
elif module_type.startswith("Endstops"):
|
||||
self.endstops = Endstops(True, parent=self, model=module_model)
|
||||
else:
|
||||
logging.warning("Module type \"{}\" not recognised.".format(module_type))
|
||||
logging.warning(
|
||||
'Module type "{}" not recognised.'.format(module_type)
|
||||
)
|
||||
|
||||
self.board = self.query("board",timeout=2).rstrip()
|
||||
self.board = self.query("board", timeout=2).rstrip()
|
||||
|
||||
except Exception as e:
|
||||
# If an error occurred while setting up (e.g. because the board isn't connected or something)
|
||||
# make sure we close the serial port cleanly (otherwise it hangs open).
|
||||
self.close()
|
||||
logging.error(e)
|
||||
logging.error("You may need to update the firmware running on the Sangaboard.")
|
||||
logging.error(
|
||||
"You may need to update the firmware running on the Sangaboard."
|
||||
)
|
||||
raise e
|
||||
|
||||
def test_communications(self):
|
||||
|
|
@ -124,16 +151,22 @@ class Sangaboard(ExtensibleSerialInstrument):
|
|||
logging.debug("Running firmware checks")
|
||||
|
||||
# Request firmware version from the board
|
||||
self.firmware = self.query("version",timeout=2).rstrip()
|
||||
self.firmware = self.query("version", timeout=2).rstrip()
|
||||
logging.info("Firmware response: {}".format(self.firmware))
|
||||
# Check for valid firmware string
|
||||
if self.firmware:
|
||||
match = re.match(r"Sangaboard Firmware v(([\d]+)(?:\.([\d]+))+)", self.firmware)
|
||||
match = re.match(
|
||||
r"Sangaboard Firmware v(([\d]+)(?:\.([\d]+))+)", self.firmware
|
||||
)
|
||||
if not match:
|
||||
# Try old firmware format
|
||||
match = re.match(r"OpenFlexure Motor Board v(([\d]+)(?:\.([\d]+))+)", self.firmware)
|
||||
match = re.match(
|
||||
r"OpenFlexure Motor Board v(([\d]+)(?:\.([\d]+))+)", self.firmware
|
||||
)
|
||||
if not match:
|
||||
logging.error("Version string \"{}\" not recognised.".format(self.firmware))
|
||||
logging.error(
|
||||
'Version string "{}" not recognised.'.format(self.firmware)
|
||||
)
|
||||
return False
|
||||
else:
|
||||
logging.error("No firmware version string was returned.")
|
||||
|
|
@ -146,15 +179,17 @@ class Sangaboard(ExtensibleSerialInstrument):
|
|||
self.firmware_version = match.group(1)
|
||||
|
||||
if version_tuple not in Sangaboard.valid_firmwares:
|
||||
logging.error("This version of the Python module requires firmware v0.5 (with legacy support for v0.4)")
|
||||
logging.error(
|
||||
"This version of the Python module requires firmware v0.5 (with legacy support for v0.4)"
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
if version_tuple in Sangaboard.deprecated_firmwares:
|
||||
logging.warning(
|
||||
"Warning this Sangaboard is using v0.4 of the firmware, this will soon be depreciated! "
|
||||
"Please consider updating the Sangaboard firmware"
|
||||
)
|
||||
|
||||
|
||||
return True
|
||||
|
||||
def move_rel(self, displacement, axis=None):
|
||||
|
|
@ -164,10 +199,12 @@ class Sangaboard(ExtensibleSerialInstrument):
|
|||
axis: None (for 3-axis moves) or one of 'x','y','z'
|
||||
"""
|
||||
if axis is not None:
|
||||
assert axis in self.axis_names, "axis must be one of {}".format(self.axis_names)
|
||||
assert axis in self.axis_names, "axis must be one of {}".format(
|
||||
self.axis_names
|
||||
)
|
||||
self.query("mr{} {}".format(axis, int(displacement)))
|
||||
else:
|
||||
#TODO: assert displacement is 3 integers
|
||||
# TODO: assert displacement is 3 integers
|
||||
self.query("mr {} {} {}".format(*list(displacement)))
|
||||
|
||||
def release_motors(self):
|
||||
|
|
@ -181,12 +218,12 @@ class Sangaboard(ExtensibleSerialInstrument):
|
|||
queries the board for its position, then instructs it to make about
|
||||
relative move.
|
||||
"""
|
||||
rel_mov = [f_pos-i_pos for f_pos,i_pos in zip(final,self.position)]
|
||||
rel_mov = [f_pos - i_pos for f_pos, i_pos in zip(final, self.position)]
|
||||
return self.move_rel(rel_mov, **kwargs)
|
||||
|
||||
def query(self, message, *args, **kwargs):
|
||||
"""Send a message and read the response. See ExtensibleSerialInstrument.query()"""
|
||||
time.sleep(0.001) # This is to protect the stage from us talking too fast!
|
||||
time.sleep(0.001) # This is to protect the stage from us talking too fast!
|
||||
return ExtensibleSerialInstrument.query(self, message, *args, **kwargs)
|
||||
|
||||
def list_modules(self):
|
||||
|
|
@ -194,12 +231,15 @@ class Sangaboard(ExtensibleSerialInstrument):
|
|||
|
||||
Each module will correspond to a string of the form ``Module Name: Model``
|
||||
"""
|
||||
modules = self.query("list_modules",multiline=True,termination_line="--END--\r\n").split('\r\n')[:-2]
|
||||
modules = self.query(
|
||||
"list_modules", multiline=True, termination_line="--END--\r\n"
|
||||
).split("\r\n")[:-2]
|
||||
return [str(module) for module in modules]
|
||||
|
||||
def print_help(self):
|
||||
"""Print the stage's built-in help message."""
|
||||
print(self.query("help",multiline=True,termination_line="--END--\r\n"))
|
||||
print(self.query("help", multiline=True, termination_line="--END--\r\n"))
|
||||
|
||||
|
||||
class LightSensor(OptionalModule):
|
||||
"""An optional module giving access to the light sensor.
|
||||
|
|
@ -209,19 +249,25 @@ class LightSensor(OptionalModule):
|
|||
module which is an instance of this class. It can be used to access
|
||||
the light sensor (usually via the I2C bus).
|
||||
"""
|
||||
|
||||
valid_gains = None
|
||||
_valid_gains_int = None
|
||||
integration_time = QueriedProperty(
|
||||
get_cmd="light_sensor_integration_time?",
|
||||
set_cmd="light_sensor_integration_time %d",
|
||||
response_string="light sensor integration time %d ms",
|
||||
doc="Get or set the integration time of the light sensor in milliseconds."
|
||||
doc="Get or set the integration time of the light sensor in milliseconds.",
|
||||
)
|
||||
intensity = QueriedProperty(
|
||||
get_cmd="light_sensor_intensity?",
|
||||
response_string="%d",
|
||||
doc="Read the current intensity measured by the light sensor (arbitrary units).",
|
||||
)
|
||||
intensity = QueriedProperty(get_cmd="light_sensor_intensity?", response_string="%d",
|
||||
doc="Read the current intensity measured by the light sensor (arbitrary units).")
|
||||
|
||||
def __init__(self,available,parent=None,model="Generic"):
|
||||
super(LightSensor, self).__init__(available,parent=parent,module_type="LightSensor",model=model)
|
||||
def __init__(self, available, parent=None, model="Generic"):
|
||||
super(LightSensor, self).__init__(
|
||||
available, parent=parent, module_type="LightSensor", model=model
|
||||
)
|
||||
if available:
|
||||
self.valid_gains = self.__get_gain_values()
|
||||
self._valid_gains_int = [int(g) for g in self.valid_gains]
|
||||
|
|
@ -232,21 +278,25 @@ class LightSensor(OptionalModule):
|
|||
|
||||
Valid gain values are defined in the `valid_gains` property, and should be floating-point numbers."""
|
||||
self.confirm_available()
|
||||
gain = self._parent.query('light_sensor_gain?')
|
||||
M = re.search('[0-9\.]+(?=x)',gain)
|
||||
assert M is not None, "Cannot read gain string: \"{}\"".format(gain)
|
||||
#gain is a float as non integer gains exist but are set with floor of value
|
||||
gain = self._parent.query("light_sensor_gain?")
|
||||
M = re.search("[0-9\.]+(?=x)", gain)
|
||||
assert M is not None, 'Cannot read gain string: "{}"'.format(gain)
|
||||
# gain is a float as non integer gains exist but are set with floor of value
|
||||
return float(M.group())
|
||||
|
||||
@gain.setter
|
||||
def gain(self,val):
|
||||
def gain(self, val):
|
||||
self.confirm_available()
|
||||
assert int(val) in self._valid_gains_int, "Gain {} not valid must be one of: {}".format(val,self.valid_gains)
|
||||
gain = self._parent.query('light_sensor_gain %d'%(int(val)))
|
||||
M = re.search('[0-9\.]+(?=x)',gain)
|
||||
assert M is not None, "Cannot read gain string: \"{}\"".format(gain)
|
||||
#gain is a float as non integer gains exist but are set with floor of value
|
||||
assert int(val) == int(float(M.group())), 'Gain of {} set, \"{}\" returned'.format(val,gain)
|
||||
assert (
|
||||
int(val) in self._valid_gains_int
|
||||
), "Gain {} not valid must be one of: {}".format(val, self.valid_gains)
|
||||
gain = self._parent.query("light_sensor_gain %d" % (int(val)))
|
||||
M = re.search("[0-9\.]+(?=x)", gain)
|
||||
assert M is not None, 'Cannot read gain string: "{}"'.format(gain)
|
||||
# gain is a float as non integer gains exist but are set with floor of value
|
||||
assert int(val) == int(
|
||||
float(M.group())
|
||||
), 'Gain of {} set, "{}" returned'.format(val, gain)
|
||||
|
||||
def __get_gain_values(self):
|
||||
"""Read the allowable values for the light sensor's gain.
|
||||
|
|
@ -256,15 +306,16 @@ class LightSensor(OptionalModule):
|
|||
values, the list will be of strings.
|
||||
"""
|
||||
self.confirm_available()
|
||||
gains = self._parent.query('light_sensor_gain_values?')
|
||||
gains = self._parent.query("light_sensor_gain_values?")
|
||||
try:
|
||||
M = re.findall('[0-9\.]+(?=x)',gains)
|
||||
M = re.findall("[0-9\.]+(?=x)", gains)
|
||||
return [float(gain) for gain in M]
|
||||
except:
|
||||
# Fall back to strings if we don't get floats (unlikely)
|
||||
gain_strings = gains[20:].split(", ")
|
||||
return gain_strings
|
||||
|
||||
|
||||
class Endstops(OptionalModule):
|
||||
"""An optional module for use with endstops.
|
||||
|
||||
|
|
@ -273,35 +324,41 @@ class Endstops(OptionalModule):
|
|||
the type, state of the endstops, read and write maximum positions, and home.
|
||||
"""
|
||||
|
||||
installed=[]
|
||||
installed = []
|
||||
"""List of installed endstop types (min, max, soft)"""
|
||||
|
||||
def __init__(self,available,parent=None,model="min"):
|
||||
super(Endstops, self).__init__(available,parent=parent,model="Endstops")
|
||||
self.installed=model.split(' ')
|
||||
def __init__(self, available, parent=None, model="min"):
|
||||
super(Endstops, self).__init__(available, parent=parent, model="Endstops")
|
||||
self.installed = model.split(" ")
|
||||
|
||||
status = QueriedProperty(get_cmd="endstops?", response_string=r"%d %d %d",
|
||||
doc="Get endstops status as {-1,0,1} for {min,no,max} endstop triggered for each axis")
|
||||
maxima = QueriedProperty(get_cmd="max_p?", set_cmd="max %d %d %d", response_string="%d %d %d",
|
||||
doc="Vector of maximum positions, homing to max endstops will measure this, "+
|
||||
"can be set to a known value for use with max only and min+soft endstops")
|
||||
status = QueriedProperty(
|
||||
get_cmd="endstops?",
|
||||
response_string=r"%d %d %d",
|
||||
doc="Get endstops status as {-1,0,1} for {min,no,max} endstop triggered for each axis",
|
||||
)
|
||||
maxima = QueriedProperty(
|
||||
get_cmd="max_p?",
|
||||
set_cmd="max %d %d %d",
|
||||
response_string="%d %d %d",
|
||||
doc="Vector of maximum positions, homing to max endstops will measure this, "
|
||||
+ "can be set to a known value for use with max only and min+soft endstops",
|
||||
)
|
||||
|
||||
def home(self, direction="min", axes=['x','y','z']):
|
||||
def home(self, direction="min", axes=["x", "y", "z"]):
|
||||
""" Home given/all axes in the given direction (min/max/both)
|
||||
|
||||
:param direction: one of {min,max,both}
|
||||
:param axes: list of axes e.g. ['x','y']
|
||||
"""
|
||||
ax=0
|
||||
if 'x' in axes:
|
||||
ax+=1
|
||||
if 'y' in axes:
|
||||
ax+=2
|
||||
if 'z' in axes:
|
||||
ax+=3
|
||||
ax = 0
|
||||
if "x" in axes:
|
||||
ax += 1
|
||||
if "y" in axes:
|
||||
ax += 2
|
||||
if "z" in axes:
|
||||
ax += 3
|
||||
|
||||
if direction == "min" or direction == "both":
|
||||
self._parent.query('home_min {}'.format(ax))
|
||||
self._parent.query("home_min {}".format(ax))
|
||||
if direction == "max" or direction == "both":
|
||||
self._parent.query('home_max {}'.format(ax))
|
||||
|
||||
self._parent.query("home_max {}".format(ax))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue