Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Bliss
bliss
Commits
bd75ef7c
Commit
bd75ef7c
authored
Apr 15, 2021
by
Perceval Guillou
Browse files
Motor controller as BlissController
parent
24b008a1
Changes
33
Hide whitespace changes
Inline
Side-by-side
bliss/common/auto_filter/filterset.py
View file @
bd75ef7c
...
...
@@ -610,7 +610,7 @@ class FilterSet:
def
get_filters
(
self
):
"""
Return the list of the public filters, a list of diction
n
ary items with at least:
Return the list of the public filters, a list of dictionary items with at least:
- position
- density_calc
- transmission_calc
...
...
bliss/common/auto_filter/filterset_wago.py
View file @
bd75ef7c
...
...
@@ -241,7 +241,7 @@ class FilterSet_Wago(FilterSet):
def
get_filters
(
self
):
"""
Return the list of the public filters, a list of diction
n
ary items with at least:
Return the list of the public filters, a list of dictionary items with at least:
- position
- transmission_calc
"""
...
...
bliss/common/auto_filter/filterset_wheel.py
View file @
bd75ef7c
...
...
@@ -206,7 +206,7 @@ class FilterSet_Wheel(FilterSet):
def
get_filters
(
self
):
"""
Return the list of the public filters, a list of diction
n
ary items with at least:
Return the list of the public filters, a list of dictionary items with at least:
- position
- transmission_calc
For the wheel filterset _filters = _config_filters
...
...
bliss/common/soft_axis.py
View file @
bd75ef7c
...
...
@@ -23,19 +23,7 @@ def SoftAxis(
unit
=
None
,
):
# if callable(position):
# position = position.__name__
# if callable(move):
# move = move.__name__
# if callable(stop):
# stop = stop.__name__
config
=
{
"low_limit"
:
low_limit
,
"high_limit"
:
high_limit
,
"limits"
:
(
low_limit
,
high_limit
),
"name"
:
name
,
}
config
=
{
"low_limit"
:
low_limit
,
"high_limit"
:
high_limit
,
"name"
:
name
}
if
tolerance
is
not
None
:
config
[
"tolerance"
]
=
tolerance
...
...
@@ -44,8 +32,8 @@ def SoftAxis(
config
[
"unit"
]
=
unit
controller
=
SoftController
(
name
,
obj
,
config
,
position
,
move
,
stop
,
state
)
controller
.
_controller_init
()
controller
.
_init
()
axis
=
controller
.
get_axis
(
name
)
axis
.
_positioner
=
False
...
...
bliss/config/plugins/bliss_controller.py
View file @
bd75ef7c
...
...
@@ -5,62 +5,8 @@
# Copyright (c) 2015-2020 Beamline Control Unit, ESRF
# Distributed under the GNU LGPLv3. See LICENSE for more info.
# ================ IMPORTANT NOTE_ ABOUT PLUGIN CYCLIC IMPORT =============
#
# The plugin prevent cyclic import thanks to the yield name2cacheditems tricks
# in create_objects_from_config_node() below.
#
# however the best way to avoid this problem would be to NOT allow references ($name) of
# a bliss_controller item within another item of the same bliss_controller.
# In other words, in a bliss_controller config, only references to external objects should be allowed.
# (external = not owned by the bliss controller itself)
# If a BC item needs to reference another item of the same BC, then just using the item name (without '$')
# should be enough, as the BC knows its items and associated names.
from
bliss.config.plugins.utils
import
find_class_and_node
from
bliss.config.static
import
ConfigNode
,
ConfigList
def
find_sub_names_config
(
config
,
selection
=
None
,
level
=
0
,
parent_key
=
None
,
exclude_ref
=
True
):
""" Search in a config the sub-sections where the key 'name' is found.
Returns a dict of tuples (sub_config, parent_key) indexed by level (0 is the top level).
sub_config: a sub-config containing 'name' key
parent_key: key under which the sub_config was found (None for level 0)
exclude_ref: if True, exclude sub-config with name as reference ($)
"""
if
selection
is
None
:
selection
=
{}
if
selection
.
get
(
level
)
is
None
:
selection
[
level
]
=
[]
if
config
.
get
(
"name"
):
if
not
exclude_ref
or
not
config
.
get
(
"name"
).
startswith
(
"$"
):
selection
[
level
].
append
((
config
,
parent_key
))
for
(
k
,
v
,
)
in
(
config
.
raw_items
()
):
# !!! raw_items to avoid cyclic import while resloving reference !!!
if
isinstance
(
v
,
ConfigNode
):
find_sub_names_config
(
v
,
selection
,
level
+
1
,
k
)
elif
isinstance
(
v
,
ConfigList
):
for
i
in
v
:
if
isinstance
(
i
,
ConfigNode
):
find_sub_names_config
(
i
,
selection
,
level
+
1
,
k
)
return
selection
from
bliss.config.plugins.utils
import
find_top_class_and_node
from
bliss.controllers.bliss_controller
import
BlissController
def
create_objects_from_config_node
(
cfg_obj
,
cfg_node
):
...
...
@@ -71,62 +17,65 @@ def create_objects_from_config_node(cfg_obj, cfg_node):
This function resolves dependencies between the BlissController and its sub-objects with a name.
It looks for the 'class' key in 'cfg_node' (or at upper levels) to instantiate the BlissController.
All sub-configs of named sub-objects are stored as cached items for later instantiation via config.get.
All sub-configs of named sub-objects
owned by the controller
are stored as cached items for later instantiation via config.get.
args:
cfg_obj: a Config object (from config.static)
cfg_node: a ConfigNode object (from config.static)
yield:
tuple: (
created_items, cached_items)
tuple: (created_items, cached_items)
"""
print
(
"
\n
===== BLISS CONTROLLER PLUGIN FROM CONFIG: "
,
cfg_node
[
"name"
])
name2items
=
{}
name2cacheditems
=
{}
# search the 'class' key in cfg_node or at a upper node level
# return the class and the associated config node
# upper_node = cfg_node.parent ??
klass
,
ctrl_node
=
find_class_and_node
(
cfg_node
)
# print("=== FOUND BLISS CONTROLLER CLASS", klass, "WITH NODE", ctrl_node)
ctrl_name
=
ctrl_node
.
get
(
"name"
)
# then return the class and the associated config node
klass
,
ctrl_node
=
find_top_class_and_node
(
cfg_node
)
ctrl_name
=
ctrl_node
.
get
(
"name"
)
# ctrl could have a name in config
item_name
=
cfg_node
[
"name"
]
# name of the item that should be created and returned
# always create the bliss controller first
bctrl
=
klass
(
ctrl_node
.
clone
())
# find all sub objects with a name in controller config
sub_cfgs
=
find_sub_names_config
(
ctrl_node
)
# .to_dict(resolve_references=False))
for
level
in
sorted
(
sub_cfgs
.
keys
()):
if
level
!=
0
:
# ignore the controller itself
for
cfg
,
pkey
in
sub_cfgs
[
level
]:
subname
=
cfg
[
"name"
]
# if subname == item_name: # this is the sub-object to return
# name2items[item_name] = bctrl._create_sub_item(item_name, cfg, pkey)
# else: # store sub-object info for later instantiation
name2cacheditems
[
subname
]
=
(
bctrl
,
cfg
,
pkey
)
# --- add the controller to stored items if it has a name
if
ctrl_name
:
name2items
[
ctrl_name
]
=
bctrl
# update the config cache dict NOW to avoid cyclic instanciation (i.e. config.get => create_object_from_... => config.get )
yield
name2items
,
name2cacheditems
# --- don't forget to instanciate the object for which this function has been called (if not a controller)
if
item_name
!=
ctrl_name
:
obj
=
cfg_obj
.
get
(
item_name
)
yield
{
item_name
:
obj
}
# --- NOW, any new object_name going through 'config.get( obj_name )' should call 'create_object_from_cache' only.
# --- 'create_objects_from_config_node' should never be called again for any object related to the controller instanciated here (see config.get code)
def
create_object_from_cache
(
config
,
name
,
cached_object_info
):
print
(
"===== REGULATION FROM CACHE"
,
name
)
# config, name, object_info)
bctrl
,
cfg
,
pkey
=
cached_object_info
new_object
=
bctrl
.
_create_sub_item
(
name
,
cfg
,
pkey
)
return
new_object
bctrl
=
klass
(
ctrl_node
)
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
}
print
(
f
"
\n
=== Caching:
{
names_to_cache
}
from
{
bctrl
.
name
}
"
)
# # --- add the controller to stored 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
# load config and init controller
bctrl
.
_controller_init
()
# --- don't forget to instanciate the object for which this function has been called (if not a controller)
if
item_name
!=
ctrl_name
:
obj
=
cfg_obj
.
get
(
item_name
)
yield
{
item_name
:
obj
}
# --- Now any new object_name going through 'config.get( obj_name )' should call 'create_object_from_cache' only.
# --- 'create_objects_from_config_node' should never be called again for any object related to the controller instanciated here (see config.get code)
elif
(
item_name
==
ctrl_name
):
# allow instantiation of top object which is not a BlissController
yield
{
ctrl_name
:
bctrl
}
return
else
:
# prevent instantiation of an item comming from a top controller which is not a BlissController
raise
TypeError
(
f
"
{
bctrl
}
is not a BlissController object!"
)
def
create_object_from_cache
(
config
,
name
,
bctrl
):
print
(
f
"
\n
=== From cache:
{
name
}
from
{
bctrl
.
name
}
"
)
return
bctrl
.
_get_subitem
(
name
)
bliss/config/plugins/diffractometer.py
View file @
bd75ef7c
...
...
@@ -68,9 +68,9 @@ def get_axes_info(diffracto):
def
create_hkl_motors
(
diffracto
,
axes_info
):
hklmots
=
HKLMotors
(
f
"
{
diffracto
.
name
}
_motors"
,
diffracto
,
diffracto
.
config
,
axes_info
)
config
=
diffracto
.
config
.
clone
()
config
[
"name"
]
=
f
"
{
diffracto
.
name
}
_motors"
hklmots
=
HKLMotors
(
diffracto
,
diffracto
.
config
,
axes_info
)
# --- force axis init before CalcController._init (see emotion)
for
axname
in
axes_info
:
hklmots
.
get_axis
(
axname
)
...
...
bliss/config/plugins/utils.py
View file @
bd75ef7c
...
...
@@ -5,12 +5,13 @@
# Copyright (c) 2015-2020 Beamline Control Unit, ESRF
# Distributed under the GNU LGPLv3. See LICENSE for more info.
from
os
import
error
import
re
from
importlib.util
import
find_spec
from
importlib
import
import_module
# ---- Alias defined for default BLISS controllers
_ALIAS_TO_MODULE_NAME
=
{
"Lima"
:
"bliss.controllers.lima.lima_base"
}
"""Alias defined for default BLISS controllers"""
def
camel_case_to_snake_style
(
name
):
...
...
@@ -68,3 +69,72 @@ def find_class_and_node(cfg_node, base_path="bliss.controllers"):
klass
=
getattr
(
module
,
class_name
.
title
())
return
klass
,
node
def
find_top_class_and_node
(
cfg_node
,
base_paths
=
None
):
if
base_paths
is
None
:
base_paths
=
[
"bliss.controllers"
,
"bliss.controllers.motors"
]
node
=
cfg_node
.
get_top_key_node
(
"class"
)
class_name
=
node
[
"class"
]
candidates
=
set
()
errors
=
[]
for
base_path
in
base_paths
:
module
=
None
# --- Find module and try import -----------
module_name
=
resolve_module_name
(
class_name
,
node
,
base_path
)
try
:
module
=
import_module
(
module_name
)
except
ModuleNotFoundError
as
e
:
errors
.
append
(
e
)
module_name
=
"%s.%s"
%
(
base_path
,
camel_case_to_snake_style
(
class_name
))
try
:
module
=
import_module
(
module_name
)
except
ModuleNotFoundError
as
e2
:
errors
.
append
(
e2
)
if
module
is
not
None
:
# --- Find class and try import -----------
if
hasattr
(
module
,
class_name
):
klass
=
getattr
(
module
,
class_name
)
candidates
.
add
((
module
,
klass
))
else
:
kname
=
class_name
.
title
()
if
kname
!=
class_name
:
if
hasattr
(
module
,
kname
):
klass
=
getattr
(
module
,
kname
)
candidates
.
add
((
module
,
klass
))
else
:
errors
.
append
(
f
"cannot find
{
class_name
}
in
{
module
}
"
)
# --- return if a single candidate was found else raise error
if
len
(
candidates
)
==
1
:
return
candidates
.
pop
()[
1
],
node
elif
len
(
candidates
)
>
1
:
for
mod
,
klass
in
candidates
:
if
"package"
in
node
:
if
node
[
"package"
]
==
mod
.
__name__
:
return
klass
,
node
elif
"module"
in
node
:
if
node
[
"module"
]
in
mod
.
__name__
:
return
klass
,
node
msg
=
f
"Multiple candidates found for class '
{
class_name
}
':"
for
mod
,
_
in
candidates
:
msg
+=
f
"
\n
-
{
mod
}
"
msg
+=
"
\n
Resolve by providing the 'module' key in yml config
\n
"
raise
ModuleNotFoundError
(
msg
)
else
:
msg
=
f
"Config could not find
{
class_name
}
:"
for
err
in
errors
:
msg
+=
f
"
\n
{
err
}
"
msg
+=
(
f
"
\n
Check that module is located under one of these modules:
{
base_paths
}
"
)
msg
+=
"
\n
Else, resolve by providing the 'package' key in yml config
\n
"
raise
ModuleNotFoundError
(
msg
)
bliss/config/static.py
View file @
bd75ef7c
...
...
@@ -462,6 +462,17 @@ class ConfigNode(MutableMapping):
return
self
.
_parent
.
is_service
return
through_server
def
get_top_key_node
(
self
,
key
):
topnode
=
None
node
=
self
while
True
:
if
node
.
get
(
key
):
topnode
=
node
node
=
node
.
_parent
if
node
is
None
or
"__children__"
in
node
.
keys
():
break
return
topnode
def
get_inherited_value_and_node
(
self
,
key
):
"""
@see get_inherited
...
...
bliss/controllers/bliss_controller.py
View file @
bd75ef7c
...
...
@@ -5,175 +5,184 @@
# Copyright (c) 2015-2020 Beamline Control Unit, ESRF
# Distributed under the GNU LGPLv3. See LICENSE for more info.
from
time
import
perf_counter
,
sleep
from
itertools
import
chain
from
gevent
import
event
,
sleep
as
gsleep
from
bliss
import
global_map
from
importlib
import
import_module
from
bliss.common.protocols
import
CounterContainer
from
bliss.common.counter
import
(
Counter
,
CalcCounter
,
SamplingCounter
,
IntegratingCounter
,
)
from
bliss.common.protocols
import
counter_namespace
from
bliss.common.utils
import
autocomplete_property
from
bliss.comm.util
import
get_comm
from
bliss.controllers.motors.mockup
import
Mockup
,
MockupAxis
from
bliss.controllers.counter
import
(
CounterController
,
SamplingCounterController
,
IntegratingCounterController
,
)
from
bliss.scanning.acquisition.counter
import
BaseCounterAcquisitionSlave
from
bliss.config.static
import
ConfigReference
,
ConfigNode
,
ConfigList
# from bliss.config.beacon_object import BeaconObject
from
bliss.common.logtools
import
log_info
,
log_debug
,
log_debug_data
,
log_warning
def
find_sub_names_config
(
config
,
selection
=
None
,
level
=
0
,
parent_key
=
None
):
""" Recursively search in a config the sub-sections where the key 'name' is found.
Returns a dict of tuples (sub_config, parent_key) indexed by level (0 is the top level).
- sub_config: the sub-config containing the 'name' key
- parent_key: key under which the sub_config was found (None for level 0)
# ============ Note about BlissController ==============
#
## --- BlissController ---
# The BlissController base class is designed for the implementation of all controllers in Bliss.
# It ensures that all controllers have the following properties:
#
# class BlissController:
# @name (can be None if only sub-items are named)
# @config (yml config)
# @hardware (associated hardware controller object, can be None if no hardware)
# @counters (associated counters)
# @axes (associated axes: real/calc/soft/pseudo)
#
# Nothing else from the base class methods will be exposed at the first level object API.
#
# The BlissController is designed to ease the management of sub-objects that depend on a common device (@hardware).
# The sub-objects are declared in the yml configuration of the bliss controller under dedicated sub-sections.
#
# A sub-object is considered as a sub-item if it has a name (key 'name' in a sub-section of the config).
# Most of the time sub-items are counters and/or axes but could be anything else (known by the custom bliss controller).
#
# The BlissController has 2 properties (@counters, @axes) to retrieve sub-items that can be identified
# as counters (Counter) or axes (Axis).
#
## --- Plugin ---
# BlissController objects are created from the yml config using the bliss_controller plugin.
# Any sub-item with a name can be imported in a Bliss session with config.get('name').
# The plugin ensures that the controller and sub-items are only created once.
# The bliss controller itself can have a name (optional) and can be imported in the session.
# The plugin resolves dependencies between the BlissController and its sub-items.
# It looks for the 'class' key in the config to instantiate the BlissController.
# While importing any sub-item in the session, the bliss controller is instantiated first (if not alive already).
#
# !!! The effective creation of the sub-items is performed by the BlissController itself and the plugin just ensures
# that the controller is always created before sub-items and only once, that's all !!!
# The sub-items can be created during the initialization of the BlissController or via
# BlissController._create_sub_item(itemname, itemcfg, parentkey) which is called only on the first config.get('itemname')
#
## --- yml config ---
#
# - plugin: bliss_controller <== use the dedicated bliss controller plugin
# module: custom_module <== module of the custom bliss controller
# class: BCMockup <== class of the custom bliss controller
# name: bcmock <== name of the custom bliss controller (optional)
#
# com: <== communication config for associated hardware (optional)
# tcp:
# url: bcmock
#
# custom_param_1: value <== a parameter for the custom bliss controller creation (optional)
# custom_param_2: value <== another parameter for the custom bliss controller creation (optional)
#
# sub-section-1: <== a sub-section where sub-items can be declared (optional) (ex: 'counters')
# - name: sub_item_1 <== config of the sub-item
# tag : item_tag_1 <== a tag for this item (known and interpreted by the custom bliss controller)
# sub_param_1: value <== a custom parameter for the item creation
#
# sub-section-2: <== a sub-section where sub-items can be declared (optional) (ex: 'axes')
# - name: sub_item_2 <== config of the sub-item
# tag : item_tag_2 <== a tag for this item (known and interpreted by the custom bliss controller)
#
# sub-section-2-1: <== nested sub-sections are possible (optional)
# - name: sub_item_21
# tag : item_tag_21
#
# sub-section-3 : <== a third sub-section without sub-items (no 'name' key) (optional)
# - anything_but_name: foo <== something interpreted by the custom bliss controller
# something: value
args:
config: the config that should be explored
selection: a list containing the info of the subnames already found (for recursion)
level: an integer describing at which level the subname was found (level=0 is the top/upper level) (for recursion)
parent_key: key under which the sub_config was found (None for level 0) (for recursion)
"""
assert
isinstance
(
config
,
(
ConfigNode
,
dict
))
class
HardwareController
:
def
__init__
(
self
,
config
):
self
.
_config
=
config
self
.
_last_cmd_time
=
perf_counter
()
self
.
_cmd_min_delta_time
=
0
if
selection
is
None
:
selection
=
{}
self
.
_init_com
()
if
selection
.
get
(
level
)
is
None
:
selection
[
level
]
=
[]
@
property
def
config
(
self
):
return
self
.
_config
if
isinstance
(
config
,
ConfigNode
):
name
=
config
.
raw_get
(
"name"
)
else
:
name
=
config
.
get
(
"name"
)
@
property
def
comm
(
self
):
return
self
.
_comm
def
send_cmd
(
self
,
cmd
,
*
values
):
now
=
perf_counter
()
log_info
(
self
,
f
"@
{
now
:
.
3
f
}
send_cmd"
,
cmd
,
values
)
if
self
.
_cmd_min_delta_time
:
delta_t
=
now
-
self
.
_last_cmd_time
if
delta_t
<
self
.
_cmd_min_delta_time
:
sleep
(
self
.
_cmd_min_delta_time
-
delta_t
)
return
self
.
_send_cmd
(
cmd
,
*
values
)
def
_send_cmd
(
self
,
cmd
,
*
values
):
if
values
:
return
self
.
_write_cmd
(
cmd
,
*
values
)
else
:
return
self
.
_read_cmd
(
cmd
)
if
name
is
not
None
:
selection
[
level
].
append
((
config
,
parent_key
))
def
_init_com
(
self
):
log_info
(
self
,
"_init_com"
,
self
.
config
)
self
.
_comm
=
get_comm
(
self
.
config
)
global_map
.
register
(
self
.
_comm
,
parents_list
=
[
self
,
"comms"
])
if
isinstance
(
config
,
ConfigNode
):
cfg_items
=
(
config
.
raw_items
()
)
# !!! raw_items to avoid cyclic import while resloving reference !!!
else
:
cfg_items
=
config
.
items
()
# ========== NOT IMPLEMENTED METHODS ====================
def
_write_cmd
(
self
,
cmd
,
*
values
):
# return self._comm.write(cmd, *values)
raise
NotImplementedError
for
k
,
v
in
cfg_items
:
if
isinstance
(
v
,
(
ConfigNode
,
dict
)):
find_sub_names_config
(
v
,
selection
,
level
+
1
,
k
)
def
_read_cmd
(
self
,
cmd
):
# return self._comm.read(cmd)
raise
NotImplementedError
elif
isinstance
(
v
,
(
ConfigList
,
list
)):
for
i
in
v
:
if
isinstance
(
i
,
(
ConfigNode
,
dict
)):
find_sub_names_config
(
i
,
selection
,
level
+
1
,
k
)
return
selection
class
BlissController
(
CounterContainer
):
_COUNTER_TAGS
=
{}
def
from_config_dict
(
ctrl_class
,
cfg_dict
):
""" Helper to instanciate a BlissController object from a configuration dictionary """
if
not
BlissController
in
ctrl_class
.
mro
():
raise
TypeError
(
f
"
{
ctrl_class
}
is not a BlissController class"
)
bctrl
=
ctrl_class
(
cfg_dict
)
bctrl
.
_controller_init
()
return
bctrl
class
BlissController
(
CounterContainer
):
"""
BlissController base class is made for the implementation of all Bliss controllers.
It is designed to ease the management of sub-objects that depend on a shared controller.
Sub-objects are declared in the yml configuration of the controller under dedicated sub-sections.
A sub-object is considered as a subitem if it has a name (key 'name' in a sub-section of the config).
Usually subitems are counters and axes but could be anything else (known by the controller).
The BlissController has properties @counters and @axes to retrieve subitems that can be identified
as counters or axes.
# --- Plugin ---
BlissController objects are created from the yml config using the bliss_controller plugin.
Any subitem with a name can be imported in a Bliss session with config.get('name').
The plugin ensures that the controller and subitems are only created once.
The bliss controller itself can have a name (optional) and can be imported in the session.
The plugin resolves dependencies between the BlissController and its subitems.
It looks for the top 'class' key in the config to instantiate the BlissController.
While importing any subitem in the session, the bliss controller is instantiated first (if not alive already).
The effective creation of the subitems is performed by the BlissController itself and the plugin just ensures
that the controller is always created before subitems and only once.
Example: config.get(bctrl_name) or config.get(item_name) with config = bliss.config.static.get_config()
# --- 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.
# --- From config dict ---
A BlissController can be instantiated directly (i.e. not via plugin) providing a config as a dictionary.
In that case, users must call the method 'self._controller_init()' just after the controller instantiation
to ensure that the controller is initialized in the same way as the plugin does.
The config dictionary should be structured like a YML file (i.e: nested dict and list) and
references replaced by their corresponding object instances.