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
31029613
Commit
31029613
authored
Apr 08, 2021
by
Perceval Guillou
Browse files
bliss controller plugin and subitem management
parent
2fab0745
Changes
3
Hide whitespace changes
Inline
Side-by-side
bliss/config/plugins/bliss_controller.py
View file @
31029613
...
...
@@ -6,26 +6,103 @@
# Distributed under the GNU LGPLv3. See LICENSE for more info.
from
re
import
subn
from
bliss.config.plugins.utils
import
find_class_and_node
from
bliss.config.static
import
ConfigNode
,
ConfigReference
# from bliss.config.static import ConfigNode, ConfigReference
def
create_objects_from_config_node
(
config
,
cfg_node
):
klass
,
node
=
find_class_and_node
(
cfg_node
)
item_name
=
cfg_node
[
"name"
]
if
node
.
get
(
"name"
)
!=
item_name
:
cfg_node
=
ConfigNode
.
indexed_nodes
[
item_name
]
else
:
cfg_node
=
node
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 ($)
"""
o
=
klass
(
item_name
,
cfg_node
.
clone
())
if
selection
is
None
:
selection
=
{}
for
key
,
value
in
cfg_node
.
items
():
if
isinstance
(
cfg_node
.
raw_get
(
key
),
ConfigReference
):
if
hasattr
(
o
,
key
):
continue
else
:
setattr
(
o
,
key
,
value
)
if
selection
.
get
(
level
)
is
None
:
selection
[
level
]
=
[]
return
{
item_name
:
o
}
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
.
items
():
if
isinstance
(
v
,
dict
):
find_sub_names_config
(
v
,
selection
,
level
+
1
,
k
)
elif
isinstance
(
v
,
list
):
for
i
in
v
:
if
isinstance
(
i
,
dict
):
find_sub_names_config
(
i
,
selection
,
level
+
1
,
k
)
return
selection
def
create_objects_from_config_node
(
cfg_obj
,
cfg_node
):
"""
Create an object from the config with a given name (unique).
It ensures that the controller and sub-objects are only created once.
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.
args:
cfg_obj: a Config object (from config.static)
cfg_node: a ConfigNode object (from config.static)
yield:
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
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"
)
item_name
=
cfg_node
[
"name"
]
# name of the item that should be created and returned
# always create the bliss controller first
bctrl
=
klass
(
ctrl_name
,
ctrl_node
.
clone
())
# find all sub objects with a name in controller config
sub_cfgs
=
find_sub_names_config
(
ctrl_node
.
to_dict
())
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
# --- 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
return
bctrl
.
_create_sub_item
(
name
,
cfg
,
pkey
)
bliss/controllers/bliss_controller.py
View file @
31029613
...
...
@@ -13,10 +13,16 @@ from gevent import Timeout, event, sleep as gsleep
from
bliss
import
global_map
from
bliss.common.protocols
import
CounterContainer
from
bliss.common.counter
import
Counter
,
CalcCounter
,
SamplingCounter
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
,
...
...
@@ -29,6 +35,77 @@ from bliss.config.beacon_object import BeaconObject
from
bliss.common.logtools
import
log_info
,
log_debug
,
log_debug_data
,
log_warning
# ============ 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
class
HardwareController
:
def
__init__
(
self
,
config
):
self
.
_config
=
config
...
...
@@ -78,7 +155,7 @@ class HardwareController:
class
BlissController
(
CounterContainer
):
COUNTER_TAGS
=
{}
_
COUNTER_TAGS
=
{}
def
__init__
(
self
,
name
,
config
):
...
...
@@ -92,10 +169,13 @@ class BlissController(CounterContainer):
self
.
_build_axes
()
self
.
_build_counters
()
print
(
"=== Create BlissController"
)
@
autocomplete_property
def
h
w_controller
(
self
):
def
h
ardware
(
self
):
if
self
.
_hw_controller
is
None
:
self
.
_hw_controller
=
self
.
_get_hardware
()
print
(
"=== _get_hardware"
,
self
.
_hw_controller
)
return
self
.
_hw_controller
@
property
...
...
@@ -107,10 +187,34 @@ class BlissController(CounterContainer):
return
self
.
_config
# ========== NOT IMPLEMENTED METHODS ====================
def
_get_hardware
(
self
):
""" Must return an HardwareController object """
raise
NotImplementedError
def
_create_sub_item
(
self
,
name
,
cfg
,
parent_key
):
""" Create/get and return an object which has a config name and which is owned by this controller
This method is called by the Bliss Controller Plugin and is called after the controller __init__().
This method is called only once per item on the first config.get('item_name') call (see plugin).
args:
'name': sub item name
'cfg' : sub item config
'parent_key': the config key under which the sub item was found (ex: 'counters').
return: the sub item object
"""
# === Example ===
# if parent_key == 'counters': #and name in self.counters._fields
# return self.counters[name]
# elif parent_key == 'axes': # and name in self.axes._fields
# return self.axes[name]
raise
NotImplementedError
def
_load_config
(
self
):
""" Read and apply the YML configuration """
raise
NotImplementedError
...
...
@@ -123,20 +227,19 @@ class BlissController(CounterContainer):
""" Build the Axes (real and pseudo) """
raise
NotImplementedError
@
property
@
autocomplete_
property
def
counters
(
self
):
# cnts = [ctrl.counters for ctrl in self._counter_controllers.values()]
# return counter_namespace(chain(*cnts))
raise
NotImplementedError
@
property
@
autocomplete_
property
def
axes
(
self
):
# axes = [ctrl.axes for ctrl in self._axis_controllers]
# return dict(ChainMap(*axes))
raise
NotImplementedError
# ========== MOCKUP CLASSES ==============================
...
...
@@ -166,22 +269,46 @@ class HCMockup(HardwareController):
class
BCMockup
(
BlissController
):
COUNTER_TAGS
=
{
_
COUNTER_TAGS
=
{
"current_temperature"
:
(
"cur_temp_ch1"
,
"scc"
),
"integration_time"
:
(
"int_time"
,
"icc"
),
}
def
_create_sub_item
(
self
,
name
,
cfg
,
parent_key
):
""" Create/get and return an object which has a config name and which is owned by this controller
This method is called by the Bliss Controller Plugin and is called after the controller __init__().
This method is called only once per item on the first config.get('item_name') call (see plugin).
args:
'name': sub item name
'cfg' : sub item config
'parent_key': the config key under which the sub item was found (ex: 'counters').
return: the sub item object
"""
if
parent_key
==
"counters"
:
return
self
.
counters
[
name
]
elif
parent_key
==
"axes"
:
return
self
.
axes
[
name
]
# return self._motor_controller.get_axis(name)
def
_get_hardware
(
self
):
""" Must return an HardwareController object """
return
HCMockup
(
self
.
config
[
"com"
])
def
_load_config
(
self
):
""" Read and apply the YML configuration """
print
(
"load config"
,
self
.
config
)
# print("load config", self.config)
if
self
.
config
.
get
(
"energy"
):
self
.
energy
=
self
.
config
.
get
(
"energy"
)
def
_build_counters
(
self
):
""" Build the CounterControllers and associated Counters"""
self
.
_counter_controllers
[
"scc"
]
=
BCSCC
(
"scc"
,
self
)
self
.
_counter_controllers
[
"icc"
]
=
BCICC
(
"icc"
,
self
)
self
.
_counter_controllers
[
"scc"
].
max_sampling_frequency
=
self
.
config
.
get
(
"max_sampling_frequency"
,
1
)
...
...
@@ -193,29 +320,43 @@ class BCMockup(BlissController):
unit
=
cfg
.
get
(
"unit"
)
convfunc
=
cfg
.
get
(
"convfunc"
)
if
self
.
COUNTER_TAGS
[
tag
][
1
]
==
"scc"
:
if
self
.
_
COUNTER_TAGS
[
tag
][
1
]
==
"scc"
:
cnt
=
self
.
_counter_controllers
[
"scc"
].
create_counter
(
SamplingCounter
,
name
,
unit
=
unit
,
mode
=
mode
)
cnt
.
tag
=
tag
elif
self
.
_COUNTER_TAGS
[
tag
][
1
]
==
"icc"
:
cnt
=
self
.
_counter_controllers
[
"icc"
].
create_counter
(
IntegratingCounter
,
name
,
unit
=
unit
)
cnt
.
tag
=
tag
def
_build_axes
(
self
):
""" Build the Axes (real and pseudo) """
# raise NotImplementedError
pass
@
property
axes_cfg
=
{
cfg
[
"name"
]:
(
MockupAxis
,
cfg
)
for
cfg
in
self
.
config
.
get
(
"axes"
,
[])
}
self
.
_motor_controller
=
Mockup
(
"motmock"
,
{},
axes_cfg
,
[],
[],
[]
)
# self.config
# === ??? initialize all now ???
for
name
in
axes_cfg
.
keys
():
self
.
_motor_controller
.
get_axis
(
name
)
@
autocomplete_property
def
counters
(
self
):
cnts
=
[
ctrl
.
counters
for
ctrl
in
self
.
_counter_controllers
.
values
()]
return
counter_namespace
(
chain
(
*
cnts
))
@
property
@
autocomplete_
property
def
axes
(
self
):
# axes = [ctrl.axes for ctrl in self._axis_controllers]
# return dict(ChainMap(*axes))
# raise NotImplementedError
return
counter_namespace
({})
return
counter_namespace
(
self
.
_motor_controller
.
axes
)
class
BCSCC
(
SamplingCounterController
):
...
...
@@ -226,9 +367,9 @@ class BCSCC(SamplingCounterController):
def
read_all
(
self
,
*
counters
):
values
=
[]
for
cnt
in
counters
:
tag_info
=
self
.
bctrl
.
COUNTER_TAGS
.
get
(
cnt
.
tag
)
tag_info
=
self
.
bctrl
.
_
COUNTER_TAGS
.
get
(
cnt
.
tag
)
if
tag_info
:
values
.
append
(
self
.
bctrl
.
h
w_controller
.
send_cmd
(
tag_info
[
0
]))
values
.
append
(
self
.
bctrl
.
h
ardware
.
send_cmd
(
tag_info
[
0
]))
else
:
# returned number of data must be equal to the length of '*counters'
# so raiseError if one of the received counter is not handled
...
...
@@ -236,74 +377,54 @@ class BCSCC(SamplingCounterController):
return
values
# class BCICC(IntegratingCounterController):
# def __init__(self, name, bctrl):
# super().__init__(name)
# self.bctrl = bctrl
# def get_values(self, from_index, *counters):
# class BCICC(CounterController):
# def __init__(self, name, bctrl):
# super().__init__(name)
# self.bctrl = bctrl
class
BCICC
(
CounterController
):
def
__init__
(
self
,
name
,
bctrl
):
super
().
__init__
(
name
)
self
.
bctrl
=
bctrl
self
.
count_time
=
None
#
def get_acquisition_object(self, acq_params, ctrl_params, parent_acq_params):
#
return BCIAS(self, ctrl_params=ctrl_params, **acq_params)
def
get_acquisition_object
(
self
,
acq_params
,
ctrl_params
,
parent_acq_params
):
return
BCIAS
(
self
,
ctrl_params
=
ctrl_params
,
**
acq_params
)
#
def get_default_chain_parameters(self, scan_params, acq_params):
def
get_default_chain_parameters
(
self
,
scan_params
,
acq_params
):
#
try:
#
count_time = acq_params["count_time"]
#
except KeyError:
#
count_time = scan_params["count_time"]
try
:
count_time
=
acq_params
[
"count_time"
]
except
KeyError
:
count_time
=
scan_params
[
"count_time"
]
#
try:
#
npoints = acq_params["npoints"]
#
except KeyError:
#
npoints = scan_params["npoints"]
try
:
npoints
=
acq_params
[
"npoints"
]
except
KeyError
:
npoints
=
scan_params
[
"npoints"
]
#
params = {"count_time": count_time, "npoints": npoints}
params
=
{
"count_time"
:
count_time
,
"npoints"
:
npoints
}
#
return params
return
params
# def read_counts(self):
# """ returns status, nremain, ntotal, time, counts """
# return self.bctrl.hw_controller.send_cmd()
def
read_data
(
self
):
gsleep
(
self
.
count_time
)
#
class BCIAS(BaseCounterAcquisitionSlave):
#
def __init__(self, *args, **kwargs):
#
super().__init__(*args, **kwargs)
#
self._reading_event = event.Event()
class
BCIAS
(
BaseCounterAcquisitionSlave
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
super
().
__init__
(
*
args
,
**
kwargs
)
self
.
_reading_event
=
event
.
Event
()
#
def prepare(self):
#
pass
def
prepare
(
self
):
self
.
device
.
count_time
=
self
.
count_time
# def start(self):
# self._stop_flag = False
# self._reading_event.clear()
def
start
(
self
):
pass
# def stop(self):
# self._stop_flag = True
# self._reading_event.set()
# if not self.device.counter_is_ready:
# self.device.counting_stop()
def
stop
(
self
):
pass
# def trigger(self):
# self.device.counting_start(self.count_time)
# gsleep(self.count_time)
# self._reading_event.set()
def
trigger
(
self
):
pass
# def reading(self):
# self._reading_event.wait()
# self._reading_event.clear()
# with Timeout(2.0):
# while not self._stop_flag:
# status, nremain, ntotal, ctime, counts = self.device.read_counts()
# if status == "D":
# self._emit_new_data([[counts]])
# break
# else:
# gevent.sleep(0.001)
def
reading
(
self
):
t0
=
perf_counter
()
self
.
device
.
read_data
()
dt
=
perf_counter
()
-
t0
self
.
_emit_new_data
([[
dt
]])
tests/test_configuration/bliss_controllers.yml
View file @
31029613
...
...
@@ -7,7 +7,8 @@
tcp
:
url
:
bcmock
energy
:
$robz
counters
:
-
name
:
bctemp
tag
:
current_temperature
...
...
@@ -33,4 +34,8 @@
-
name
:
axis2
tag
:
zgap
steps_per_unit
:
10000
velocity
:
2500.0
acceleration
:
1000.0
low_limit
:
-180
high_limit
:
180
\ No newline at end of file
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment