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
classBlissController(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`).
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`).
`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`.
raiseValueError(f"cannot identify counter tag {tag}")
returncnt
elifparent_key=="operators":
returnitem_class(cfg)
elifparent_key=="axes":
ifitem_classisNone:# 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
returnaxis
else:
raiseValueError(
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.
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).
"""
ifpkey=="axes":
returnself._motor_controller
else:
returnself
```
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.