Commit 7eb3ccf8 authored by Perceval Guillou's avatar Perceval Guillou
Browse files

add doc

parent 71b473c5
......@@ -187,7 +187,7 @@ class BlissController(CounterContainer):
@autocomplete_property
def hardware(self):
if self._hw_controller is None:
self._hw_controller = self._get_hardware()
self._hw_controller = self._create_hardware()
return self._hw_controller
@property
......@@ -226,7 +226,7 @@ class BlissController(CounterContainer):
else: # its a referenced object (cfg_name contains the object instance)
item_class = None
item = self._get_config_subitem(cfg_name, cfg, pkey, item_class)
item = self._create_subitem_from_config(cfg_name, cfg, pkey, item_class)
if item is None:
msg = f"\nUnable to obtain item {cfg_name} from {self.name} with:\n"
msg += f" class: {item_class}\n"
......@@ -356,7 +356,7 @@ class BlissController(CounterContainer):
# ========== ABSTRACT METHODS ====================
def _get_hardware(self):
def _create_hardware(self):
""" return the low level hardware controller interface """
raise NotImplementedError
......@@ -388,7 +388,7 @@ class BlissController(CounterContainer):
raise NotImplementedError
def _get_config_subitem(self, name, cfg, parent_key, item_class):
def _create_subitem_from_config(self, name, cfg, parent_key, item_class):
# Called when a new subitem is created (i.e accessed for the first time via self._get_subitem)
"""
Return the instance of a new item owned by this controller.
......
......@@ -104,7 +104,7 @@ class BCMockup(BlissController):
"integration_time": ("int_time", "icc"),
}
def _get_hardware(self):
def _create_hardware(self):
""" return the low level hardware controller interface """
return HCMockup(self.config["com"])
......@@ -120,7 +120,7 @@ class BCMockup(BlissController):
elif self._COUNTER_TAGS[tag][1] == "icc":
return "IntegratingCounter"
def _get_config_subitem(self, name, cfg, parent_key, item_class):
def _create_subitem_from_config(self, name, cfg, parent_key, item_class):
if parent_key == "counters":
name = cfg["name"]
tag = cfg["tag"]
......@@ -296,9 +296,9 @@ class Operator:
class TestBCMockup(BCMockup):
def _get_config_subitem(self, name, cfg, parent_key, item_class):
def _create_subitem_from_config(self, name, cfg, parent_key, item_class):
item = super()._get_config_subitem(name, cfg, parent_key, item_class)
item = super()._create_subitem_from_config(name, cfg, parent_key, item_class)
if item is None:
if parent_key in ["fakeitems", "subsection"]:
......
......@@ -125,7 +125,7 @@ class Controller(BlissController):
return "Switch"
@check_disabled
def _get_config_subitem(self, name, cfg, parent_key, item_class):
def _create_subitem_from_config(self, name, cfg, parent_key, item_class):
if parent_key == "axes":
if item_class is None: # it is a reference and name is the object
......
......@@ -89,13 +89,15 @@ class Mockup(Controller):
self._hw_state.create_state("PARKED", "mot au parking")
def _get_config_subitem(self, name, cfg, parent_key, item_class):
def _create_subitem_from_config(self, name, cfg, parent_key, item_class):
if parent_key == "switches":
switch = item_class(name, self, cfg)
self._switches[name] = switch
return switch
else:
return super()._get_config_subitem(name, cfg, parent_key, item_class)
return super()._create_subitem_from_config(
name, cfg, parent_key, item_class
)
def steps_position_precision(self, axis):
"""Mockup is really a stepper motor controller"""
......
......@@ -106,7 +106,7 @@ class Controller(BlissController):
item_classes = {"inputs": "Input", "outputs": "Output", "ctrl_loops": "Loop"}
return item_classes[parent_key]
def _get_config_subitem(self, name, cfg, parent_key, item_class):
def _create_subitem_from_config(self, name, cfg, parent_key, item_class):
item = item_class(self, cfg)
# --- For custom attributes and commands.
set_custom_members(self, item, self.init_obj) # really needed ???????
......
# Writing a BLISS controller
# Writing controllers for Bliss
Here are some tips to help you writing a BLISS controller.
Bliss put no constrains on controllers classes and developers can start from scratch and define everything.
However, there are several generic mechanisms, like loading the controller from a YML configuration (plugin) or managing the controller's counters and axes, which are already defined in Bliss and which can be inherited while writing a new controller class.
## @autocomplete_property decorator
## BlissController base class
As a base for the implementation of controllers, Bliss provides the `BlissController` class.
This class already implements the plugin mechanisms and is designed to ease the management of sub-objects under a top controller.
Examples of controllers that should inherit from `BlissController` class:
- a controller of axes
- a controller with counters
- a controller with axes and counter
- a top-controller (software) managing other sub-controllers (software/hardware)
Example of the YML structure:
```yml
- plugin: bliss_controller <== the dedicated BlissController 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)
param_1: value <== a parameter for the custom bliss controller (optional)
section_1: <== a section where subitems config can be declared (ex: 'counters') (optional)
- name: subitem_1 <== name of a subitem
- name: subitem_2 <== name of another subitem of the same type
section_2: <== another section for another type of subitems (ex: 'axes') (optional)
- name: subitem_2 <== name of another subitem type
```
The signature of a `BlissController` takes a single argument `config`.
It could be a `ConfigNode` object or a standard dictionary.
```python
class BlissController(CounterContainer):
def __init__(self, config):
```
### BlissController and subitems
A `BlissController` subitem is an object managed by the controller and which could have a name declared under a sub-section of the controller's configuration. Usually subitems are counters and axes but could be anything else (known by the controller only).
```yml
section_1:
- name: subitem_1 <== a subitem using the default class (defined by the controller)
- name: subitem_2 <== a subitem using a given class path (from an absolute path)
class: bliss.foo.custom.myclass
- name: subitem_3 <== a subitem using a given class name (default path known by the controller)
class: myitemclass
```
Subitems can be declared in the controller's YML configuration if they are expected to be directly imported in a user session.
If not declared in the YML, they are still accessible via the controller (see `BlissController._get_subitem(name)`).
To retrieve the subitems that can be identified as counters or axes, `BlissController` class implements the `@counters` and `@axes` properties.
The `BlissController` identifies the subitem type thanks to the name of the sub-section where the item was found (aka `parent_key`).
Also, the controller must provides a default class for each kind of `parent_key` (see `BlissController._get_subitem_default_class_name`).
Examples:
```python
def _get_subitem_default_class_name(self, cfg, parent_key):
if parent_key == "axes":
return "Axis"
elif parent_key == "encoders":
return "Encoder"
elif parent_key == "shutters":
return "Shutter"
elif parent_key == "switches":
return "Switch"
```
or
```python
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"
```
The default subitem class can be overridden by specifing the `class` key in its configuration.
The class can be given as an absolute path or as a class name.
If providing a class name the controller first look at its module level to find the item class, else it uses a default path defined by the controller (see `BlissController._get_subitem_default_module`).
Examples:
```python
def _get_subitem_default_module(self, class_name, cfg, parent_key):
if parent_key == "axes":
return "bliss.common.axis"
elif parent_key == "encoders":
return "bliss.common.encoder"
elif parent_key == "shutters":
return "bliss.common.shutter"
elif parent_key == "switches":
return "bliss.common.switch"
```
or
```python
def _get_subitem_default_module(self, class_name, cfg, parent_key):
if class_name == "IntegratingCounter":
return "bliss.common.counter"
```
### Bliss controller plugin
`BlissControllers` are created from the yml configuration using the `bliss_controller` plugin.
Any subitem can be imported in a Bliss session with the command `config.get('name')`.
The bliss controller itself can have a name (optional) and can be imported in the session.
The plugin ensures that the controller and subitems are only created once.
The effective creation of subitems is performed by the `BlissController` itself and the plugin just ensures that the controller is always created before subitems and only once.
The `bliss_controller` plugin will also manage the resolution order of the references to other objects within the `BlissController` configuration. It handles external and internal references and allows to use a reference for a subitem name.
Example of an advanced configuration using different kind of references:
```yml
- plugin: bliss_controller
module: custom_module
class: BCMockup
name: bcmock
custom_param_1: value
custom_param_2: $ref1 <== a referenced object for the controller (optional/authorized)
sub-section-1:
- name: sub_item_1
tag : item_tag_1
sub_param_1: value
device: $ref2 <== an external reference for this subitem (optional/authorized)
sub-section-2:
- name: sub_item_2
tag : item_tag_2
input: $sub_item_1 <== an internal reference to another subitem owned by the same controller (optional/authorized)
sub-section-2-1: <== nested sub-sections are possible (optional)
- name: sub_item_21
tag : item_tag_21
sub-section-3 :
- name: $ref3 <== a subitem as an external reference is possible (optional/authorized)
something: value
```
### Subitem creation
In order to keep the plugin as generic as possible, all the knowledge specfic to the controller is asked by the plugin to the `BlissController`.
In particular, when the plugin needs to instantiate a subitem it will call the method `BlissController._create_subitem_from_config`. This abstract method must be implemented and must return the subitem instance.
To be able to decide which instance should be created, the method receives 4 arguments:
- `name`: subitem name
- `cfg`: subitem config
- `parent_key`: name of the subsection where the item was found (in controller's config)
- `item_class`: class for the subitem (see [BlissController and sub-items](dev_write_ctrl.md#BlissController-and-subitems) ).
If `None` then the subitem is a reference and the object exist already and is contained in `name`.
Examples:
```python
@check_disabled
def _create_subitem_from_config(self, name, cfg, parent_key, item_class):
if parent_key == "axes":
if item_class is None: # it is a reference and name is the object
axis = name
name = axis.name
else:
axis = item_class(name, self, cfg)
self._axes[name] = axis
axis_tags = cfg.get("tags")
if axis_tags:
for tag in axis_tags.split():
self._tagged.setdefault(tag, []).append(axis)
if axis.controller is self:
set_custom_members(self, axis, self._initialize_axis)
else:
# reference axis
return axis
if axis.controller is self:
axis_initialized = Cache(axis, "initialized", default_value=0)
self.__initialized_hw_axis[axis] = axis_initialized
self.__initialized_axis[axis] = False
self._add_axis(axis)
return axis
elif parent_key == "encoders":
encoder = self._encoder_counter_controller.create_counter(
item_class, name, motor_controller=self, config=cfg
)
self._encoders[name] = encoder
self.__initialized_encoder[encoder] = False
return encoder
```
or
```python
def _create_subitem_from_config(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
```
### Nested BlissControllers
A top-bliss-controller can have multiple sub-bliss-controllers.
In that case there are two ways to create the sub-bliss-controllers:
The most simple way to do this is to declare a sub-bliss-controller as an independant object with its own yml config and use a reference to this object into the top-bliss-controller config.
Else, if a sub-bliss-controller has no reason to exist independently from the top-bliss-controller, then the top-bliss-controller will create and manage its sub-bliss-controllers from the knowledge of the top-bliss-controller configuration only.
In the second case, some items declared in the top-bliss-controller are, in fact, managed by one of the sub-bliss-controllers.
Then, the author of the top-bliss-controller class must overload the `_get_item_owner` method and specify which is the sub-bliss-controller that manages which items.
Example:
Consider a top-bliss-controller which has internally another sub-bliss-controller that manages pseudo axes.
(`self._motor_controller = AxesBlissController(...)`)
```yml
- plugin: bliss_controller
module: custom_module
class: BCMockup
name: bcmock
axes:
- name: $xrot
tags: real xrot
- name: $yrot
tags: real yrot
- name: axis_1
tag : theta
```
The top-bliss-controller configuration declares the axes subitems but those items are in fact managed by the motors controller (`self._motor_controller`).
In that case, developers must override the `self._get_item_owner` method to specify the subitems that are managed by `self._motor_controller` instead of `self`.
```python
def _get_item_owner(self, name, cfg, pkey):
""" Return the controller that owns the items declared in the config.
By default, this controller is the owner of all config items.
However if this controller has sub-controllers that are the real owners
of some items, this method should use to specify which sub-controller is
the owner of which item (identified with name and pkey).
"""
if pkey == "axes":
return self._motor_controller
else:
return self
```
The method receives the item name and the `parent_key`. So `self._motor_controller` can be associated to all subitems under the `axes` parent_key (instead of doing it for each subitem name).
### Direct instantiation
A BlissController can be instantiated directly (i.e. not instantiated by the plugin) providing a configuration 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.
Example: `bctrl = BlissController( config_dict )` => `bctrl._controller_init()`
## Other tips
### @autocomplete_property decorator
In many controllers, the `@property` decorator is heavily used to protect certain
attributes of the instance or to limit the access to read-only. When using the
......@@ -42,7 +396,7 @@ BLISS [1]: lima_simulator.counters. ↹
_bpm_
```
## The `__info__()` method for Bliss shell
### The `__info__()` method for Bliss shell
!!! info
......
Supports Markdown
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