Skip to content
GitLab
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
Expand all
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
This diff is collapsed.
Click to expand it.
bliss/controllers/bliss_controller_mockup.py
0 → 100644
View file @
bd75ef7c
# -*- coding: utf-8 -*-
#
# This file is part of the bliss project
#
# 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
collections
import
ChainMap
from
gevent
import
event
,
sleep
as
gsleep
from
bliss
import
global_map
from
bliss.common.counter
import
(
SamplingCounter
)
# make it available at ctrl level for plugin and tests
from
bliss.common.protocols
import
counter_namespace
,
IterableNamespace
from
bliss.common.utils
import
autocomplete_property
from
bliss.comm.util
import
get_comm
from
bliss.controllers.counter
import
CounterController
,
SamplingCounterController
from
bliss.scanning.acquisition.counter
import
BaseCounterAcquisitionSlave
from
bliss.common.logtools
import
log_info
,
log_debug
,
log_debug_data
,
log_warning
from
bliss.controllers.bliss_controller
import
BlissController
,
from_config_dict
from
bliss.controllers.motors.mockup
import
Mockup
,
calc_motor_mockup
class
HardwareController
:
def
__init__
(
self
,
config
):
self
.
_config
=
config
self
.
_last_cmd_time
=
perf_counter
()
self
.
_cmd_min_delta_time
=
0
self
.
_init_com
()
@
property
def
config
(
self
):
return
self
.
_config
@
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
)
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"
])
# ========== NOT IMPLEMENTED METHODS ====================
def
_write_cmd
(
self
,
cmd
,
*
values
):
# return self._comm.write(cmd, *values)
raise
NotImplementedError
def
_read_cmd
(
self
,
cmd
):
# return self._comm.read(cmd)
raise
NotImplementedError
class
HCMockup
(
HardwareController
):
class
FakeCom
:
def
__init__
(
self
,
config
):
pass
def
read
(
self
,
cmd
):
return
69
def
write
(
self
,
cmd
,
*
values
):
print
(
"HCMockup write"
,
cmd
,
values
)
return
True
def
_init_com
(
self
):
log_info
(
self
,
"_init_com"
,
self
.
config
)
self
.
_comm
=
HCMockup
.
FakeCom
(
self
.
config
)
global_map
.
register
(
self
.
_comm
,
parents_list
=
[
self
,
"comms"
])
def
_write_cmd
(
self
,
cmd
,
*
values
):
return
self
.
_comm
.
write
(
cmd
,
*
values
)
def
_read_cmd
(
self
,
cmd
):
return
self
.
_comm
.
read
(
cmd
)
class
BCMockup
(
BlissController
):
_COUNTER_TAGS
=
{
"current_temperature"
:
(
"cur_temp_ch1"
,
"scc"
),
"integration_time"
:
(
"int_time"
,
"icc"
),
}
def
_get_hardware
(
self
):
""" return the low level hardware controller interface """
return
HCMockup
(
self
.
config
[
"com"
])
def
_get_subitem_default_module
(
self
,
class_name
,
cfg
,
parent_key
):
if
class_name
==
"IntegratingCounter"
:
return
"bliss.common.counter"
def
_get_subitem_default_class_name
(
self
,
cfg
,
parent_key
):
if
parent_key
==
"counters"
:
tag
=
cfg
[
"tag"
]
if
self
.
_COUNTER_TAGS
[
tag
][
1
]
==
"scc"
:
return
"SamplingCounter"
elif
self
.
_COUNTER_TAGS
[
tag
][
1
]
==
"icc"
:
return
"IntegratingCounter"
def
_get_config_subitem
(
self
,
name
,
cfg
,
parent_key
,
item_class
):
if
parent_key
==
"counters"
:
name
=
cfg
[
"name"
]
tag
=
cfg
[
"tag"
]
mode
=
cfg
.
get
(
"mode"
)
unit
=
cfg
.
get
(
"unit"
)
convfunc
=
cfg
.
get
(
"convfunc"
)
if
self
.
_COUNTER_TAGS
[
tag
][
1
]
==
"scc"
:
cnt
=
self
.
_counter_controllers
[
"scc"
].
create_counter
(
item_class
,
name
,
unit
=
unit
,
mode
=
mode
)
cnt
.
tag
=
tag
elif
self
.
_COUNTER_TAGS
[
tag
][
1
]
==
"icc"
:
cnt
=
self
.
_counter_controllers
[
"icc"
].
create_counter
(
item_class
,
name
,
unit
=
unit
)
cnt
.
tag
=
tag
else
:
raise
ValueError
(
f
"cannot identify counter tag
{
tag
}
"
)
return
cnt
elif
parent_key
==
"operators"
:
return
item_class
(
cfg
)
elif
parent_key
==
"axes"
:
if
item_class
is
None
:
# mean it is a referenced axis (i.e external axis)
axis
=
name
# the axis instance
name
=
axis
.
name
# the axis name
tag
=
cfg
[
"tag"
]
# ask for a tag which only concerns this ctrl (local tag)
self
.
_tag2axis
[
tag
]
=
name
# store the axis tag
return
axis
else
:
raise
ValueError
(
f
"
{
self
}
only accept referenced axes"
)
# reject none-referenced axis
def
_load_config
(
self
):
self
.
_calc_mot
=
None
if
self
.
config
.
get
(
"energy"
):
self
.
energy
=
self
.
config
.
get
(
"energy"
)
# create different counter controllers
self
.
_counter_controllers
=
{}
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
)
# create the counter subitems now in order to have all of them immediately available after ctrl init
for
cfg
,
pkey
in
self
.
_subitems_config
.
values
():
if
pkey
==
"counters"
:
self
.
_get_subitem
(
cfg
[
"name"
])
# force item creation now
# prepare a storage for the tags associated to the axes referenced in the config
if
self
.
config
.
get
(
"axes"
)
is
not
None
:
self
.
_tag2axis
=
{}
@
autocomplete_property
def
counters
(
self
):
cnts
=
[
ctrl
.
counters
for
ctrl
in
self
.
_counter_controllers
.
values
()]
return
counter_namespace
(
chain
(
*
cnts
))
@
autocomplete_property
def
axes
(
self
):
return
IterableNamespace
(
**
{
name
:
self
.
_subitems
[
name
]
for
name
in
self
.
_tag2axis
.
values
()}
)
def
get_axis
(
self
,
name
):
return
self
.
_get_subitem
(
name
)
def
available_axis_names
(
self
):
return
[
k
for
k
,
v
in
self
.
_subitems_config
.
items
()
if
v
[
1
]
==
"axes"
]
@
property
def
calc_mot
(
self
):
if
self
.
_calc_mot
is
None
:
self
.
_calc_mot
=
self
.
config
.
get
(
"calc_controller"
)
return
self
.
_calc_mot
class
BCSCC
(
SamplingCounterController
):
def
__init__
(
self
,
name
,
bctrl
):
super
().
__init__
(
name
)
self
.
bctrl
=
bctrl
def
read_all
(
self
,
*
counters
):
values
=
[]
for
cnt
in
counters
:
tag_info
=
self
.
bctrl
.
_COUNTER_TAGS
.
get
(
cnt
.
tag
)
if
tag_info
:
values
.
append
(
self
.
bctrl
.
hardware
.
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
raise
ValueError
(
f
"Unknown counter
{
cnt
}
with tag
{
cnt
.
tag
}
!"
)
return
values
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
)