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
24b008a1
Commit
24b008a1
authored
Apr 14, 2021
by
Perceval Guillou
Browse files
Regulator as BlissController
parent
31029613
Changes
6
Hide whitespace changes
Inline
Side-by-side
bliss/common/regulation.py
View file @
24b008a1
...
...
@@ -170,6 +170,8 @@ import gevent
import
gevent.event
import
enum
from
bliss.common.protocols
import
CounterContainer
from
bliss.common.logtools
import
log_debug
,
disable_user_output
from
bliss.common.utils
import
with_custom_members
,
autocomplete_property
from
bliss.common.counter
import
SamplingCounter
...
...
@@ -200,38 +202,38 @@ def _get_external_device_name(device):
return
device
class
SCC
(
SamplingCounterController
):
def
__init__
(
self
,
name
,
boss
):
super
().
__init__
(
name
)
self
.
boss
=
boss
def
read_all
(
self
,
*
counters
):
return
[
self
.
boss
.
read
()]
@
with_custom_members
class
Input
(
Sampling
CounterCont
roll
er
):
class
Input
(
CounterCont
ain
er
):
"""Implements the access to an input device which is accessed via the
regulation controller (like a sensor plugged on a channel of the controller)
"""
def
__init__
(
self
,
controller
,
config
):
""" Constructor """
super
().
__init__
(
name
=
config
[
"name"
])
self
.
_name
=
config
[
"name"
]
self
.
_controller
=
controller
self
.
_config
=
config
self
.
_channel
=
self
.
_config
.
get
(
"channel"
)
self
.
max_sampling_frequency
=
config
.
get
(
"max_sampling_frequency"
,
5
)
# useful attribute for a temperature controller writer
self
.
_attr_dict
=
{}
self
.
_build_counters
()
def
_build_counters
(
self
):
self
.
create_counter
(
SamplingCounter
,
self
.
name
+
"_counter"
,
unit
=
self
.
_config
.
get
(
"unit"
,
"N/A"
),
mode
=
self
.
_config
.
get
(
"sampling-counter-mode"
,
"SINGLE"
),
)
@
property
def
name
(
self
):
return
self
.
_name
def
read_all
(
self
,
*
counters
):
return
[
self
.
read
()]
@
autocomplete_property
def
counters
(
self
):
return
counter_namespace
({
self
.
name
:
self
.
_controller
.
counters
[
self
.
name
]})
# ----------- BASE METHODS -----------------------------------------
...
...
@@ -316,7 +318,13 @@ class ExternalInput(Input):
self
.
device
=
config
.
get
(
"device"
)
self
.
load_base_config
()
# ----------- METHODS THAT A CHILD CLASS MAY CUSTOMIZE ------------------
self
.
_controller
=
SCC
(
self
.
name
,
self
)
self
.
_controller
.
create_counter
(
SamplingCounter
,
self
.
name
,
unit
=
self
.
_config
.
get
(
"unit"
),
mode
=
self
.
_config
.
get
(
"mode"
,
"SINGLE"
),
)
def
__info__
(
self
):
lines
=
[
"
\n
"
]
...
...
@@ -358,7 +366,7 @@ class ExternalInput(Input):
@
with_custom_members
class
Output
(
Sampling
CounterCont
roll
er
):
class
Output
(
CounterCont
ain
er
):
""" Implements the access to an output device which is accessed via the regulation controller (like an heater plugged on a channel of the controller)
The Output has a ramp object.
...
...
@@ -370,7 +378,7 @@ class Output(SamplingCounterController):
def
__init__
(
self
,
controller
,
config
):
""" Constructor """
s
uper
().
__init__
(
name
=
config
[
"name"
]
)
s
elf
.
_
name
=
config
[
"name"
]
self
.
_controller
=
controller
self
.
_config
=
config
...
...
@@ -387,20 +395,13 @@ class Output(SamplingCounterController):
# useful attribute for a temperature controller writer
self
.
_attr_dict
=
{}
self
.
max_sampling_frequency
=
config
.
get
(
"max_sampling_frequency"
,
5
)
self
.
_build_counters
()
def
_build_counters
(
self
):
self
.
create_counter
(
SamplingCounter
,
self
.
name
+
"_counter"
,
unit
=
self
.
_config
.
get
(
"unit"
,
"N/A"
),
mode
=
self
.
_config
.
get
(
"sampling-counter-mode"
,
"SINGLE"
),
)
@
property
def
name
(
self
):
return
self
.
_name
def
read_all
(
self
,
*
counters
):
return
[
self
.
read
()]
@
autocomplete_property
def
counters
(
self
):
return
counter_namespace
({
self
.
name
:
self
.
_controller
.
counters
[
self
.
name
]})
# ----------- BASE METHODS -----------------------------------------
...
...
@@ -608,6 +609,14 @@ class ExternalOutput(Output):
self
.
mode
=
config
.
get
(
"mode"
,
"relative"
)
self
.
load_base_config
()
self
.
_controller
=
SCC
(
self
.
name
,
self
)
self
.
_controller
.
create_counter
(
SamplingCounter
,
self
.
name
,
unit
=
self
.
_config
.
get
(
"unit"
),
mode
=
self
.
_config
.
get
(
"mode"
,
"SINGLE"
),
)
# ----------- BASE METHODS -----------------------------------------
@
property
...
...
@@ -700,7 +709,7 @@ class ExternalOutput(Output):
@
with_custom_members
class
Loop
(
Sampling
CounterCont
roll
er
):
class
Loop
(
CounterCont
ain
er
):
""" Implements the access to the regulation loop
The regulation is the PID process that:
...
...
@@ -733,7 +742,7 @@ class Loop(SamplingCounterController):
def
__init__
(
self
,
controller
,
config
):
""" Constructor """
s
uper
().
__init__
(
name
=
config
[
"name"
]
)
s
elf
.
_
name
=
config
[
"name"
]
self
.
_controller
=
controller
self
.
_config
=
config
...
...
@@ -761,20 +770,8 @@ class Loop(SamplingCounterController):
self
.
reg_plot
=
None
self
.
max_sampling_frequency
=
config
.
get
(
"max_sampling_frequency"
,
5
)
self
.
_build_counters
()
self
.
_create_soft_axis
()
def
_build_counters
(
self
):
self
.
create_counter
(
SamplingCounter
,
self
.
name
+
"_setpoint"
,
unit
=
self
.
input
.
config
.
get
(
"unit"
,
"N/A"
),
mode
=
"SINGLE"
,
)
def
__del__
(
self
):
self
.
close
()
...
...
@@ -787,8 +784,16 @@ class Loop(SamplingCounterController):
# ----------- BASE METHODS -----------------------------------------
def
read_all
(
self
,
*
counters
):
return
[
self
.
_get_working_setpoint
()]
@
lazy_init
def
read
(
self
):
""" Return the current working setpoint """
log_debug
(
self
,
"Loop:read"
)
return
self
.
_get_working_setpoint
()
@
property
def
name
(
self
):
return
self
.
_name
##--- CONFIG METHODS
def
load_base_config
(
self
):
...
...
@@ -847,7 +852,7 @@ class Loop(SamplingCounterController):
all_counters
=
(
list
(
self
.
input
.
counters
)
+
list
(
self
.
output
.
counters
)
+
list
(
self
.
_co
unters
.
values
())
+
[
self
.
_co
ntroller
.
counters
[
self
.
name
]]
)
return
counter_namespace
(
all_counters
)
...
...
@@ -1484,6 +1489,14 @@ class SoftLoop(Loop):
self
.
load_base_config
()
self
.
max_attempts_before_failure
=
config
.
get
(
"max_attempts_before_failure"
,
3
)
self
.
_controller
=
SCC
(
self
.
name
,
self
)
self
.
_controller
.
create_counter
(
SamplingCounter
,
self
.
name
,
unit
=
self
.
_config
.
get
(
"unit"
),
mode
=
self
.
_config
.
get
(
"mode"
,
"SINGLE"
),
)
def
__info__
(
self
):
lines
=
[
"
\n
"
]
lines
.
append
(
f
"=== SoftLoop:
{
self
.
name
}
==="
)
...
...
@@ -1517,6 +1530,12 @@ class SoftLoop(Loop):
self
.
_ramp
.
stop
()
self
.
_stop_regulation
()
def
read
(
self
):
""" Return the current working setpoint """
log_debug
(
self
,
"SoftLoop:read"
)
return
self
.
_get_working_setpoint
()
@
property
def
max_attempts_before_failure
(
self
):
"""
...
...
bliss/config/plugins/bliss_controller.py
View file @
24b008a1
...
...
@@ -6,10 +6,22 @@
# Distributed under the GNU LGPLv3. See LICENSE for more info.
from
re
import
subn
# ================ 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, Config
Reference
from
bliss.config.static
import
ConfigNode
,
Config
List
def
find_sub_names_config
(
...
...
@@ -34,13 +46,18 @@ def find_sub_names_config(
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
):
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
,
l
ist
):
elif
isinstance
(
v
,
ConfigL
ist
):
for
i
in
v
:
if
isinstance
(
i
,
dict
):
if
isinstance
(
i
,
ConfigNode
):
find_sub_names_config
(
i
,
selection
,
level
+
1
,
k
)
return
selection
...
...
@@ -71,6 +88,7 @@ def create_objects_from_config_node(cfg_obj, cfg_node):
# 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)
...
...
@@ -78,18 +96,18 @@ def create_objects_from_config_node(cfg_obj, cfg_node):
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
())
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
())
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
)
#
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
:
...
...
@@ -98,6 +116,11 @@ def create_objects_from_config_node(cfg_obj, cfg_node):
# 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)
...
...
@@ -105,4 +128,5 @@ def create_objects_from_config_node(cfg_obj, cfg_node):
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
)
new_object
=
bctrl
.
_create_sub_item
(
name
,
cfg
,
pkey
)
return
new_object
bliss/controllers/bliss_controller.py
View file @
24b008a1
...
...
@@ -5,11 +5,9 @@
# Copyright (c) 2015-2020 Beamline Control Unit, ESRF
# Distributed under the GNU LGPLv3. See LICENSE for more info.
import
enum
from
time
import
perf_counter
,
sleep
from
itertools
import
chain
from
collections
import
ChainMap
from
gevent
import
Timeout
,
event
,
sleep
as
gsleep
from
gevent
import
event
,
sleep
as
gsleep
from
bliss
import
global_map
from
bliss.common.protocols
import
CounterContainer
...
...
@@ -30,7 +28,7 @@ from bliss.controllers.counter import (
)
from
bliss.scanning.acquisition.counter
import
BaseCounterAcquisitionSlave
from
bliss.config.beacon_object
import
BeaconObject
#
from bliss.config.beacon_object import BeaconObject
from
bliss.common.logtools
import
log_info
,
log_debug
,
log_debug_data
,
log_warning
...
...
@@ -157,10 +155,10 @@ class BlissController(CounterContainer):
_COUNTER_TAGS
=
{}
def
__init__
(
self
,
name
,
config
):
def
__init__
(
self
,
config
):
self
.
_name
=
name
self
.
_config
=
config
self
.
_name
=
config
.
get
(
"name"
)
self
.
_counter_controllers
=
{}
self
.
_hw_controller
=
None
...
...
@@ -217,6 +215,13 @@ class BlissController(CounterContainer):
def
_load_config
(
self
):
""" Read and apply the YML configuration """
# for k in self.config.keys():
# if k in self._SUB_CLASS:
# for cfg in self.config[k]:
# if cfg.get('name'):
# self._objects[cfg.get('name')] = self._SUB_CLASS[k](self, cfg)
raise
NotImplementedError
def
_build_counters
(
self
):
...
...
bliss/controllers/regulator.py
View file @
24b008a1
...
...
@@ -55,36 +55,67 @@ with the mandatory fields:
"""
from
gevent
import
lock
from
itertools
import
chain
from
bliss.common.regulation
import
Input
,
Output
,
Loop
from
bliss.common.utils
import
set_custom_members
from
bliss.common.logtools
import
log_info
from
bliss.common.protocols
import
counter_namespace
from
bliss.common.utils
import
autocomplete_property
import
time
from
bliss.controllers.bliss_controller
import
BlissController
from
bliss.common.counter
import
SamplingCounter
from
bliss.controllers.counter
import
SamplingCounterController
class
Controller
:
class
Controller
(
BlissController
):
"""
Regulation controller base class
Regulation controller base class
The 'Controller' class should be inherited by controller classes that are linked to an hardware
which has internal PID regulation functionnalities and optionally ramping functionnalities (on setpoint or output value) .
If controller hardware does not have ramping capabilities, the Loop objects associated to the controller will automatically use a SoftRamp.
The 'Controller' class should be inherited by controller classes that are linked to an hardware
which has internal PID regulation functionnalities and optionally ramping functionnalities (on setpoint or output value) .
If controller hardware does not have ramping capabilities, the Loop objects associated to the controller will automatically use a SoftRamp.
"""
class
SCC
(
SamplingCounterController
):
def
__init__
(
self
,
name
,
bctrl
):
super
().
__init__
(
name
)
self
.
bctrl
=
bctrl
def
read_all
(
self
,
*
counters
):
values
=
[]
for
cnt
in
counters
:
item
=
self
.
bctrl
.
get_object
(
cnt
.
name
)
if
item
is
not
None
:
values
.
append
(
item
.
read
())
return
values
_SUB_CLASS
=
{
"inputs"
:
Input
,
"outputs"
:
Output
,
"ctrl_loops"
:
Loop
}
def
__init__
(
self
,
config
):
self
.
__config
=
config
self
.
__name
=
config
.
get
(
"name"
)
self
.
_objects
=
{}
self
.
__lock
=
lock
.
RLock
()
self
.
__initialized_obj
=
{}
self
.
__hw_controller_initialized
=
False
def
add_object
(
self
,
node_type_name
,
object_class
,
cfg
):
""" creates an instance of the object and add it to the controller. Called by regulation plugin. """
super
().
__init__
(
config
)
new_obj
=
object_class
(
self
,
cfg
)
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
"""
new_obj
=
self
.
_SUB_CLASS
[
parent_key
](
self
,
cfg
)
# --- store the new object
self
.
_objects
[
new_obj
.
name
]
=
new_obj
...
...
@@ -94,6 +125,44 @@ class Controller:
return
new_obj
def
_load_config
(
self
):
""" Read and apply the YML configuration """
print
(
"=== _load_config"
)
pass
def
_build_counters
(
self
):
""" Build the CounterControllers and associated Counters"""
print
(
"=== _build_counters"
)
self
.
_counter_controllers
[
"scc"
]
=
self
.
SCC
(
"scc"
,
self
)
self
.
_counter_controllers
[
"scc"
].
max_sampling_frequency
=
self
.
config
.
get
(
"max_sampling_frequency"
,
1
)
for
k
in
self
.
_SUB_CLASS
:
for
cfg
in
self
.
config
.
get
(
k
,
[]):
name
=
cfg
[
"name"
]
mode
=
cfg
.
get
(
"mode"
,
"SINGLE"
)
unit
=
cfg
.
get
(
"unit"
)
self
.
_counter_controllers
[
"scc"
].
create_counter
(
SamplingCounter
,
name
,
unit
=
unit
,
mode
=
mode
)
def
_build_axes
(
self
):
""" Build the Axes (real and pseudo) """
print
(
"=== _build_axes"
)
pass
@
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 counter_namespace(self._motor_controller.axes)
pass
def
init_obj
(
self
,
obj
):
""" Initialize objects under the controller. Called by @lazy_init. """
...
...
@@ -103,6 +172,7 @@ class Controller:
if
not
self
.
__hw_controller_initialized
:
self
.
initialize_controller
()
print
(
"=== initialize_controller"
)
self
.
__hw_controller_initialized
=
True
if
self
.
__initialized_obj
.
get
(
obj
):
...
...
@@ -116,33 +186,27 @@ class Controller:
self
.
__initialized_obj
[
obj
.
input
]
=
True
obj
.
input
.
load_base_config
()
self
.
initialize_input
(
obj
.
input
)
print
(
"=== initialize_input"
)
if
not
self
.
__initialized_obj
.
get
(
obj
.
output
):
self
.
__initialized_obj
[
obj
.
output
]
=
True
obj
.
output
.
load_base_config
()
self
.
initialize_output
(
obj
.
output
)
print
(
"=== initialize_output"
)
obj
.
load_base_config
()
self
.
initialize_loop
(
obj
)
print
(
"=== initialize_loop"
)
else
:
self
.
__initialized_obj
[
obj
]
=
True
obj
.
load_base_config
()
if
isinstance
(
obj
,
Input
):
self
.
initialize_input
(
obj
)
print
(
"=== initialize_input"
)
elif
isinstance
(
obj
,
Output
):
self
.
initialize_output
(
obj
)
@
property
def
name
(
self
):
return
self
.
__name
@
property
def
config
(
self
):
"""
returns the config node
"""
return
self
.
__config
print
(
"=== initialize_output"
)
def
get_object
(
self
,
name
):
"""
...
...
tests/test_configuration/regulation/__init__.yml
View file @
24b008a1
plugin
:
regulation
plugin
:
bliss_controller
#
regulation
tests/test_configuration/regulation/test.yml
View file @
24b008a1
...
...
@@ -42,6 +42,7 @@
-
class
:
ExternalInput
# <== declare an 'ExternalInput' object
package
:
bliss.common.regulation
name
:
diode_input
device
:
$diode
# <== a SamplingCounter object reference (pointing to a counter declared somewhere else in a YML config file)
unit
:
N/A
...
...
@@ -49,6 +50,7 @@
-
class
:
ExternalOutput
# <== declare an 'ExternalOutput' object
package
:
bliss.common.regulation
name
:
robz_output
device
:
$robz
# <== an axis object reference (pointing to an object declared somewhere else in a YML config file)
unit
:
mm
...
...
@@ -60,6 +62,7 @@
-
class
:
SoftLoop
# <== declare a 'SoftLoop' object
package
:
bliss.common.regulation
name
:
soft_regul
input
:
$custom_input
output
:
$custom_output
...
...
@@ -77,6 +80,7 @@
-
class
:
SoftLoop
# <== declare a 'SoftLoop' object
package
:
bliss.common.regulation
name
:
soft_regul2
input
:
$bound_input
output
:
$robz_output
...
...
@@ -96,7 +100,8 @@
-
class
:
Mockup
module
:
temperature.mockup
# plugin: bliss_controller
module
:
regulation.temperature.mockup
host
:
lid42
inputs
:
-
...
...
@@ -138,4 +143,4 @@
ramprate
:
1.0
# <== ramprate to reach the setpoint value [input_unit/s]
wait_mode
:
deadband
tango_server
:
temp1
\ No newline at end of file
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new 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