Commit dabdd4dc authored by Sebastien Petitdemange's avatar Sebastien Petitdemange

Merge branch 'emotion_axis_improvements' into 'master'

Emotion plugin + Axis improvements

See merge request !1290
parents 0dd97487 d237ef43
......@@ -35,7 +35,6 @@ from bliss.common.motor_settings import AxisSettings
from bliss.common import event
from bliss.common.greenlet_utils import protect_from_one_kill
from bliss.common.utils import Null, with_custom_members
from bliss.config.static import get_config
from bliss.common.encoder import Encoder
from bliss.common.hook import MotionHook
from bliss.config.channels import Channel
......@@ -316,32 +315,6 @@ class GroupMove:
event.send(self.parent, "move_done", True)
def get_encoder(name):
cfg = get_config()
enc = cfg.get(name)
if not isinstance(enc, Encoder):
raise TypeError("%s is not an Encoder" % name)
return enc
def get_axis(name):
cfg = get_config()
axis = cfg.get(name)
if not isinstance(axis, Axis):
raise TypeError("%s is not an Axis" % name)
return axis
def get_motion_hook(name):
cfg = get_config()
if name.startswith("$"):
name = name[1:]
hook = cfg.get(name)
if not isinstance(hook, MotionHook):
raise TypeError("%s is not a MotionHook" % name)
return hook
class Modulo:
def __init__(self, mod=360):
self.modulo = mod
......@@ -582,18 +555,18 @@ class Axis(AliasMixin, LogMixin):
def __init__(self, name, controller, config):
self.__name = name
self.__controller = controller
self.__config = StaticConfig(config)
self.__settings = AxisSettings(self)
self.__move_done = gevent.event.Event()
self.__move_done_callback = gevent.event.Event()
self.__move_done.set()
self.__move_done_callback.set()
motion_hooks = []
for hook_ref in config.get("motion_hooks", ()):
hook = get_motion_hook(hook_ref)
self.__motion_hooks = []
for hook in config.get("motion_hooks", []):
hook = hook()
hook.add_axis(self)
motion_hooks.append(hook)
self.__motion_hooks = motion_hooks
self.__motion_hooks.append(hook)
self.__encoder = config.get("encoder")
self.__config = StaticConfig(config)
self._group_move = GroupMove()
self._beacon_channels = dict()
self._move_stop_channel = Channel(
......@@ -698,14 +671,13 @@ class Axis(AliasMixin, LogMixin):
Reference to :class:`~bliss.common.encoder.Encoder` or None if no
encoder is defined
"""
try:
encoder_name = self.config.get("encoder")
except KeyError:
return None
if isinstance(self.__encoder, Encoder):
return self.__encoder
else:
enc = get_encoder(encoder_name)
enc.controller._initialize_encoder(enc)
return enc
if self.__encoder:
self.__encoder = self.__encoder()
self.__encoder.controller._initialize_encoder(self.__encoder)
return self.__encoder
@property
def motion_hooks(self):
......@@ -1144,13 +1116,13 @@ class Axis(AliasMixin, LogMixin):
return (position - self.offset) / self.sign
def __execute_pre_move_hook(self, motion):
for hook in self.__motion_hooks:
for hook in self.motion_hooks:
hook.pre_move([motion])
self._check_ready()
def __execute_post_move_hook(self, motions):
for hook in self.__motion_hooks:
for hook in self.motion_hooks:
try:
hook.post_move(motions)
except:
......@@ -1591,25 +1563,6 @@ class Axis(AliasMixin, LogMixin):
return dial_positions / self.steps_per_unit
class AxisRef(object):
"""Object representing a named reference to an :class:`Axis`."""
def __init__(self, name, _, config):
self.__name = name
self.__config = config
self.settings = AxisSettings(None)
@property
def name(self):
"""Axis reference name"""
return self.__name
@property
def config(self):
"""Reference to the :class:`~bliss.common.motor_config.StaticConfig`"""
return self.__config
class AxisState(object):
"""
Standard states:
......@@ -1920,41 +1873,3 @@ class NoSettingsAxis(Axis):
Axis.__init__(self, *args, **kwags)
self.settings.get = mock.MagicMock(return_value=None)
self.settings.set = mock.MagicMock(return_value=None)
def SoftAxis(
name,
obj,
position="position",
move="position",
stop=None,
low_limit=float("-inf"),
high_limit=float("+inf"),
):
from bliss.controllers.motors.soft import SoftController
config = get_config()
if callable(position):
position = position.__name__
if callable(move):
move = move.__name__
if callable(stop):
stop = stop.__name__
controller = SoftController(
name,
obj,
{
"position": position,
"move": move,
"stop": stop,
"limits": (low_limit, high_limit),
"name": name,
},
)
controller._init()
axis = controller.get_axis(name)
config._name2instance[name] = axis
setattr(setup_globals, name, axis)
return axis
......@@ -27,12 +27,10 @@ class StaticConfig(object):
if not "axes" in config_dict and not "encoders" in config_dict:
# axis config
self.config_channel = channels.Channel(
config_chan_name,
config_dict.to_dict(),
callback=self._config_changed,
config_chan_name, config_dict, callback=self._config_changed
)
def get(self, property_name, converter=str, default=NO_VALUE, inherited=False):
def get(self, property_name, converter=str, default=NO_VALUE):
"""Get static property
Args:
......@@ -47,8 +45,7 @@ class StaticConfig(object):
Raises:
KeyError, ValueError
"""
get_method = "get_inherited" if inherited else "get"
property_value = getattr(self.config_dict, get_method)(property_name)
property_value = self.config_dict.get(property_name)
if property_value is not None:
return converter(property_value)
else:
......@@ -58,10 +55,15 @@ class StaticConfig(object):
raise KeyError("no property '%s` in config" % property_name)
def set(self, property_name, value):
self.config_dict[property_name] = value
cfg = get_config()
config_node = cfg.get_config(self.config_dict["name"])
config_node[property_name] = value
self.config_dict = config_node.to_dict()
def save(self):
self.config_dict.save()
cfg = get_config()
config_node = cfg.get_config(self.config_dict["name"])
config_node.save()
self._update_channel()
def reload(self):
......@@ -71,14 +73,18 @@ class StaticConfig(object):
# we could selectively reload only parts of the config (e.g one
# single object yml file)
cfg.reload()
self.config_dict = cfg.get_config(self.config_dict["name"])
config_node = cfg.get_config(self.config_dict["name"])
self.config_dict = config_node.to_dict()
self._update_channel()
def _update_channel(self):
if self.config_channel is not None:
# inform all clients that config has changed
self.config_channel.value = dict(self.config_dict)
self.config_channel.value = self.config_dict
def _config_changed(self, config_dict):
cfg = get_config()
config_node = cfg.get_config(self.config_dict["name"])
for key, value in config_dict.items():
config_node[key] = value
self.config_dict[key] = value
# -*- coding: utf-8 -*-
#
# This file is part of the bliss project
#
# Copyright (c) 2016 Beamline Control Unit, ESRF
# Distributed under the GNU LGPLv3. See LICENSE for more info.
from bliss.controllers.motors.soft import SoftController
from bliss.common import session
from bliss.common import mapping
from bliss import setup_globals
def SoftAxis(
name,
obj,
position="position",
move="position",
stop=None,
low_limit=float("-inf"),
high_limit=float("+inf"),
):
config = session.get_current().config
if callable(position):
position = position.__name__
if callable(move):
move = move.__name__
if callable(stop):
stop = stop.__name__
controller = SoftController(
name,
obj,
{
"position": position,
"move": move,
"stop": stop,
"limits": (low_limit, high_limit),
"name": name,
},
)
controller._init()
axis = controller.get_axis(name)
mapping.register(axis, parents_list=[controller], tag=f"axis.{name}")
setattr(setup_globals, name, axis)
return axis
......@@ -12,7 +12,7 @@ Standard bliss macros (:func:`~bliss.common.standard.wa`, \
from bliss.common import scans
from bliss.common.scans import *
from bliss.common.plot import plot
from bliss.common.axis import SoftAxis
from bliss.common.soft_axis import SoftAxis
from bliss.common.measurement import SoftCounter
from bliss.common.cleanup import cleanup, error_cleanup
from bliss.common import logtools
......
......@@ -9,14 +9,13 @@
import os
import sys
import pkgutil
import weakref
from bliss.common import axis as axis_module
from bliss.common.axis import Axis, AxisRef
from bliss.common.axis import Axis
from bliss.common.encoder import Encoder
from bliss.config.static import Config, get_config
from bliss.common.tango import DeviceProxy
from bliss.config.plugins.utils import find_class
from bliss.config.plugins.utils import find_class, replace_reference_by_object
import bliss.controllers.motors
import gevent
......@@ -314,6 +313,21 @@ def add_axis(cfg, request):
)
class Reference:
def __init__(self, name, *args, **kwargs):
self.__name = name.lstrip("$")
@property
def name(self):
return self.__name
def __call__(self, *args, **kwargs):
return get_config().get(self.name)
def __str__(self):
return f"${self.name}"
def create_objects_from_config_node(config, node):
if "axes" in node or "encoders" in node:
# asking for a controller
......@@ -341,43 +355,45 @@ def create_objects_from_config_node(config, node):
switches_names = list()
shutters = list()
shutters_names = list()
for axis_config in node.get("axes"):
axis_name = axis_config.get("name")
if axis_name.startswith("$"):
axis_class = AxisRef
axis_name = axis_name.lstrip("$")
else:
axis_class_name = axis_config.get("class")
if axis_class_name is None:
axis_class = Axis
else:
try:
axis_class = getattr(axis_module, axis_class_name)
except AttributeError:
axis_class = getattr(controller_module, axis_class_name)
axes_names.append(axis_name)
axes.append((axis_name, axis_class, axis_config))
for objects, objects_names, default_class, default_class_name, objects_config in (
node = node.to_dict()
for (
objects,
objects_names,
default_class,
default_class_name,
config_nodes_list,
) in (
(axes, axes_names, Axis, "", node.get("axes", [])),
(encoders, encoders_names, Encoder, "", node.get("encoders", [])),
(shutters, shutters_names, None, "Shutter", node.get("shutters", [])),
(switches, switches_names, None, "Switch", node.get("switches", [])),
):
for object_config in objects_config:
object_name = object_config.get("name")
object_class_name = object_config.get("class")
object_config = _checkref(config, object_config)
if object_class_name is None:
object_class = default_class
if object_class is None:
for config_dict in config_nodes_list:
replace_reference_by_object(config, config_dict, placeholder=Reference)
if not isinstance(config_dict.get("name"), str):
# reference
object_class = config_dict.get("name")
object_name = object_class.name
else:
object_name = config_dict.get("name")
object_class_name = config_dict.get("class")
if object_class_name is None:
object_class = default_class
if object_class is None:
try:
object_class = getattr(
controller_module, default_class_name
)
except AttributeError:
pass
else:
try:
object_class = getattr(controller_module, default_class_name)
object_class = getattr(controller_module, object_class_name)
except AttributeError:
pass
else:
object_class = getattr(controller_module, object_class_name)
object_class = getattr(axis_module, object_class_name)
objects_names.append(object_name)
objects.append((object_name, object_class, object_config))
objects.append((object_name, object_class, config_dict))
controller = controller_class(
controller_name, node, axes, encoders, shutters, switches
......@@ -406,13 +422,3 @@ def create_object_from_cache(config, name, controller):
except KeyError:
pass
raise KeyError(name)
def _checkref(config, cfg):
obj_cfg = cfg.deep_copy()
for key, value in obj_cfg.items():
if isinstance(value, str) and value.startswith("$"):
# convert reference to item from config
obj = weakref.proxy(config.get(value))
obj_cfg[key] = obj
return obj_cfg
......@@ -32,10 +32,14 @@ def find_class_and_node(cfg_node, base_path="bliss.controllers"):
return klass, node
def _checkref(config, item_cfg_node, referenced_objects, name, value):
def _checkref(config, item_cfg_node, referenced_objects, name, value, placeholder):
if isinstance(value, str) and value.startswith("$"):
# convert reference to item from config
obj = config.get(value)
value = value.lstrip("$")
if placeholder:
obj = placeholder(value)
else:
obj = config.get(value)
item_cfg_node[name] = obj
referenced_objects[name] = obj
return True
......@@ -43,39 +47,43 @@ def _checkref(config, item_cfg_node, referenced_objects, name, value):
return False
def _parse_dict(config, item_cfg_node, referenced_objects, subdict):
for name, node in subdict.items():
if _checkref(config, subdict, referenced_objects, name, node):
def _parse_dict(config, item_cfg_node, referenced_objects, subdict, placeholder):
for name, value in subdict.items():
if _checkref(config, subdict, referenced_objects, name, value, placeholder):
continue
elif isinstance(node, dict):
elif isinstance(value, dict):
childdict = dict()
childref = dict()
_parse_dict(config, childdict, childref, node)
_parse_dict(config, childdict, childref, value, placeholder)
if childref:
node.update(childref)
referenced_objects[name] = node
value.update(childref)
referenced_objects[name] = value
subdict.update(childdict)
elif isinstance(node, list):
return_list = _parse_list(config, node)
elif isinstance(value, list):
return_list = _parse_list(config, value, placeholder)
if return_list:
referenced_objects[name] = return_list
item_cfg_node[name] = return_list
def _parse_list(config, value):
def _parse_list(config, value, placeholder):
object_list = list()
for node in value:
if isinstance(node, str) and node.startswith("$"):
object_list.append(config.get(node))
node = node.lstrip("$")
if placeholder:
object_list.append(placeholder(node))
else:
object_list.append(config.get(node))
elif isinstance(node, dict):
subdict = dict()
subref = dict()
_parse_dict(config, subdict, subref, node)
_parse_dict(config, subdict, subref, node, placeholder)
if subdict:
node.update(subdict)
object_list.append(node)
elif isinstance(node, list):
return_list = _parse_list(config, node)
return_list = _parse_list(config, node, placeholder)
if return_list:
object_list.append(return_list)
else:
......@@ -83,21 +91,25 @@ def _parse_list(config, value):
return object_list
def replace_reference_by_object(config, item_cfg_node, ref_objects=None):
def replace_reference_by_object(
config, item_cfg_node, ref_objects=None, placeholder=None
):
referenced_objects = ref_objects if ref_objects is not None else dict()
for name, value in item_cfg_node.items():
if _checkref(config, item_cfg_node, referenced_objects, name, value):
if _checkref(
config, item_cfg_node, referenced_objects, name, value, placeholder
):
continue
if isinstance(value, list):
return_list = _parse_list(config, value)
return_list = _parse_list(config, value, placeholder)
if return_list:
referenced_objects[name] = return_list
item_cfg_node[name] = return_list
elif isinstance(value, dict):
subdict = dict()
subref = dict()
_parse_dict(config, subdict, subref, value)
_parse_dict(config, subdict, subref, value, placeholder)
if subref:
referenced_objects[name] = subref
item_cfg_node.update(subdict)
......@@ -550,10 +550,7 @@ class Config:
def _create_file_index(self, node, filename):
if filename:
self._node2file[node] = filename
weak_set = self._file2node.get(filename)
if weak_set is None:
weak_set = weakref.WeakSet()
self._file2node[filename] = weak_set
weak_set = self._file2node.setdefault(filename, weakref.WeakSet())
weak_set.add(node)
def _get_or_create_path_node(self, base_path):
......
......@@ -14,7 +14,7 @@ from bliss.common.motor_settings import (
setting_update_from_channel,
floatOrNone,
)
from bliss.common.axis import Axis, NoSettingsAxis, AxisRef, Trajectory
from bliss.common.axis import Axis, NoSettingsAxis, Trajectory
from bliss.common.motor_group import Group, TrajectoryGroup
from bliss.common import event
from bliss.physics import trajectory
......@@ -22,12 +22,11 @@ from bliss.common.utils import set_custom_members, object_method
from bliss.common import mapping
from bliss.common.logtools import LogMixin
from bliss.config.channels import Cache, Channel
from bliss.config import static, settings
from bliss.config import settings
from gevent import lock
# make the link between encoder and axis, if axis uses an encoder
# (only 1 encoder per axis of course)
ENCODER_AXIS = dict()
# apply settings or config parameters
def get_setting_or_config_value(axis, name):
......@@ -73,10 +72,8 @@ class Controller(LogMixin):
for tag in axis_tags.split():
self._tagged.setdefault(tag, []).append(axis)
# For custom attributes and commands.
# NB : AxisRef has no controller.
if not isinstance(axis, AxisRef):
set_custom_members(self, axis, axis.controller._initialize_axis)
if axis.controller is self:
set_custom_members(self, axis, self._initialize_axis)
for encoder_name, encoder_class, encoder_config in encoders:
encoder = encoder_class(encoder_name, self, encoder_config)
......@@ -93,13 +90,6 @@ class Controller(LogMixin):
mapping.register(self)
def _init(self):
controller_axes = [
(axis_name, axis)
for axis_name, axis in self.axes.items()
if not isinstance(axis, AxisRef)
]
self._update_refs()
for axis in self.axes.values():
axis._beacon_channels.clear()
hash_setting = settings.HashSetting("axis.%s" % axis.name)
......@@ -123,14 +113,12 @@ class Controller(LogMixin):
self.initialize()
for axis_name, axis in controller_axes:
for axis_name, axis in self.axes.items():
if axis.controller is not self:
continue
axis_initialized = Cache(axis, "initialized", default_value=0)
self.__initialized_hw_axis[axis] = axis_initialized
self.__initialized_axis[axis] = False
encoder = axis.config.get("encoder", str, "")
if encoder:
encoder_name = encoder.lstrip("$")
ENCODER_AXIS[encoder_name] = axis.name
@property
def axes(self):
......@@ -162,20 +150,6 @@ class Controller(LogMixin):
def config(self):
return self.__config
def _update_refs(self):
config = static.get_config()
for tag, axis_list in self._tagged.items():
for i, axis in enumerate(axis_list):
if not isinstance(axis, AxisRef):
continue
referenced_axis = config.get(axis.name)
if not isinstance(referenced_axis, Axis):
raise TypeError(
"%s: invalid axis '%s`, not an Axis" % (self.name, axis.name)
)
self.axes[axis.name] = referenced_axis
axis_list[i] = referenced_axis
def _check_limits(self, axis, user_positions):
min_pos = user_positions.min()
max_pos = user_positions.max()
......
......@@ -10,9 +10,10 @@ import random
import gevent
from bliss.physics.trajectory import LinearTrajectory
from bliss.controllers.motor import Controller, ENCODER_AXIS, CalcController
from bliss.common.axis import Axis, AxisState, get_axis
from bliss.controllers.motor import Controller, CalcController
from bliss.common.axis import Axis, AxisState
from bliss.common import event
from bliss.config.static import get_config
from bliss.common.hook import MotionHook
from bliss.common.utils import object_method
......@@ -45,6 +46,8 @@ class Motion:
class Mockup(Controller):
ENCODER_AXIS = dict()
def __init__(self, *args, **kwargs):
Controller.__init__(self, *args, **kwargs)
......@@ -83,6 +86,9 @@ class Mockup(Controller):
def initialize(self):
for axis_name, axis in self.axes.items():
axis.settings.set("init_count", 0)
encoder_name = axis.config.get("encoder", str, "").lstrip("$")
if encoder_name:
self.ENCODER_AXIS[encoder_name] = axis_name
"""
Axes initialization actions.
......@@ -106,11 +112,11 @@ class Mockup(Controller):
def initialize_encoder(self, encoder):
self.__encoders.setdefault(encoder, {})["measured_noise"] = None
self.__encoders[encoder]["steps"] = None
axis_name = ENCODER_AXIS[encoder.name]
self.__encoders[encoder]["axis"] = axis_name
axis = get_axis(axis_name)
if not axis in self._axis_moves:
self.initialize_axis(axis)
axis_name = self.ENCODER_AXIS.get(encoder.name)
if axis_name:
self.__encoders[encoder]["axis"] = axis_name
axis = get_config().get(axis_name)
axis.controller._initialize_axis(axis)
"""
Actions to perform at controller closing.
......@@ -201,7 +207,7 @@ class Mockup(Controller):
enc_steps = self.__encoders[encoder]["steps"]
else:
axis_name = self.__encoders[encoder]["axis"]
axis = get_axis(axis_name)
axis = get_config().get(axis_name)
_pos = self.read_position(axis) / float(axis.steps_per_unit)
......
......@@ -21,11 +21,11 @@ import gevent
from gevent.backdoor import BackdoorServer
import bliss.common.log as elog
from bliss.common.axis import get_axis
from bliss.common import event
from bliss.common.utils import grouped
from bliss.config.static import get_config as beacon_get_config
from bliss.common.motor_group import Group
from bliss.common.axis import Axis
import tango
from tango.server import Device, device_property
......@@ -42,6 +42,14 @@ except:
pass
def get_axis(name):
cfg = beacon_get_config()
axis = cfg.get(name)
if not isinstance(axis, Axis):
raise TypeError(f"{name} is not an Axis")
return axis
class bcolors:
PINK = "\033[95m"
BLUE = "\033[94m"
......
</
"""
Unittest for FlexDC controller in bliss library.
"""
import unittest
import sys
import os
import time
sys.path.insert(
0,
os.path.abspath(
os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir)