Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestion for message types. #56

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,19 @@
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information

project = 'experiment_control_protocol'
copyright = '2023, PyMeasure maintainers'
author = 'PyMeasure maintainers'
copyright = '2023, LECO maintainers'
author = 'LECO maintainers'

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration

extensions = ['myst_parser', 'sphinxcontrib.mermaid', 'sphinx_rtd_theme']
extensions = [
'sphinx.ext.autodoc',
'sphinx-jsonschema',
'myst_parser',
'sphinxcontrib.mermaid',
'sphinx_rtd_theme',
]

myst_enable_extensions = [
"colon_fence",
Expand Down
84 changes: 75 additions & 9 deletions control_protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,17 +330,83 @@ A Component MUST also offer a list of all possibly callable methods in accordanc
For such a RPC message, the first content frame MUST consist in a JSON-RPC compatible content, for example a single request object or a batch of request objects.


### List of methods

### Messages for Transport Layer
List of methods defined by LECO.
You MAY implement the optional methods of this list and you MUST implement the methods obligatory for your type of Component.
All methods implemented in a Component MUST adhere to this list or MUST have a name different to any method in this list.

- SIGNIN
- SIGNOUT
- ACKNOWLEDGE
- ERROR
- PING
- CO_SIGNIN
- CO_SIGNOUT

#### Component

Any Component, i.e. any participant in the LECO protocol, MUST offer the [OpenRPC Service Discovery Method](https://spec.open-rpc.org/#service-discovery-method) and the following methods.

.. jsonschema:: schemas/actor.json

Any Component MAY offer ANY of the following methods.
Components SHOULD offer ``shut_down``.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious about the optional methods. What happens if some component calls another one's shutdown, but that is not implemented? Do we need some common fallback method? Do you get a not implemented error back or somesuch?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you get a not implemented error back or somesuch?

jsonrpc has a "method not found" error defined.

I'm curious about the optional methods.

My reasoning is: There are some methods necessary, such that a Component fulfills it role (as a component, it has to be pingable), these are necessary.

Some features are nice to have, for example logging is not essential (maybe the Component does not have logging capabilities).

Copy link
Member

@bilderbuchi bilderbuchi Jul 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I realise why optional methods can be useful. We should have a clear approach how do deal with that optionality, though -- there are several possibilities (do we error? ignore? publish/discover/supply a list of implemented methods? how should the initiating party deal with that? ...)

Copy link
Member Author

@BenediktBurger BenediktBurger Jul 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Components MUST adhere to openRPC, therefore they MUST offer a "rpc.discover" methods, which tells about all the implemented methods (the output is exactly that, what is part of this PR).

  • Should we include this method in the Components list (together with "pong")?

If you try to call a method, a Component does not have, you get an appropriate error.

So, you (as a user) can either query beforehand and use only methods present, or you can try a method name and handle the error.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we include this method in the Components list (together with "pong")?

IMO, yes.

So, you (as a user) can either query beforehand and use only methods present, or you can try a method name and handle the error.

It's more a LECO implementor that has to deal with that. As we are designing for different implementations to talk to each other, I'm wondering if we should standardise the expectations around what happens with optional methods. The answer might as well be "no", or "use what rpc.discover gives you as you see fit".

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"no", or "use what rpc.discover gives you as you see fit".

That's my stance 😁


.. jsonschema:: schemas/component_optional.json


#### Coordinator

Control protocol Coordinators are also {ref}`Components <control_protocol.md#Component>`.
Furthermore, Coordinators MUST offer the following methods.

.. jsonschema:: schemas/coordinator.json


#### Actor

An Actor is a {ref}`control_protocol.md#Component`.
Additionally, it MUST offer the following methods.

.. jsonschema:: schemas/actor.schema


#### Polling Actor

An {ref}`control_protocol.md#Actor`, which supports regular polling of values, MUST implement these methods.

.. jsonschema:: schemas/polling_actor.json


#### Locking Actor

An {ref}`control_protocol.md#Actor` which support locking resources MUST offer the following methods.

:::{note}
TODO How to make these messages work? Define them directly in the transport layer?
TBD change the python code to json
:::

::: python
def lock(self, resource: Optional[str] = None) -> bool: ...

def unlock(self, resource: Optional[str] = None) -> None: ...

def force_unlock(self, resource: Optional[str] = None) -> None: ...
:::


### Errors

Every error has a code and a message.
Additionally they may have a ``data`` field with more information.

According to JSONRPC, applications can define error codes between -32000 and -32099.
BenediktBurger marked this conversation as resolved.
Show resolved Hide resolved
LECO defines the following errors.


#### Routing errors

Errors related to routing (mainly emitted by Coordinators).
Their error codes are in the range of -32090 to -32099.


| code | message | data | description |
|--------|------------------------------------|----------------------|--------------------------------------------------------------------------------------|
| -32090 | Component not signed in yet! | Name of the Component| If a Component did not sign in. |
| -32091 | The name is already taken. | Name of the Component| A Component tries to sign in, but another Component is signed in with the same name |
| -32092 | Node is unknown. | Name of the Node | The Node to which the message should be sent, is not known to this Coordinator. |
| -32093 | Receiver is not in addresses list. | Name of the receiver | The Component to which the message should be sent, is not known to this Coordinator. |
1 change: 1 addition & 0 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ dependencies:
- sphinx=5.3.0
- sphinxcontrib-mermaid=0.8.1
- sphinx_rtd_theme=1.1.1
- sphinx-jsonschema=1.19.1
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get jsonschema to work. It still shows .. jsonschema:: filename.json instead of the content...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've never used it before. Maybe an unexpected interaction with myst parser?
Is the file path correct? Is the extension really loaded (such that the directive gets acted upon)? no idea otherwise.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe an unexpected interaction with myst parser?

I commented the myst parser (and mermaid), but to no avail.

Is the file path correct?

Yes

Is the extension really loaded (such that the directive gets acted upon)?

At least it does not show any errors locally or in the CI.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I even used the jsonschema in the rst file from: https://sphinx-jsonschema.readthedocs.io/en/latest/directive.html#usage
It does not work. I don't know 🤷

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got it to work:
In an markdown file you have to use a colon fence:

:::{jsonschema}
your schema
:::

This does not work, however, for referencing files, only for showing a code snippet.

It does not render our schemata nicely 😭 (it does not render anything at all, as it does not recognize the keywords...).

Copy link
Member

@bilderbuchi bilderbuchi Sep 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, some progress at least I guess? :-/

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went with the data-viewer, that works better (but does not allow examples...).

171 changes: 171 additions & 0 deletions schemas/actor.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
{
"openrpc": "1.2.6",
"info": {
"title": "Actor",
"version": "0.1.0"
},
"methods": [
{
"name": "get_parameters",
"description": "Get the values for parameters.",
"params": [
{
"name": "parameters",
"schema": {
"type": "array",
"items": {
"type": "string"
}
},
"required": true
}
],
"result": {
"name": "result",
"schema": {
"type": "object",
"description": "Object with parameter-value pairs",
"additionalProperties": true
},
"required": true
},
"examples": [
{
"name": "Get parameters example",
"params": [
{
"name": "parameters",
"value": ["parameter1", "parameter2"]
}
],
"result": {
"name": "result",
"value": {
"parameter1": "value_of_parameter1",
"parameter2": "some_other_value"
}
}
}
]
},
{
"name": "set_parameters",
"params": [
{
"name": "parameters",
"schema": {
"type": "object",
"description": "Object with parameter-value pairs",
"additionalProperties": true

},
"required": true
}
],
"result": {
"name": "result",
"schema": {
"type": "null"
},
"required": true
},
"examples": [
{
"name": "Set parameters example",
"params": [
{
"name": "parameters",
"value": {
"parameter1": "value_of_parameter1",
"parameter2": "some_other_value"
}
}
],
"result": {
"name": "result",
"value": null
}
}
]

},
{
"name": "call_action",
"description": "Call an Action of this Component. All other arguments are handed to the action.",
"params": [
{
"name": "action",
"schema": {
"type": "string"
},
"required": true
},
{
"name": "args",
"schema": {
"type": "array",
"description": "List of positional arguments.",
"items": {
"$ref": "#/components/any"
}
},
"required": false
}
],
BenediktBurger marked this conversation as resolved.
Show resolved Hide resolved
"result": {
"name": "result",
"description": "Result of the action.",
"schema": {
"$ref": "#/components/any"
},
"required": true
},
"examples": [
{
"name": "Calling some action with keyword arguments only.",
"params": [
{
"name": "action",
"value": "super_action"
},
{
"name": "args",
"value": null

},
{
"name": "argument1",
"value": 5
}
],
"result": {
"name": "result",
"value": "result of 'super_action(argument1=5)'."
}
}
]
}
],
"components": {
"any" : {
"anyOf": [
{
"description": "Value of the parameter.",
"type": "integer"
},
{
"type": "number"
},
{
"type": "string"
},
{
"type": "null"
},
{
"type": "object"
}
]
}
}
}
21 changes: 21 additions & 0 deletions schemas/component.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"openrpc": "1.2.6",
"info": {
"title": "Component",
"version": "0.1.0"
},
"methods": [
{
"name": "pong",
"description": "Respond to a ping.",
"params": [],
"result": {
"name": "result",
"schema": {
"type": "null"
},
"required": true
}
}
]
}
45 changes: 45 additions & 0 deletions schemas/component_optional.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"openrpc": "1.2.6",
"info": {
"title": "Component-optional",
"version": "0.1.0"
},
"methods": [
{
"name": "set_log_level",
"params": [
{
"name": "level",
"schema": {
"type": "integer",
"multipleOf": 10,
"minimum": 0,
"maximum": 100
},
Copy link
Member Author

@BenediktBurger BenediktBurger Jul 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #56 (comment)

No, I mean the parameter of the method
"name": "level",
"schema": {
"type": "integer" "one-of-info/debug/warning/error/critical"

Idea is to change to a set of possible values. I don't know yet, how to define it properly.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found a way to define a set of possible values in json-schema ( "type": "integer", "enum": [1, 5, 9]).
Now the question is, do we want to have error levels as an integer or as a string?
If an integer, how do we define it (copy and or link to the python definitions at https://docs.python.org/3/library/logging.html#logging-levels)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, for language (and logging-framework) independence, I think a fixed set of level names seems most clear (the python ones seem fine to me).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it to strings.

"required": true
}
],
"description": "Set the logging level.",
"result": {
"name": "result",
"schema": {
"type": "null"
},
"required": true
}
},
{
"name": "shut_down",
"params": [],
"description": "Shut down the Component.",
"result": {
"name": "result",
"schema": {

"type": "null"
},
"required": true
}
}
]
}
Loading