Commit 9e42263c authored by Manuel Perez's avatar Manuel Perez
Browse files

Merge branch 'issue_51' into 'master'

Fixes #51: IcePAP axis state mix between greenlets/threads

Tries to fix a possible state mix between two or more axis that are queried concurrently for the state (different greenlets or threads)

Also minimizes dictionary copies when creating new `AxisState` objects.
This seemed important since in a motion loop these objects might be created quite often.

See merge request !185
parents 7de1f6ff 4a438a36
...@@ -735,9 +735,6 @@ class AxisRef(object): ...@@ -735,9 +735,6 @@ class AxisRef(object):
class AxisState(object): class AxisState(object):
STATE_VALIDATOR = re.compile("^[A-Z0-9]+\s*$")
""" """
Standard states: Standard states:
MOVING : 'Axis is moving' MOVING : 'Axis is moving'
...@@ -749,6 +746,18 @@ class AxisState(object): ...@@ -749,6 +746,18 @@ class AxisState(object):
OFF : 'Axis is disabled (must be enabled to move (not ready ?))' OFF : 'Axis is disabled (must be enabled to move (not ready ?))'
""" """
STATE_VALIDATOR = re.compile("^[A-Z0-9]+\s*$")
_STANDARD_STATES = {
"READY" : "Axis is READY",
"MOVING": "Axis is MOVING",
"FAULT" : "Error from controller",
"LIMPOS": "Hardware high limit active",
"LIMNEG": "Hardware low limit active",
"HOME" : "Home signal active",
"OFF" : "Axis is disabled (must be enabled to move (not ready ?))"
}
@property @property
def READY(self): def READY(self):
return "READY" in self._current_states return "READY" in self._current_states
...@@ -785,18 +794,8 @@ class AxisState(object): ...@@ -785,18 +794,8 @@ class AxisState(object):
# set of active states. # set of active states.
self._current_states = list() self._current_states = list()
# set of defined/created states.
self._axis_states = set(["READY", "MOVING", "FAULT", "LIMPOS", "LIMNEG", "HOME", "OFF"])
# dict of descriptions of states. # dict of descriptions of states.
self._state_desc = {"READY" : "Axis is READY", self._state_desc = self._STANDARD_STATES
"MOVING": "Axis is MOVING",
"FAULT" : "Error from controller",
"LIMPOS": "Hardware high limit active",
"LIMNEG": "Hardware low limit active",
"HOME" : "Home signal active",
"OFF" : "Axis is disabled (must be enabled to move (not ready ?))"
}
for state in states: for state in states:
if isinstance(state, tuple): if isinstance(state, tuple):
...@@ -811,26 +810,32 @@ class AxisState(object): ...@@ -811,26 +810,32 @@ class AxisState(object):
""" """
Returns a list of available/created states for this axis. Returns a list of available/created states for this axis.
""" """
return list(self._axis_states) return list(self._state_desc)
def _check_state_name(self, state_name): def _check_state_name(self, state_name):
if not isinstance(state_name, str) or not AxisState.STATE_VALIDATOR.match(state_name): if not isinstance(state_name, str) or not AxisState.STATE_VALIDATOR.match(state_name):
raise ValueError( raise ValueError(
"Invalid state: a state must be a string containing only block letters") "Invalid state: a state must be a string containing only block letters")
def _has_custom_states(self):
return not self._state_desc is AxisState._STANDARD_STATES
def create_state(self, state_name, state_desc=None): def create_state(self, state_name, state_desc=None):
# Raises ValueError if state_name is invalid. # Raises ValueError if state_name is invalid.
self._check_state_name(state_name) self._check_state_name(state_name)
if state_desc is not None and '|' in state_desc: if state_desc is not None and '|' in state_desc:
raise ValueError("Invalid state: description contains invalid character '|'") raise ValueError("Invalid state: description contains invalid character '|'")
if state_name not in self._axis_states: # if it is the first time we are creating a new state, create a
self._axis_states.add(state_name) # private copy of standard states to be able to modify locally
if not self._has_custom_states():
self._state_desc = AxisState._STANDARD_STATES.copy()
if state_name not in self._state_desc:
# new description is put in dict. # new description is put in dict.
if state_desc is None: if state_desc is None:
self._state_desc[state_name] = "Axis is %s" % state_name state_desc = "Axis is %s" % state_name
else: self._state_desc[state_name] = state_desc
self._state_desc[state_name] = state_desc
# Makes state accessible via a class property. # Makes state accessible via a class property.
# NO: we can't, because the objects of this class will become unpickable, # NO: we can't, because the objects of this class will become unpickable,
...@@ -844,7 +849,7 @@ class AxisState(object): ...@@ -844,7 +849,7 @@ class AxisState(object):
??? how to flag OFF ???-> no : on en cree un nouveau. ??? how to flag OFF ???-> no : on en cree un nouveau.
""" """
def set(self, state_name): def set(self, state_name):
if state_name in self._axis_states: if state_name in self._state_desc:
if state_name not in self._current_states: if state_name not in self._current_states:
self._current_states.append(state_name) self._current_states.append(state_name)
...@@ -905,3 +910,28 @@ class AxisState(object): ...@@ -905,3 +910,28 @@ class AxisState(object):
def __ne__(self, other): def __ne__(self, other):
return not self.__eq__(other) return not self.__eq__(other)
def new(self, share_states=True):
"""
Create a new AxisState which contains the same possible states but no
current state.
If this AxisState contains custom states and *share_states* is True
(default), the possible states are shared with the new AxisState.
Otherwise, a copy of possible states is created for the new AxisState.
Keyword Args:
share_states: If this AxisState contains custom states and
*share_states* is True (default), the possible states
are shared with the new AxisState. Otherwise, a copy
of possible states is created for the new AxisState.
Returns:
AxisState: a copy of this AxisState with no current states
"""
result = AxisState()
if self._has_custom_states() and not share_states:
result._state_desc = self._state_desc.copy()
else:
result._state_desc = self._state_desc
return result
...@@ -201,57 +201,57 @@ class IcePAP(Controller): ...@@ -201,57 +201,57 @@ class IcePAP(Controller):
status = self.libgroup.status(axis.libaxis) status = self.libgroup.status(axis.libaxis)
# Convert status from icepaplib to bliss format. # Convert status from icepaplib to bliss format.
self.icestate.clear() icestate = self.icestate.new()
# first all concurrent states # first all concurrent states
if(libicepap.status_lowlim(status)): if(libicepap.status_lowlim(status)):
self.icestate.set("LIMNEG") icestate.set("LIMNEG")
if(libicepap.status_highlim(status)): if(libicepap.status_highlim(status)):
self.icestate.set("LIMPOS") icestate.set("LIMPOS")
if(libicepap.status_home(status)): if(libicepap.status_home(status)):
self.icestate.set("HOME") icestate.set("HOME")
modcod, modstr, moddsc = libicepap.status_get_mode(status) modcod, modstr, moddsc = libicepap.status_get_mode(status)
if modcod != None: if modcod != None:
self.icestate.set(modstr) icestate.set(modstr)
sccod, scstr, scdsc = libicepap.status_get_stopcode(status) sccod, scstr, scdsc = libicepap.status_get_stopcode(status)
if sccod != None: if sccod != None:
self.icestate.set(scstr) icestate.set(scstr)
if(libicepap.status_isready(status)): if(libicepap.status_isready(status)):
self.icestate.set("READY") icestate.set("READY")
# if motor is ready then no need to investigate deeper # if motor is ready then no need to investigate deeper
return self.icestate return icestate
if(libicepap.status_ismoving(status)): if(libicepap.status_ismoving(status)):
self.icestate.set("MOVING") icestate.set("MOVING")
if(not libicepap.status_ispoweron(status)): if(not libicepap.status_ispoweron(status)):
self.icestate.set("POWEROFF") icestate.set("POWEROFF")
discod, disstr, disdsc = libicepap.status_get_disable(status) discod, disstr, disdsc = libicepap.status_get_disable(status)
if discod != None: if discod != None:
self.icestate.set(disstr) icestate.set(disstr)
if not self.icestate.MOVING: if not icestate.MOVING:
# it seems it is not safe to call warning and/or alarm commands # it seems it is not safe to call warning and/or alarm commands
# while homing motor, so let's not ask if motor is moving # while homing motor, so let's not ask if motor is moving
if(libicepap.status_warning(status)): if(libicepap.status_warning(status)):
warn_str = self.libgroup.warning(axis.libaxis) warn_str = self.libgroup.warning(axis.libaxis)
warn_dsc = "warning condition: \n" + str(warn_str) warn_dsc = "warning condition: \n" + str(warn_str)
self.icestate.create_state("WARNING", warn_dsc) icestate.create_state("WARNING", warn_dsc)
self.icestate.set("WARNING") icestate.set("WARNING")
alarm_str = self.libgroup.alarm(axis.libaxis) alarm_str = self.libgroup.alarm(axis.libaxis)
if alarm_str != 'NO': if alarm_str != 'NO':
alarm_dsc = "alarm condition: " + str(alarm_str) alarm_dsc = "alarm condition: " + str(alarm_str)
self.icestate.create_state("ALARMDESC", alarm_dsc) icestate.create_state("ALARMDESC", alarm_dsc)
self.icestate.set("ALARMDESC") icestate.set("ALARMDESC")
return self.icestate return icestate
def prepare_move(self, motion): def prepare_move(self, motion):
""" """
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment