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):
class AxisState(object):
STATE_VALIDATOR = re.compile("^[A-Z0-9]+\s*$")
"""
Standard states:
MOVING : 'Axis is moving'
......@@ -749,6 +746,18 @@ class AxisState(object):
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
def READY(self):
return "READY" in self._current_states
......@@ -785,18 +794,8 @@ class AxisState(object):
# set of active states.
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.
self._state_desc = {"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 ?))"
}
self._state_desc = self._STANDARD_STATES
for state in states:
if isinstance(state, tuple):
......@@ -811,25 +810,31 @@ class AxisState(object):
"""
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):
if not isinstance(state_name, str) or not AxisState.STATE_VALIDATOR.match(state_name):
raise ValueError(
"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):
# Raises ValueError if state_name is invalid.
self._check_state_name(state_name)
if state_desc is not None and '|' in state_desc:
raise ValueError("Invalid state: description contains invalid character '|'")
if state_name not in self._axis_states:
self._axis_states.add(state_name)
# if it is the first time we are creating a new state, create a
# 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.
if state_desc is None:
self._state_desc[state_name] = "Axis is %s" % state_name
else:
state_desc = "Axis is %s" % state_name
self._state_desc[state_name] = state_desc
# Makes state accessible via a class property.
......@@ -844,7 +849,7 @@ class AxisState(object):
??? how to flag OFF ???-> no : on en cree un nouveau.
"""
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:
self._current_states.append(state_name)
......@@ -905,3 +910,28 @@ class AxisState(object):
def __ne__(self, 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):
status = self.libgroup.status(axis.libaxis)
# Convert status from icepaplib to bliss format.
self.icestate.clear()
icestate = self.icestate.new()
# first all concurrent states
if(libicepap.status_lowlim(status)):
self.icestate.set("LIMNEG")
icestate.set("LIMNEG")
if(libicepap.status_highlim(status)):
self.icestate.set("LIMPOS")
icestate.set("LIMPOS")
if(libicepap.status_home(status)):
self.icestate.set("HOME")
icestate.set("HOME")
modcod, modstr, moddsc = libicepap.status_get_mode(status)
if modcod != None:
self.icestate.set(modstr)
icestate.set(modstr)
sccod, scstr, scdsc = libicepap.status_get_stopcode(status)
if sccod != None:
self.icestate.set(scstr)
icestate.set(scstr)
if(libicepap.status_isready(status)):
self.icestate.set("READY")
icestate.set("READY")
# if motor is ready then no need to investigate deeper
return self.icestate
return icestate
if(libicepap.status_ismoving(status)):
self.icestate.set("MOVING")
icestate.set("MOVING")
if(not libicepap.status_ispoweron(status)):
self.icestate.set("POWEROFF")
icestate.set("POWEROFF")
discod, disstr, disdsc = libicepap.status_get_disable(status)
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
# while homing motor, so let's not ask if motor is moving
if(libicepap.status_warning(status)):
warn_str = self.libgroup.warning(axis.libaxis)
warn_dsc = "warning condition: \n" + str(warn_str)
self.icestate.create_state("WARNING", warn_dsc)
self.icestate.set("WARNING")
icestate.create_state("WARNING", warn_dsc)
icestate.set("WARNING")
alarm_str = self.libgroup.alarm(axis.libaxis)
if alarm_str != 'NO':
alarm_dsc = "alarm condition: " + str(alarm_str)
self.icestate.create_state("ALARMDESC", alarm_dsc)
self.icestate.set("ALARMDESC")
icestate.create_state("ALARMDESC", alarm_dsc)
icestate.set("ALARMDESC")
return self.icestate
return icestate
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