Commit 71b473c5 authored by Perceval Guillou's avatar Perceval Guillou
Browse files

diffractometer as BlissController

parent bd75ef7c
......@@ -611,10 +611,7 @@ class ExternalOutput(Output):
self._controller = SCC(self.name, self)
self._controller.create_counter(
SamplingCounter,
self.name,
unit=self._config.get("unit"),
mode=self._config.get("mode", "SINGLE"),
SamplingCounter, self.name, unit=self._config.get("unit"), mode="SINGLE"
)
# ----------- BASE METHODS -----------------------------------------
......
......@@ -36,25 +36,24 @@ def create_objects_from_config_node(cfg_obj, cfg_node):
# always create the bliss controller first
bctrl = klass(ctrl_node)
print(f"\n=== From config: {item_name} from {bctrl.name}")
# print(f"\n=== From config: {item_name} from {bctrl.name}")
if isinstance(bctrl, BlissController):
# prepare subitems configs and cache item's controller
names_to_cache = bctrl._prepare_subitems_configs(ctrl_node)
cachednames2ctrl = {name: bctrl for name in names_to_cache}
# prepare subitems configs and cache item's controller.
# the controller decides which items should be cached and which controller
# is associated to the cached item (in case the cached item is owned by a sub-controller of this controller)
cacheditemnames2ctrl = bctrl._prepare_subitems_configs()
# print(f"\n=== Caching: {list(cacheditemnames2ctrl.keys())} from {bctrl.name}")
print(f"\n=== Caching: {names_to_cache} from {bctrl.name}")
# # --- add the controller to stored items if it has a name
# --- add the controller to registered items, if it has a name.
name2items = {}
if ctrl_name:
name2items[ctrl_name] = bctrl
# name2items[bctrl.name] = bctrl
# update the config cache dict now to avoid cyclic instanciation with internal references
# an internal reference happens when a subitem config uses a reference to another subitem owned by the same controller.
yield name2items, cachednames2ctrl
# an internal reference occurs when a subitem config uses a reference to another subitem owned by the same controller.
yield name2items, cacheditemnames2ctrl
# load config and init controller
bctrl._controller_init()
......@@ -77,5 +76,5 @@ def create_objects_from_config_node(cfg_obj, cfg_node):
def create_object_from_cache(config, name, bctrl):
print(f"\n=== From cache: {name} from {bctrl.name}")
# print(f"\n=== From cache: {name} from {bctrl.name}")
return bctrl._get_subitem(name)
......@@ -98,16 +98,24 @@ class BlissController(CounterContainer):
Example: config.get(bctrl_name) or config.get(item_name) with config = bliss.config.static.get_config()
# --- Items and sub-controllers ---
A controller (top) can have sub-controllers. In that case there are two ways to create the sub_controllers:
# --- Plugin limitations ----
Use references to declare subitems that also have subitems (i.e subitem of type bliss controller).
It is possible to build a bliss controller which have subitems of the type BlissController.
But in that case, the declaration of the subitems of the different bliss controllers cannot be
merged in the configuration of the top controller. Each bliss controller must be decalred separately
and one can reference this other in its config with '$name'. Using a reference to bliss_controllers
subitems will ensure that the plugin will associate the correct controller to subitems.
- The most simple way to do this is to declare the sub-controller as an independant object with its own yml config
and use a reference to this object into the top-controller config.
- If a sub-controller has no reason to exist independently from the top-controller, then the top-controller
will create and manage its sub-controllers from the knowledge of the top-controller config only.
In that case, some items declared in the top-controller are, in fact, managed by one of the sub-controllers.
In that case, the author of the top controller class must overload the '_get_item_owner' method and specify
which is the sub-controller that manages which items.
Example: Consider a top controller which manages a motors controller internally. The top controller config
declares the axes subitems but those items are in fact managed by the motors controller.
In that case, '_get_item_owner' should specify that the axes subitems are managed by 'self.motor_controller'
instead of 'self'. The method receives the item name and the parent_key. So 'self.motor_controller' can be
associated to all subitems under the 'axes' parent_key (instead of doing it for each subitem name).
# --- From config dict ---
......@@ -155,16 +163,14 @@ class BlissController(CounterContainer):
"""
def __init__(self, config):
self.__initialized = False
self.__subitems_configs_ready = False
self.__ctrl_is_initialized = False
self._subitems_config = {} # stores items info (cfg, pkey) (filled by self._prepare_subitems_configs)
self._subitems = {} # stores items instances (filled by self.__build_subitem_from_config)
self._hw_controller = (
None
) # acces the low level hardware controller interface (if any)
if isinstance(config, dict):
self._prepare_subitems_configs(config)
# generate generic name if no controller name found in config
self._name = config.get("name")
if self._name is None:
......@@ -173,8 +179,7 @@ class BlissController(CounterContainer):
else:
self._name = f"{self.__class__.__name__}_{id(self)}"
# config is a ConfigNode if this controller is imported from Config (i.e config.get(name))
# or config is a dict if direct instantiation of this controller (i.e bctrl = BlissController(cfg_dict))
# config can be a ConfigNode or a dict
self._config = config
# ========== STANDARD METHODS ============================
......@@ -203,7 +208,12 @@ class BlissController(CounterContainer):
- the controller, via self._get_subitem(item_name) => name is NOT exported in session
"""
print(f"=== Build item {name} from {self.name}")
# print(f"=== Build item {name} from {self.name}")
if not self.__ctrl_is_initialized:
raise RuntimeError(
f"{self} not initialized:\n call '{self.name}._controller_init()'"
)
if name not in self._subitems_config:
raise ValueError(f"Cannot find item with name: {name}")
......@@ -279,14 +289,23 @@ class BlissController(CounterContainer):
f"cannot find class {class_name} in {module}"
)
def _prepare_subitems_configs(self, ctrl_node):
def _get_item_owner(self, name, cfg, pkey):
""" Return the controller that owns the items declared in the config.
By default, this controller is the owner of all config items.
However if this controller has sub-controllers that are the real owners
of some items, this method should use to specify which sub-controller is
the owner of which item (identified with name and pkey).
"""
return self
def _prepare_subitems_configs(self):
""" Find all sub objects with a name in the controller config.
Store the items config info (cfg, pkey) in the controller (including referenced items).
Return the list of found items (excluding referenced items).
"""
items_list = []
sub_cfgs = find_sub_names_config(ctrl_node)
cacheditemnames2ctrl = {}
sub_cfgs = find_sub_names_config(self._config)
for level in sorted(sub_cfgs.keys()):
if level != 0: # ignore the controller itself
for cfg, pkey in sub_cfgs[level]:
......@@ -299,7 +318,9 @@ class BlissController(CounterContainer):
# only store in items_list the subitems with a name as a string
# because items_list is used by the plugin to cache subitem's controller.
# (i.e exclude referenced names as they are not owned by this controller)
items_list.append(name)
cacheditemnames2ctrl[name] = self._get_item_owner(
name, cfg, pkey
)
elif isinstance(name, ConfigReference):
name = name.object_name
else:
......@@ -307,7 +328,8 @@ class BlissController(CounterContainer):
self._subitems_config[name] = (cfg, pkey)
return items_list
self.__subitems_configs_ready = True
return cacheditemnames2ctrl
def _get_subitem(self, name):
""" return an item (create it if not alive) """
......@@ -320,9 +342,17 @@ class BlissController(CounterContainer):
This method must be called if the controller has been directly
instantiated with a config dictionary (i.e without going through the plugin and YML config).
"""
if not self.__initialized:
self._load_config()
self._init()
if not self.__ctrl_is_initialized:
if not self.__subitems_configs_ready:
self._prepare_subitems_configs()
self.__ctrl_is_initialized = True
try:
self._load_config()
self._init()
except BaseException:
self.__ctrl_is_initialized = False
raise
# ========== ABSTRACT METHODS ====================
......
......@@ -16,6 +16,9 @@ from bliss.common.cleanup import error_cleanup
from bliss.common.utils import autocomplete_property
from gevent import sleep
from bliss.controllers.bliss_controller import BlissController
from bliss.controllers.motors.hklmotors import HKLMotors
__CURR_DIFF = None
__ALL_DIFF = dict()
......@@ -81,7 +84,7 @@ def remove_diff_settings(name, *keys):
settings.remove(keyname)
class Diffractometer(object):
class Diffractometer(BlissController):
""" Diffractometer base class.
......@@ -126,17 +129,81 @@ class Diffractometer(object):
"psi_constant_horizontal",
]
def __init__(self, name, config):
self.name = name
self._config = config
def __init__(self, config):
super().__init__(config)
geometry = config.get("geometry")
if geometry is None:
raise ValueError(f"Missing geometry in config of Diffractometer '{name}'")
self._settings = HashObjSetting(name)
raise ValueError(
f"Missing geometry in config of Diffractometer '{self.name}'"
)
self._settings = HashObjSetting(self.name)
self._geometry = HklGeometry(geometry, self._settings)
self._motor_calc = None
self._motor_names = dict()
register_diffractometer(name, self)
register_diffractometer(self.name, self)
hklmot_cfg = self.__prepare_hklmotors_config()
self._motor_calc = HKLMotors(self, hklmot_cfg)
def __prepare_hklmotors_config(self):
""" Prepare axes config for the HKLMotors sub-controller.
Add pseudo-axes that are not declared in the config.
"""
real_names = list(self.axis_names)
pseudo_names = list(self.pseudo_names)
axes_cfgs = self._config.get("axes")
for cfg in axes_cfgs:
axis_name = cfg["name"]
axis_tag = cfg["tags"]
# check if axes tags are valid
if axis_tag.startswith("real"):
axis_calc_name = axis_tag.split()[1]
if axis_calc_name in real_names:
real_names.remove(axis_calc_name)
else:
if axis_calc_name != "energy":
raise KeyError(
f"{axis_calc_name} is not not a valid real axis tag"
)
else:
if axis_tag in pseudo_names:
pseudo_names.remove(axis_tag)
else:
raise KeyError(f"{axis_tag} is not a valid pseudo axis tag")
# check that all required reals have been found in config
if len(real_names):
raise KeyError(f"Missing real axis tags {real_names}")
# get remaining pseudo axis not declared in config
for axis_name in pseudo_names:
cfg = {"name": axis_name, "tags": axis_name}
axes_cfgs.append(cfg)
return {"axes": axes_cfgs}
def _get_item_owner(self, name, cfg, pkey):
""" Return the controller that owns the items declared in the config.
By default, this controller is the owner of all config items.
However if this controller has sub-controllers that are the real owners
of some items, this method should use to specify which sub-controller is
the owner of which item (identified with name and pkey).
"""
if pkey == "axes":
return self._motor_calc
else:
return self
def _load_config(self):
pass
def _init(self):
self._motor_calc._controller_init()
self.calc_controller = self._motor_calc
def __info__(self):
self._calc_geo()
......
......@@ -170,6 +170,10 @@ class Controller(BlissController):
self._disabled = True
raise
@property
def config(self):
return self.__motor_config
@property
def axes(self):
return self._axes
......@@ -202,14 +206,6 @@ class Controller(BlissController):
def get_switch(self, name):
return self._get_subitem(name)
# @property
# def motor_config(self):
# return self.__motor_config
@property
def config(self):
return self.__motor_config
def steps_position_precision(self, axis):
"""
Return a float value representing the precision of the position in steps
......@@ -549,6 +545,7 @@ class CalcController(Controller):
def _init(self):
# As any motors can be used into a calc
# force for all axis creation
for axis_name in self._axes_config.keys():
self.get_axis(axis_name)
......
......@@ -15,8 +15,8 @@ from bliss.common.utils import all_equal, object_method, grouped
class HKLMotors(CalcController):
def __init__(self, diffractometer, config, axes):
super().__init__(config, axes, [], [], [])
def __init__(self, diffractometer, config):
super().__init__(config)
self.diffracto = diffractometer
self._frozen_angles = dict()
......
......@@ -15,6 +15,8 @@ from bliss.config.plugins.bliss_controller import create_objects_from_config_nod
from bliss.config.plugins.bliss_controller import create_object_from_cache
from bliss.config.plugins.bliss_controller import find_top_class_and_node
# test if no class specified at controller level !
def test_plugin_get_items_from_config(default_session):
......
-
controller:
plugin: diffractometer
name: d4ch
diffractometers:
- name: d4ch
plugin: bliss_controller
module: diffractometers.diff_fourc
class: DiffE4CH
geometry: E4CH
axes:
axes:
- name: $roby
tags: real omega
- name: $robu
......@@ -13,7 +14,6 @@
tags: real phi
- name: $robz2
tags: real tth
- name: $mono
tags: real energy
......@@ -25,14 +25,15 @@
tags: hkl_l
- name: Q
tags: q_q
-
controller:
plugin: diffractometer
name: zaxis
- name: zaxis
plugin: bliss_controller
module: diffractometers.diff_zaxis
class: DiffZAXIS
geometry: ZAXIS
axes:
- name: $roby
tags: real mu
- name: $robu
......@@ -51,4 +52,4 @@
# - name: Qz
# tags: q2_q
# - name: Az
# tags: q2_alpha
# tags: q2_alpha
\ No newline at end of file
......@@ -5,9 +5,7 @@
# Copyright (c) 2016 Beamline Control Unit, ESRF
# Distributed under the GNU LGPLv3. See LICENSE for more info.
# from bliss.physics.hkl import geometry, sample
# import math
# import pytest
import numpy as np
# @pytest.fixture
......@@ -239,7 +237,106 @@ def test_zaxis(default_session):
hkl.hscan(1, 2, 10, 0.1, diode)
hkl.kscan(1, 2, 10, 0.1, diode)
hkl.lscan(1, 2, 10, 0.1, diode)
hkl.hklscan((0, 1, 0), (0, 1, 6), 10, 0.1, diode)
s = hkl.hklscan((0, 1, 0), (0, 1, 6), 10, 0.1, diode)
assert np.all(
np.isclose(
s.get_data("Hz"),
np.array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]),
rtol=1e-05,
atol=1e-05,
)
)
assert np.all(
np.isclose(
s.get_data("Kz"),
np.array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]),
rtol=1e-05,
atol=1e-05,
)
)
assert np.all(
np.isclose(
s.get_data("Lz"),
np.array(
[0.0, 0.6667, 1.3333, 2.0, 2.6667, 3.3333, 4.0, 4.6667, 5.3333, 6.0]
),
rtol=1e-05,
atol=1e-04,
)
)
assert np.all(
np.isclose(
s.get_data("roby"),
np.array([0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]),
rtol=1e-05,
atol=1e-05,
)
)
assert np.all(
np.isclose(
s.get_data("robu"),
np.array(
[
78.7755,
79.0414,
79.6139,
80.4951,
81.6877,
83.1955,
85.0235,
87.1792,
89.6728,
92.5196,
]
),
rtol=1e-05,
atol=1e-05,
)
)
assert np.all(
np.isclose(
s.get_data("robz"),
np.array(
[
6.7811,
6.7698,
6.7604,
6.7520,
6.7431,
6.7317,
6.7150,
6.6896,
6.6511,
6.5941,
]
),
rtol=1e-05,
atol=1e-05,
)
)
assert np.all(
np.isclose(
s.get_data("robz2"),
np.array(
[
-0.0422,
1.3927,
2.8285,
4.2660,
5.7063,
7.1502,
8.5986,
10.0527,
11.5132,
12.9815,
]
),
rtol=1e-05,
atol=1e-05,
)
)
hkl.hdscan(1, 2, 10, 0.1, diode)
hkl.kdscan(1, 2, 10, 0.1, diode)
......
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