Commit da64659f authored by Matias Guijarro's avatar Matias Guijarro
Browse files

motor settings: move code from controller and axis to motor_settings.py

parent 593a3bda
......@@ -341,8 +341,7 @@ class GroupMove:
for motion in motions:
motion.axis._set_moving_state()
for _, chan in motion.axis._beacon_channels.items():
chan.unregister_callback(chan._setting_update_cb)
motion.axis.settings.unregister_channels_callbacks()
with capture_exceptions(raise_index=0) as capture:
with capture():
......@@ -402,10 +401,9 @@ class GroupMove:
for hook in axis.motion_hooks:
hooks[hook].append(motion)
# set move done
for _, chan in axis._beacon_channels.items():
chan.register_callback(chan._setting_update_cb)
axis.settings.register_channels_callbacks()
# set move done
motion.axis._set_move_done()
if self._interrupted_move:
......@@ -676,7 +674,7 @@ class Axis:
self.__encoder.axis = self
self.__config = StaticConfig(config)
self.__settings = AxisSettings(self)
self.__init_config_properties()
self._init_config_properties()
self.__no_offset = False
self._group_move = GroupMove()
self._lock = gevent.lock.Semaphore()
......@@ -762,20 +760,20 @@ class Axis:
"""
return not self.__move_done.is_set()
def __init_config_properties(
def _init_config_properties(
self, velocity=True, acceleration=True, limits=True, sign=True, backlash=True
):
self.__steps_per_unit = self.config.get("steps_per_unit", float, 1)
self.__tolerance = self.config.get("tolerance", float, 1e-4)
if velocity:
if self.controller.axis_settings.config_setting["velocity"]:
if "velocity" in self.settings.config_settings:
self.__config_velocity = self.config.get("velocity", float)
if self.controller.axis_settings.config_setting["jog_velocity"]:
if "jog_velocity" in self.settings.config_settings:
self.__config_jog_velocity = self.config.get(
"jog_velocity", float, self.__config_velocity
)
if acceleration:
if self.controller.axis_settings.config_setting["acceleration"]:
if "acceleration" in self.settings.config_settings:
self.__config_acceleration = self.config.get("acceleration", float)
if limits:
self.__config_low_limit = self.config.get("low_limit", float, float("-inf"))
......@@ -786,13 +784,11 @@ class Axis:
self.__config_backlash = self.config.get("backlash", float, 0)
@property
@lazy_init
def steps_per_unit(self):
"""Current steps per unit (:obj:`float`)"""
return self.__steps_per_unit
@property
@lazy_init
def config_backlash(self):
"""Current backlash in user units (:obj:`float`)"""
return self.__config_backlash
......@@ -811,7 +807,6 @@ class Axis:
self.settings.set("backlash", backlash)
@property
@lazy_init
def tolerance(self):
"""Current Axis tolerance in dial units (:obj:`float`)"""
return self.__tolerance
......@@ -2056,7 +2051,7 @@ class Axis:
if any((velocity, acceleration, limits, sign, backlash)):
self.__config.save()
self.__init_config_properties(
self._init_config_properties(
velocity=velocity,
acceleration=acceleration,
limits=limits,
......@@ -2079,7 +2074,7 @@ class Axis:
if reload:
self.config.reload()
self.__init_config_properties(
self._init_config_properties(
velocity=velocity,
acceleration=acceleration,
limits=limits,
......@@ -2099,7 +2094,7 @@ class Axis:
if backlash:
self.settings.clear("backlash")
self.controller._init_settings(self)
self.settings.init()
# update position (needed for sign change)
pos = self.dial2user(self.dial)
......@@ -2458,5 +2453,5 @@ class ModuloAxis(Axis):
class NoSettingsAxis(Axis):
def __init__(self, *args, **kwags):
super().__init__(*args, **kwags)
for setting_name in self.settings:
for setting_name in self.settings.setting_names:
self.settings.disable_cache(setting_name)
......@@ -5,11 +5,16 @@
# Copyright (c) 2015-2020 Beamline Control Unit, ESRF
# Distributed under the GNU LGPLv3. See LICENSE for more info.
import collections
import functools
import inspect
import math
import sys
from bliss.common import event
from bliss.common.greenlet_utils import KillMask
from bliss.config.channels import Channel
from bliss.config import settings, settings_cache
import collections
import sys
def setting_update_from_channel(value, setting_name=None, axis=None):
......@@ -47,23 +52,24 @@ class ControllerAxisSettings:
self.config_setting = {}
# 'offset' must be set BEFORE limits to ensure good dial/user conversion.
self.add("offset", float)
self.add("sign", int)
self.add("offset", float)
self.add("backlash", float)
self.add("low_limit", floatOrNone)
self.add("high_limit", floatOrNone)
self.add("velocity", float, config=True)
self.add("jog_velocity", float)
self.add("acceleration", float, config=True)
self.add("low_limit", floatOrNone)
self.add("high_limit", floatOrNone)
self.add("dial_position", float)
self.add("_set_position", float)
self.add("position", float)
self.add("state", stateSetting, persistent=False)
self.add("steps_per_unit", float, config=True)
@property
def config_settings(self):
return tuple(
[setting for setting, config in self.config_setting.items() if config]
setting for setting, config in self.config_setting.items() if config
)
def add(self, setting_name, convert_func=str, persistent=True, config=False):
......@@ -83,6 +89,7 @@ class AxisSettings:
def __init__(self, axis):
self.__axis = axis
self.__state = None
self._beacon_channels = {}
self._disabled_settings = disabled_settings_namedtuple(
set(), dict(axis.config.config_dict)
)
......@@ -95,26 +102,145 @@ class AxisSettings:
# Activate prefetch
cnx_cache.add_prefetch(self._hash)
def __iter__(self):
for name in self.__axis.controller.axis_settings.setting_names:
yield name
@property
def setting_names(self):
yield from self.__axis.controller.axis_settings.setting_names
def convert_func(self, setting_name):
return self.__axis.controller.axis_settings.convert_func.get(setting_name)
@property
def config_settings(self):
return self.__axis.controller.axis_settings.config_settings()
return self.__axis.controller.axis_settings.config_settings
def register_channels_callbacks(self):
for chan in self._beacon_channels.values():
chan.register_callback(chan._setting_update_cb)
def unregister_channels_callbacks(self):
for chan in self._beacon_channels.values():
chan.unregister_callback(chan._setting_update_cb)
def _create_channel(self, setting_name):
chan_name = "axis.%s.%s" % (self.__axis.name, setting_name)
chan = Channel(chan_name)
cb = functools.partial(
setting_update_from_channel, setting_name=setting_name, axis=self.__axis
)
chan._setting_update_cb = cb
return chan
def init_channels(self):
self._beacon_channels.clear()
for setting_name in self.__axis.controller.axis_settings.setting_names:
self._beacon_channels[setting_name] = self._create_channel(setting_name)
self.register_channels_callbacks()
def _get_setting_or_config_value(self, name, default=None):
# return setting or config parameter
converter = self.convert_func(name)
value = self.get(name)
if value is None:
value = self.__axis.config.get(name, converter, default=default)
return value
def check_config_settings(self):
axis = self.__axis
props = dict(
inspect.getmembers(axis.__class__, lambda o: isinstance(o, property))
)
config_settings = []
for setting_name in self.config_settings:
# check if setting is in config
value = axis.config.get(setting_name)
if value is None:
raise RuntimeError(
"Axis %s: missing configuration key '%s`"
% (self.name, setting_name)
)
if setting_name == "steps_per_unit":
# steps_per_unit is read-only
continue
config_settings.append(setting_name)
# check if setting has a method to initialize (set) its value,
# without actually executing the property
try:
assert callable(props[setting_name].fset)
except AssertionError:
raise RuntimeError(
"Axis %s: missing method '%s` to set setting value"
% (axis.name, setting_name)
)
return config_settings
def init(self):
""" Initialize settings
"config settings" are those that **must** be in YML config like
steps per unit, acceleration and velocity ; otherwise settings
can optionally be present in the config file.
Config settings must have a property setter on the Axis object.
"persistent settings" are stored in redis, like position for example;
in any case, when a setting is set its value is emitted via a
dedicated channel.
Some settings can be both config+persistent (like velocity) or
none (like state, which is only emitted when it changes, but not stored
at all)
"""
axis = self.__axis
config_settings = self.check_config_settings()
config_steps_per_unit = axis.config.get("steps_per_unit", float)
if axis.no_offset:
sign = 1
offset = 0
else:
sign = self._get_setting_or_config_value("sign", 1)
offset = self._get_setting_or_config_value("offset", 0)
self.set("sign", sign)
self.set("offset", offset)
self.set("backlash", self._get_setting_or_config_value("backlash", 0))
low_limit_dial = self._get_setting_or_config_value("low_limit")
high_limit_dial = self._get_setting_or_config_value("high_limit")
if config_steps_per_unit:
cval = config_steps_per_unit
rval = self._hash.raw_get("steps_per_unit")
# Record steps_per_unit
if rval is None:
self.set("steps_per_unit", cval)
else:
rval = float(rval)
if cval != rval:
ratio = rval / cval
new_dial = axis.dial * ratio
self.set("steps_per_unit", cval)
if not axis.no_offset:
# calculate offset so user pos stays the same
self.set("offset", axis.position - axis.sign * new_dial)
self.set("dial_position", new_dial)
if math.copysign(rval, cval) != rval:
ll, hl = low_limit_dial, high_limit_dial
low_limit_dial, high_limit_dial = -hl, -ll
self.set("low_limit", low_limit_dial)
self.set("high_limit", high_limit_dial)
for setting_name in config_settings:
value = self._get_setting_or_config_value(setting_name)
setattr(axis, setting_name, value)
def set(self, setting_name, value):
if setting_name == "state":
if self.__state == value:
return
self.__state = value
"""
* set setting
* send event
* write
"""
axis = self.__axis
axis_settings = axis.controller.axis_settings
if setting_name not in axis_settings.setting_names:
......@@ -135,9 +261,9 @@ class AxisSettings:
else:
if axis_settings.persistent_setting[setting_name]:
with KillMask():
axis.settings._hash[setting_name] = value
self._hash[setting_name] = value
axis._beacon_channels[setting_name].value = value
self._beacon_channels[setting_name].value = value
event.send(axis, "internal_" + setting_name, value)
try:
......@@ -151,45 +277,42 @@ class AxisSettings:
disabled_settings = self._disabled_settings
if setting_name not in axis_settings.setting_names:
raise ValueError(
"No setting '%s` for axis '%s`" % (setting_name, axis.name)
)
raise NameError("No setting '%s` for axis '%s`" % (setting_name, axis.name))
if setting_name in disabled_settings.names:
return disabled_settings.config_dict.get(setting_name)
if axis_settings.persistent_setting[setting_name]:
with KillMask():
value = self._hash.get(setting_name)
else:
if axis_settings.persistent_setting[setting_name]:
with KillMask():
value = axis.settings._hash.get(setting_name)
chan = self._beacon_channels.get(setting_name)
if chan:
value = chan.value
else:
chan = axis._beacon_channels.get(setting_name)
if chan:
value = chan.value
else:
value = None
if value is not None:
convert_func = self.convert_func(setting_name)
if convert_func is not None:
value = convert_func(value)
return value
value = None
if value is not None:
convert_func = self.convert_func(setting_name)
if convert_func is not None:
value = convert_func(value)
return value
def clear(self, setting_name):
axis = self.__axis
axis_settings = axis.controller.axis_settings
disabled_settings = self._disabled_settings
if setting_name in disabled_settings.names:
disabled_settings.config_dict[setting_name] = None
else:
axis.settings._hash[setting_name] = None
self._hash[setting_name] = None
# reset beacon channel, if it is there
try:
channel = axis._beacon_channels[setting_name]
channel = self._beacon_channels[setting_name]
except KeyError:
pass
else:
channel.value = channel.default_value
channel.close()
self._beacon_channels[setting_name] = self._create_channel(setting_name)
def disable_cache(self, setting_name, flag=True):
"""
......
......@@ -7,14 +7,8 @@
import math
import numpy
import inspect
import functools
from bliss.common.motor_config import StaticConfig
from bliss.common.motor_settings import (
ControllerAxisSettings,
setting_update_from_channel,
floatOrNone,
)
from bliss.common.motor_settings import ControllerAxisSettings, floatOrNone
from bliss.common.axis import Trajectory
from bliss.common.motor_group import Group, TrajectoryGroup
from bliss.common import event
......@@ -26,18 +20,6 @@ from bliss.config import settings
from gevent import lock
# apply settings or config parameters
def get_setting_or_config_value(axis, name):
converter = axis.settings.convert_func(name)
value = axis.settings.get(name)
if value is None:
try:
value = axis.config.get(name, converter)
except Exception:
return None
return value
class Controller:
"""
Motor controller base class
......@@ -171,10 +153,10 @@ class Controller:
def _initialize_axis(self, axis, *args, **kwargs):
"""
"""
if self.__initialized_axis[axis]:
return
with self.__lock:
if self.__initialized_axis[axis]:
return
# Initialize controller hardware only once.
if not self.__initialized_hw.value:
self.initialize_hardware()
......@@ -193,90 +175,15 @@ class Controller:
axis_initialized = self.__initialized_hw_axis[axis]
if not axis_initialized.value:
self.initialize_hardware_axis(axis)
axis.settings.check_config_settings()
axis.settings.init() # get settings, from config or from cache, and apply to hardware
axis_initialized.value = 1
self._init_settings(axis)
except BaseException:
# Failed to initialize
self.__initialized_axis[axis] = False
raise
def _init_settings(self, axis):
""" Initialize hardware with settings
"""
props = dict(
inspect.getmembers(axis.__class__, lambda o: isinstance(o, property))
)
sign = get_setting_or_config_value(axis, "sign")
if sign is None or axis.no_offset:
sign = 1
axis.settings.set("sign", sign)
offset = get_setting_or_config_value(axis, "offset")
if offset is None or axis.no_offset:
offset = 0
axis.settings.set("offset", offset)
backlash = get_setting_or_config_value(axis, "backlash")
if backlash is None:
backlash = 0
axis.backlash = backlash
low_limit_dial = get_setting_or_config_value(axis, "low_limit")
high_limit_dial = get_setting_or_config_value(axis, "high_limit")
axis.dial_limits = low_limit_dial, high_limit_dial
for setting_name in axis.settings.config_settings():
# check if setting is in config
if axis.config.get(setting_name) is None:
raise RuntimeError(
"Axis %s: missing configuration key '%s`"
% (axis.name, setting_name)
)
# check if setting has a method to initialize (set) its value,
# without actually executing the property
try:
props[setting_name].fset
except AttributeError:
raise RuntimeError(
"Axis %s: missing method '%s` to set setting value"
% (axis.name, setting_name)
)
for setting_name in axis.settings.config_settings():
if setting_name == "steps_per_unit":
cval = float(axis.config.get(setting_name))
rval = axis.settings._hash.raw_get(setting_name)
# Record steps_per_unit
if rval is None:
axis.settings.set(setting_name, cval)
continue
else:
rval = float(rval)
if cval != rval:
ratio = rval / cval
new_dial = axis.dial * ratio
axis.settings.set("steps_per_unit", cval)
if not axis.no_offset:
# calculate offset so user pos stays the same
axis.settings.set(
"offset", axis.position - axis.sign * new_dial
)
else:
axis.settings.set("offset", 0)
axis.settings.set("dial_position", new_dial)
if math.copysign(rval, cval) != rval:
ll = axis.settings.get("low_limit")
hl = axis.settings.get("high_limit")
axis.settings.set("low_limit", -hl)
axis.settings.set("high_limit", -ll)
else:
value = get_setting_or_config_value(axis, setting_name)
setattr(axis, setting_name, value)
def get_axis(self, axis_name):
axis = self._axes.get(axis_name)
if axis is None: # create it
......@@ -303,23 +210,13 @@ class Controller:
# reference axis
return
axis._beacon_channels.clear()
for setting_name in axis.settings:
setting_value = get_setting_or_config_value(axis, setting_name)
chan_name = "axis.%s.%s" % (axis.name, setting_name)
cb = functools.partial(
setting_update_from_channel, setting_name=setting_name, axis=axis
)
chan = Channel(chan_name, default_value=setting_value, callback=cb)
chan._setting_update_cb = cb
axis._beacon_channels[setting_name] = chan
if axis.controller is self:
axis_initialized = Cache(axis, "initialized", default_value=0)
self.__initialized_hw_axis[axis] = axis_initialized
self.__initialized_axis[axis] = False
self._add_axis(axis)
return axis
def _add_axis(self, axis):
......
......@@ -1442,7 +1442,7 @@ def __create_tango_axis_class(axis):
% axis.name
)
for setting_name in axis.settings:
for setting_name in axis.settings.setting_names:
if setting_name in [
"velocity",
"position",
......@@ -1456,7 +1456,7 @@ def __create_tango_axis_class(axis):
]:
elog.debug(" BlissAxisManager.py -- std SETTING %s " % (setting_name))
else:
_setting_type = axis.controller.axis_settings.convert_func[setting_name]
_setting_type = axis.settings.convert_func(setting_name)
_attr_type = types_conv_tab[_setting_type]
elog.debug(
" BlissAxisManager.py -- adds SETTING %s as %s attribute"
......
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