Commit 9f65499f authored by Matias Guijarro's avatar Matias Guijarro

Merge branch '656-regulation-framework' into 'master'

Resolve "regulation framework"

Closes #656

See merge request !1338
parents 7b68eb6a e7788665
Pipeline #18380 failed with stages
in 43 minutes and 29 seconds
This diff is collapsed.
......@@ -16,8 +16,10 @@ def SoftAxis(
position="position",
move="position",
stop=None,
state=None,
low_limit=float("-inf"),
high_limit=float("+inf"),
tolerance=None,
):
if callable(position):
......@@ -27,17 +29,19 @@ def SoftAxis(
if callable(stop):
stop = stop.__name__
controller = SoftController(
name,
obj,
{
"position": position,
"move": move,
"stop": stop,
"limits": (low_limit, high_limit),
"name": name,
},
)
config = {
"position": position,
"move": move,
"stop": stop,
"state": state,
"limits": (low_limit, high_limit),
"name": name,
}
if tolerance is not None:
config["tolerance"] = tolerance
controller = SoftController(name, obj, config)
controller._init()
axis = controller.get_axis(name)
......
......@@ -51,6 +51,7 @@ class Input(SamplingCounterController):
# useful attribute for a temperature controller writer
self._attr_dict = {}
global_map.register(self, parents_list=[controller])
@property
......@@ -84,7 +85,6 @@ class Output(SamplingCounterController):
""" Constructor """
super().__init__(config["name"])
self.__controller = controller
global_map.register(self, parents_list=[controller])
try:
self.__limits = (config.get("low_limit"), config.get("high_limit"))
......@@ -110,6 +110,8 @@ class Output(SamplingCounterController):
# useful attribute for a temperature controller writer
self._attr_dict = {}
global_map.register(self, parents_list=[controller])
@property
def controller(self):
""" returns the temperature controller """
......@@ -379,10 +381,9 @@ class Loop(SamplingCounterController):
self.__config = config
self.__input = config.get("input")
self.__output = config.get("output")
global_map.register(
self,
parents_list=[controller, "counters"],
children_list=[self.__input, self.__output],
self, parents_list=[controller], children_list=[self.__input, self.__output]
)
self._Pval = None
......
# -*- coding: utf-8 -*-
#
# This file is part of the bliss project
#
# Copyright (c) 2015-2019 Beamline Control Unit, ESRF
# Distributed under the GNU LGPLv3. See LICENSE for more info.
import sys
import enum
from bliss.common.regulation import (
Input,
Output,
Loop,
SoftLoop,
ExternalInput,
ExternalOutput,
)
from bliss.config.plugins.utils import find_class, replace_reference_by_object
TYPE = enum.Enum("TYPE", "INPUT OUTPUT LOOP")
DEFAULT_CLASS_NAME = {TYPE.INPUT: "Input", TYPE.OUTPUT: "Output", TYPE.LOOP: "Loop"}
DEFAULT_CLASS = {TYPE.INPUT: Input, TYPE.OUTPUT: Output, TYPE.LOOP: Loop}
def create_objects_from_config_node(config, node):
# --- for a better understanding of this function, see bliss.config.static => config.get( obj_name )
# --- prepare dictionaries for cached object and instanciated objects
name2cacheditems = {}
name2items = {}
if "inputs" in node or "outputs" in node or "ctrl_loops" in node:
# --- dealing with a controller
obj_name = None
else:
# --- dealing with a child of a controller (Input, Output, Loop) or an object defined outside of a controller (like ExternalIn/out or SoftLoop)
obj_name = node.get("name")
upper_node = node.parent # <= check parent node and see if it is a controller
if (
"inputs" in upper_node
or "outputs" in upper_node
or "ctrl_loops" in upper_node
): # <= if True it is a contoller
node = upper_node
else: # <= else it is an object without a controller (like ExternalIn/out or SoftLoop)
replace_reference_by_object(config, node)
if node.get("class") in ["SoftLoop", "Loop"]:
new_obj = SoftLoop(node)
elif node.get("class") in ["Input", "ExternalInput"]:
new_obj = ExternalInput(node)
elif node.get("class") in ["Output", "ExternalOutput"]:
new_obj = ExternalOutput(node)
name2items[obj_name] = new_obj
yield name2items
return
# --- whatever the object kind, first of all we instanciate the controller
controller_name = node.get("name") # usually is None
controller_class = find_class(node, "bliss.controllers.regulation.temperature")
controller = controller_class(node)
# controller.initialize_controller() # removed for lasy_init
# --- store in cache the sub-objects of the controller for a later instanciation
# --- for each type of a controller sub-node (i.e. inputs, outputs, loops)
for node_type, child_nodes in (
(TYPE.INPUT, node.get("inputs", [])),
(TYPE.OUTPUT, node.get("outputs", [])),
(TYPE.LOOP, node.get("ctrl_loops", [])),
):
# --- for each subnode of a given type, store info in cache
for nd in child_nodes:
name2cacheditems[nd["name"]] = (node_type, nd.deep_copy(), controller)
# --- add the controller to stored items if it has a name
if controller_name:
name2items[controller_name] = controller
# update the config cache dict NOW to avoid cyclic instanciation (i.e. config.get => create_object_from_... => replace_reference_by_object => config.get )
yield name2items, name2cacheditems
# --- don't forget to instanciate the object for which this function has been called (if not a controller)
if obj_name is not None:
obj = config.get(obj_name)
yield {obj_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, object_info):
# for a better understanding of this function, see bliss.config.static => config.get( obj_name )
node_type, node, controller = object_info
replace_reference_by_object(config, node)
controller_module = sys.modules[controller.__module__]
object_class_name = node.get("class", DEFAULT_CLASS_NAME[node_type])
try:
object_class = getattr(controller_module, object_class_name)
except AttributeError:
object_class = DEFAULT_CLASS[node_type]
new_object = controller.add_object(node_type.name, object_class, node)
return new_object
......@@ -5,10 +5,7 @@
# Copyright (c) 2015-2019 Beamline Control Unit, ESRF
# Distributed under the GNU LGPLv3. See LICENSE for more info.
import numpy
from bliss.common.axis import NoSettingsAxis, AxisState
from bliss.config.static import get_config
from bliss.controllers.motor import Controller
......@@ -53,6 +50,7 @@ def get_move_func(obj, move):
def get_stop_func(obj, stop):
if stop is None:
return None
if callable(stop):
return stop
......@@ -63,6 +61,20 @@ def get_stop_func(obj, stop):
return stop_func
def get_state_func(obj, state):
if state is None:
return None
if callable(state):
return state
def state_func():
return getattr(obj, state)()
state_func.__name__ = state
return state_func
class _Config(dict):
def to_dict(self):
return dict(self)
......@@ -79,6 +91,7 @@ class SoftController(Controller):
self._position = get_position_func(obj, axis_config["position"])
self._move = get_move_func(obj, axis_config["move"])
self._stop = get_stop_func(obj, axis_config["stop"])
self._state = get_state_func(obj, axis_config["state"])
def initialize(self):
# velocity and acceleration are not mandatory in config
......@@ -89,7 +102,10 @@ class SoftController(Controller):
pass
def state(self, axis):
return AxisState("READY")
if self._state is None:
return AxisState("READY")
else:
return self._state()
def start_one(self, motion):
self._move(motion.target_pos)
......@@ -102,4 +118,7 @@ class SoftController(Controller):
return self._position()
def stop(self, axis):
pass
if self._stop is None:
return
else:
return self._stop()
......@@ -66,11 +66,11 @@ import time
import enum
from bliss.comm import serial
from bliss.comm.util import get_interface, get_comm
from bliss.common.logtools import *
from bliss.common.logtools import log_info, log_debug, log_warning
from bliss.controllers.temperature.lakeshore.lakeshore import LakeshoreBase
from .lakeshore import LakeshoreInput as Input
from .lakeshore import LakeshoreOutput as Output
from .lakeshore import LakeshoreLoop as Loop
from bliss.controllers.temperature.lakeshore.lakeshore import LakeshoreInput as Input
from bliss.controllers.temperature.lakeshore.lakeshore import LakeshoreOutput as Output
from bliss.controllers.temperature.lakeshore.lakeshore import LakeshoreLoop as Loop
_last_call = time.time()
# limit number of commands per second
......@@ -292,6 +292,9 @@ class LakeShore331:
ki = kic
if kd is None:
kd = kdc
print("LakeShore331.pid: %s %s %s" % (kp, ki, kd))
if float(kp) < 0.1 or float(kp) > 1000.:
raise ValueError(
"Proportional gain %s is out of bounds [0.1,1000]" % kp
......@@ -301,6 +304,7 @@ class LakeShore331:
if float(kd) < 0 or float(kd) > 200:
raise ValueError("Derivative rate %s is out of bounds [0,200]" % kd)
self.send_cmd("PID", kp, ki, kd, channel=channel)
else:
kp, ki, kd = self.send_cmd("PID?", channel=channel).split(",")
return float(kp), float(ki), float(kd)
......@@ -563,7 +567,7 @@ class lakeshore331(LakeshoreBase):
OPEN_LOAD = 1
SHORT = 2
def __init__(self, config, *args):
def __init__(self, config):
comm_interface = get_comm(config, parity="O", bytesize=7, stopbits=1)
_lakeshore = LakeShore331(comm_interface)
......@@ -574,7 +578,7 @@ class lakeshore331(LakeshoreBase):
"Error, the Lakeshore model is {0}. It should be 331.".format(model)
)
LakeshoreBase.__init__(self, _lakeshore, config, *args)
LakeshoreBase.__init__(self, _lakeshore, config)
def _read_state_output(self, channel):
log_info(self, "_state_output")
......
......@@ -64,7 +64,7 @@ yml configuration example:
output: $ls332o_2
channel: 2
"""
import types
import time
import enum
from bliss.comm import serial
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -33,6 +33,7 @@ pyzmq
redis >= 5.0
redis-py >= 3.3.7
requests
simple-pid
sqlalchemy
tabulate
tango >= 9.3.2
......
......@@ -262,7 +262,7 @@ def test_global_map(beacon, s1hg, roby):
assert len(axes) == 9
counters = list(m.find_children("counters"))
assert id(heater.counters[0]) in counters
assert len(counters) == 3
assert len(counters) == 2
slits_children = m.find_children(id(s1hg.controller))
for real_axis in s1hg.controller.reals:
assert id(real_axis) in slits_children
......
-
class: MyDevice # <== any kind of object
package: bliss.controllers.regulation.temperature.mockup
plugin: bliss
name: my_device
-
class: MyCustomInput # <== a custom input defined by the user, inheriting from the ExternalInput class
package: bliss.controllers.regulation.temperature.mockup
plugin: bliss
name: custom_input
device: $my_device # <== any kind of object
unit: eV
-
class: MyCustomOutput # <== a custom output defined by the user, inheriting from the ExternalOutput class
package: bliss.controllers.regulation.temperature.mockup
plugin: bliss
name: custom_output
device: $my_device # <== any kind of object
unit: eV
low_limit: 0.0 # <== minimum device value [unit]
high_limit: 100.0 # <== maximum device value [unit]
ramprate: 0.0 # <== ramprate to reach the output value [unit/s].
-
class: Input # <== value of key 'class' could be 'Input' or 'ExternalInput', the object will be an ExternalInput
name: diode_input
device: $diode # <== a SamplingCounter
unit: mm
-
class: Output # <== value of key 'class' could be 'Output' or 'ExternalOutput', the object will be an ExternalOutput
name: robz_output
device: $robz # <== an axis
unit: mm
low_limit: -1.0 # <== minimum device value [unit]
high_limit: 1.0 # <== maximum device value [unit]
ramprate: 0.0 # <== ramprate to reach the output value [unit/s].
mode: relative
-
class: Loop # <== value of key 'class' could be 'Loop' or 'SoftLoop', the object will be a SoftLoop
name: soft_regul
input: $custom_input
output: $custom_output
P: 0.01
I: 0.01
D: 0.0
low_limit: -1.0 # <== low limit of the PID output value. Usaually equal to 0 or -1.
high_limit: 1.0 # <== high limit of the PID output value. Usaually equal to 1.
frequency: 10.0
deadband: 0.1
deadband_time: 1.5
ramprate: 1.0
-
class: Mockup
module: mockup
host: lid42
inputs:
-
name: thermo_sample_new
channel: A
unit: deg
cooling_rate: 1.0 # <== special parameter for mockup inputs: defines the rate of the simulated cooling of the associated temperature [unit/s].
tango_server: temp1
-
name: sensor_new
channel: B
cooling_rate: 2.0 # <== special parameter for mockup inputs: defines the rate of the simulated cooling of the associated temperature [unit/s].
tango_server: temp1
outputs:
-
name: heater_new
channel: A
unit: Volt
low_limit: 0.0 # <== minimum device value [unit]
high_limit: 100.0 # <== maximum device value [unit]
ramprate: 0.0 # <== ramprate to reach the output value [unit/s].
heating_rate: 10.0 # <== special parameter for mockup outputs: heating capability of the device at 100% of its power [deg/s].
tango_server: temp1
ctrl_loops:
-
name: sample_regulation_new
input: $thermo_sample_new
output: $heater_new
P: 0.5
I: 0.2
D: 0.0
low_limit: 0.0 # <== low limit of the PID output value. Usaually equal to 0 or -1.
high_limit: 1.0 # <== high limit of the PID output value. Usaually equal to 1.
frequency: 10.0
deadband: 0.05
deadband_time: 1.5
ramprate: 1.0 # <== ramprate to reach the setpoint value [input_unit/s]
wait_mode: deadband
tango_server: temp1
\ No newline at end of file
......@@ -51,6 +51,8 @@
- s1vg
- s1vo
- sample_regulation
- sample_regulation_new
- soft_regul
- sensor
- sim_ct_gauss
- sim_ct_gauss_noise
......
Markdown is supported
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