Skip to content

Commit

Permalink
Improve OpenEMS documentation (OpenEMS#1399)
Browse files Browse the repository at this point in the history
Integrate some documentation that was created within the EASY-RES research project
  • Loading branch information
sfeilmeier authored Feb 23, 2021
1 parent fcc6540 commit 7d182f5
Show file tree
Hide file tree
Showing 18 changed files with 554 additions and 55 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed doc/modules/ROOT/assets/images/ui-config.png
Binary file not shown.
2 changes: 1 addition & 1 deletion doc/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
** xref:edge/build.adoc[Build OpenEMS Edge]
** xref:edge/deploy.adoc[Deploy OpenEMS Edge]
* OpenEMS UI
** xref:ui/architecture.adoc[Architecture]
** xref:ui/build.adoc[Build OpenEMS UI]
* OpenEMS Backend
** xref:backend/architecture.adoc[Architecture]
** xref:backend/configuration.adoc[Configuration]
** xref:backend/backend-to-backend.adoc[Backend-to-Backend]
** xref:backend/build.adoc[Build OpenEMS Backend]
** xref:backend/deploy.adoc[Deploy OpenEMS Backend]
Expand Down
2 changes: 0 additions & 2 deletions doc/modules/ROOT/pages/backend/configuration.adoc

This file was deleted.

159 changes: 150 additions & 9 deletions doc/modules/ROOT/pages/component-communication/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,163 @@
:imagesdir: ../../assets/images


This page describes the internal communication protocol between OpenEMS Edge, OpenEMS Backend and OpenEMS UI. The components keep an open https://de.wikipedia.org/wiki/WebSocket[Websocket] connection which is used for bi-directional communication. The protocol is based on https://www.jsonrpc.org/specification[JSON-RPC]. For details about JSON-RPC please refer to the specification. As a rough summary, the protocol is divided into
This page describes the internal communication protocol between OpenEMS Edge, OpenEMS Backend and OpenEMS UI. The components keep an open https://de.wikipedia.org/wiki/WebSocket[Websocket] connection which is used for bi-directional communication.

JSON-RPC Request::
Identified by a unique ID and method name with specific params. Expects a Response.
== JSON-RPC introduction

JSON-RPC Success Response::
Referring to the ID of the Request, providing a result which can be empty or hold specific data.
The protocol is based on https://www.jsonrpc.org/specification[JSON-RPC]. For details about JSON-RPC please refer to the specification. As a rough summary, the protocol is divided into

JSON-RPC Error Response::
Referring to the ID of the Request, providing error code, message and optionally data.
=== JSON-RPC Request

JSON-RPC Notification::
Identified by a unique method name with specific params. Does not expect a Response.
Identified by a unique ID and method name with specific params. Expects a Response.

[source,json]
----
{
"jsonrpc": "2.0",
"id": UUID,
"method": "method",
"params": {}
}
----

=== JSON-RPC Success Response

Referring to the ID of the Request, providing a result which can be empty or hold specific data.

[source,json]
----
{
"jsonrpc": "2.0",
"id": UUID,
"result": {}
}
----

=== JSON-RPC Error Response

Referring to the ID of the Request, providing error code, message and optionally data.

[source,json]
----
{
"jsonrpc": "2.0",
"id": UUID,
"result": {
"code": number,
"message": string,
"data"?: {}
}
}
----

=== JSON-RPC Notification

Identified by a unique method name with specific params. Does not expect a Response.

[source,json]
----
{
"jsonrpc": "2.0",
"method": "method",
"params": {}
}
----

== Example communication messages

The information on this page is useful to understand the internal communication structure and can help if your plan is to replace one component by a custom implementation, e.g. implementing your own frontend application, or if you plan to extend the provided feature-set.

== Subscribe Channels

Real-time channel data may change at a high rate. Instead of requiring the consumer to constantly pull the data, the OpenEMS API provides a publish-subscribe schema that notifies the consumer about updated values. It is initiated via a JSON-RPC request:

[source,json]
----
{
"jsonrpc": "2.0",
"id": UUID,
"method": "subscribeChannels",
"params": {
"count": number,
"channels": string[]
}
}
----

The parameters for subscribing to channel data are:

- `count`: a request count value that needs to be increased on each request to avoid concurrency problems
- `channels`: a list of channel addresses as strings, e.g. "ess0/Soc", "ess0/ActivePower"

From then on, the API constantly keeps sending `currentData` JSON-RPC notifications, containing the data for all subscribed channels:
[source,json]
----
{
"jsonrpc": "2.0",
"method": "currentData",
"params": {
[channelAddress]: string | number
}
}
----

To unsubscribe from channels, a new `subscribeChannels` request has to be sent with an empty list of channels.


== Edge-RPC

When using the API via OpenEMS Backend, it is possible to transparently target a specific OpenEMS Edge, that is connected to the Backend by wrapping the JSON-RPC request into an `Edge-RPC` request:

[source,json]
----
{
"jsonrpc": "2.0",
"id": UUID,
"method": "edgeRpc",
"params": {
"edgeId": string,
"payload": JSON-RPC-Request
}
}
----

The parameters for an “edgeRpc” request are:

- `edgeId`: the unique ID of the Edge at the Backend
- `payload`: the JSON-RPC Request that should be forwarded to the Edge, e.g. `getEdgeConfig` or `updateComponentConfig`.

The JSON-RPC response then also wraps the original result as a payload:

[source,json]
----
{
"jsonrpc": "2.0",
"id": UUID,
"result": {
"payload": JSON-RPC-Response
}
}
----

== JsonApi Component

To directly send a JSON-RPC request to one specific OpenEMS Component, that component has to implement the `JsonApi` interface.
Then the `componentJsonApi` request can be used to wrap a JSON-RPC request as payload:

[source,json]
----
{
"jsonrpc": "2.0",
"id": "UUID",
"method": "componentJsonApi",
"params": {
"componentId": string,
"payload": JSON-RPC-Request
}
}
----

== Authenticate UI to Edge/Backend using token

NOTE: On opening of the websocket connection to Edge/Backend, the UI is authenticated using an existing token.
Expand Down
76 changes: 51 additions & 25 deletions doc/modules/ROOT/pages/coreconcepts.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -56,41 +56,67 @@ for central singleton services:

== OpenEMS Component

OpenEMS Edge is built of Components, i.e. every main component implements the link:https://github.com/OpenEMS/openems/blob/develop/io.openems.edge.common/src/io/openems/edge/common/component/OpenemsComponent.java[OpenemsComponent interface icon:code[]].
An OpenEMS Component is the fundamental building block in OpenEMS. Within the used OSGi Java framework, an OpenEMS component represents a service with requirements and capabilities.

As an example, an OpenEMS Component can declare to have the capabilities of an Energy Storage System (ESS) and as such represents the digital twin of a real device.
A specific control algorithm can be implemented as a separate OpenEMS Component that declares a requirement for an ESS.
Using this metadata, these building blocks are wired together at runtime and form a very flexible system.
OSGi provides the capability to enable, modify or disable an OpenEMS Component at any time, without requiring a restart of the software.
Re-wiring of the building blocks happens transparently in the background by the framework.

Every OpenEMS Component is identified by a unique ID, the "Component-ID".
In an ecosystem consisting of a couple of ESS, a power meter at the grid connection point, and a measured photovoltaic system, those Component-IDs can be represented as follows:
* `ess1` for the first ESS
* `ess2` for the second ESS
* `ess0` for a virtual ESS cluster Component that aggregates ess1 and ess2
* `meter0` for the power meter at the grid connection point
* `meter1` for the measured photovoltaic system
* ...

By definition each Component has a unique ID. Those *Component-IDs* are typically:
To declare an OpenEMS component, the Java class has to `implement` the link:https://github.com/OpenEMS/openems/blob/develop/io.openems.edge.common/src/io/openems/edge/common/component/OpenemsComponent.java[OpenemsComponent interface icon:code[]].

* `ess0` for the first storage system or battery inverter
* `ess1` for the second storage system or battery inverter
* ...
* `meter0` for the first meter in the system
* ...
== Channel

If you receive your OpenEMS together with a FENECON energy storage system, you will find the following Component-IDs:
Each OpenEMS component has a defined set of data points.
These data points are called "Channels".
Each represents a single piece of information about a component.
By definition, each channel has a unique ID, the "Channel-ID", within its parent component.
Channels are defined by metadata like descriptive text, access-mode (`read-only`, `read-write`, `write-only`), data type (`string`, `integer`, `float`, etc.), and unit of measure (`Watt`, `Volt`, `Degree Celsius`, etc.).
It is up to the OpenEMS component to provide the input for its read-channels as well as triggering actions on write-channels.

* FENECON Pro
** `ess0`: FENECON Pro Ess
// TODO link:https://github.com/OpenEMS/openems/blob/develop/edge/src/io/openems/impl/device/pro/FeneconProEss.java[FENECON Pro Ess icon:code[]]
** `meter0`: Socomec grid meter
// TODO link:https://github.com/OpenEMS/openems/blob/develop/edge/src/io/openems/impl/device/socomec/SocomecMeter.java[Socomec grid meter icon:code[]]
** `meter1`: FENECON Pro production meter
// TODO link:https://github.com/OpenEMS/openems/blob/develop/edge/src/io/openems/impl/device/pro/FeneconProPvMeter.java[FENECON Pro production meter icon:code[]]
_Example:_ An OpenEMS Component that represents a device connected via the Modbus communication protocol continuously reads data, such as the current measured power and provides the data in its Channels.
Other Components in the system can then use the channel data for their application, e.g. as input for a control algorithm, to analyse it, store it locally or publish it via an application programming interface (API).

* FENECON Mini
** `ess0`: FENECON Mini
// TODO link:https://github.com/OpenEMS/openems/blob/develop/edge/src/io/openems/impl/device/minireadonly/FeneconMiniEss.java[FENECON Mini icon:code[]]
** `meter0`: FENECON Mini grid meter
// TODO link:https://github.com/OpenEMS/openems/blob/develop/edge/src/io/openems/impl/device/minireadonly/FeneconMiniGridMeter.java[FENECON Mini grid meter icon:code[]]
** `meter1`: FENECON Mini production meter
// TODO link:https://github.com/OpenEMS/openems/blob/develop/edge/src/io/openems/impl/device/minireadonly/FeneconMiniProductionMeter.java[FENECON Mini production meter icon:code[]]
An energy system architecture as depicted in the Introduction is complex: connected to multiple hardware devices - batteries, converters, meters, and others - and an operating system and other software components.
All of these elements are possible sources of errors.
Because of this, measures are implemented in OpenEMS to improve fault tolerance.
The developer needs to be aware, that every Channel value, while it will never change within a cycle, it could always be `undefined` or `null`, e.g. because there is no communication (yet) with the external hardware device or service.
Therefore, the programming API for accessing a channel value requires an explicit declaration of what should be done in that case.
It provides the following methods to get the actual value:

== Channel
- `public T getOrError() throws InvalidValueException;`
- `public T orElse(T alternativeValue);`

Each OpenemsComponent provides a number of Channels. Each represents a single piece of information. Each Channel implements the link:https://github.com/OpenEMS/openems/blob/develop/io.openems.edge.common/src/io/openems/edge/common/channel/Channel.java[Channel interface icon:code[]]. By definition each Channel has a unique ID within its parent Component.
Each Channel implements the link:https://github.com/OpenEMS/openems/blob/develop/io.openems.edge.common/src/io/openems/edge/common/channel/Channel.java[Channel interface icon:code[]].

== Nature

Natures extend normal Java interfaces with 'Channels'. If a Component implements a Nature it also needs to provide the required Channels. For example the Energy Storage System (ESS) Simulator link:https://github.com/OpenEMS/openems/blob/develop/io.openems.edge.simulator/src/io/openems/edge/simulator/ess/symmetric/reacting/EssSymmetric.java[Simulator.EssSymmetric.Reacting icon:code[]] implements the link:https://github.com/OpenEMS/openems/blob/develop/io.openems.edge.ess.api/src/io/openems/edge/ess/api/SymmetricEss.java[Ess interface icon:code[]] and therefor needs to provide a `Soc` Channel that provides the current 'State of Charge' of the battery.
Certain categories of devices and services provide the same kind of information (i.e. Channels).
To group these similar devices and services, OpenEMS defines "Natures" as sets of characteristics and attributes which need to be provided by each component that implements them.
That is, a Nature extends a normal Java interface with channels.

Examples of abstracting physical devices using Natures are:
- "SymmetricMeter" for power meters
- "SymmetricEss" for symmetric battery energy storage systems
- "Evcs" for electric vehicle charging stations.

OpenEMS components can declare their service capabilities and requirements as Natures.
In this way, a control algorithm can simply declare a requirement for a controllable energy storage system (“ManagedSymmetricEss”) and will at runtime be wired with a service that provides this capability.
The control algorithm does not need to know anything about the ESS's specific communication interface, protocol, or manufacturer.

Natures extend normal Java interfaces with 'Channels'.
If a Component implements a Nature it also needs to provide the required Channels.
For example the Energy Storage System (ESS) Simulator link:https://github.com/OpenEMS/openems/blob/develop/io.openems.edge.simulator/src/io/openems/edge/simulator/ess/symmetric/reacting/EssSymmetric.java[Simulator.EssSymmetric.Reacting icon:code[]] implements the link:https://github.com/OpenEMS/openems/blob/develop/io.openems.edge.ess.api/src/io/openems/edge/ess/api/SymmetricEss.java[Ess interface icon:code[]] and therefor needs to provide a `Soc` Channel that provides the current 'State of Charge' of the battery.

xref:edge/controller.adoc[Controllers] are written against Nature implementations. Example: A Controller can be used with any ESS, because it can be sure that it provides all the data the Controller requires for its algorithm.

Expand Down
Loading

0 comments on commit 7d182f5

Please sign in to comment.