Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter

- CitrineOS core extracted (CSMS OCPP 2.0.1)
- OpenOCPP extracted (firmware OCPP 1.6J/2.0.1)
- ShapeShifter library installed (pip install -e)
- ShapeShifter specification extracted
- EVerest extracted

TODO updated with progress
This commit is contained in:
Eric F
2026-06-08 00:38:27 -04:00
parent 468cfeaa50
commit d398a6ced2
7326 changed files with 1177561 additions and 7 deletions

View File

@@ -0,0 +1,277 @@
############
EVerest APIs
############
.. tip::
You can find the API reference documentation here:
:doc:`EVerest API Reference </reference/api/autogenerated_api_index>` .
EVerest can be extended and adapted to specific needs via two sets of
APIs: The internal interfaces and the EVerest API.
The EVerest API are versioned and guaranteed to not introduce breaking
changes for the same major version of the API, while the internal
interfaces may change between versions. Therefore, the EVerest API
is the preferred way to implement custom integrations,
extensions and adaptations.
.. tip::
If you plan to integrate EVerest on your charging station hardware we
strongly recommend to use the EVerest APIs for this purpose, since these
interfaces are the ones that are supposed to be kept stable and maintained.
over time.
Overview
========
The EVerest API is the interface for hardware integration,
custom extensions and adaptations.
It is provided via a public MQTT interface, and the format for data
exchange is plain text JSON. The API is defined in AsyncAPI 3.
Custom client software using the EVerest API is therefore only loosely
coupled to the EVerest application and its internal interfaces.
There are no obligatory binary or link time dependencies of any kind.
As a result, the clients can be built individually and without
reference to the EVerest application at all.
This also implies that the programming language used for implementing
the client can be chosen freely. Typically a compiled language is
preferable for an embedded target, but it is not strictly required at
all.
The EVerest API is implemented in terms of EVerest modules, and every
API module implements one or more of EVerest's internal interfaces.
There are two distinct versions of API modules, either to implement
an interface (e.g. a driver for a DC power supply) or to consume it
(e.g. to check for the validation status of a token).
They can be included in the configuration file just like any other
module. E.g. - in this example -, a EVerest API module was loaded to
fulfill the power meter requirement of the EvseManager. The actual code
that talks to the power meter hardware to fetch the measurements can now
be implemented in a process running outside of EVerest (and is started
e.g. by a separate *systemd* unit). It just needs to feed the measured
values via MQTT into the
:ref:`Powermeter API module <everest_modules_powermeter_API>`.
.. figure:: images/everest-api-1.png
:alt: EVerest API - Image 1
:width: 450px
For a better understanding of how the EVerest API works, let us have an
exemplary closer look at the
:ref:`Power Supply DC API module <everest_modules_power_supply_DC_API>`.
The manifest can be reduced to this:
.. code-block:: yaml
config:
cfg_communication_check_to_s:
type: integer
default: 5
cfg_heartbeat_interval_ms:
type: integer
default: 1000
provides:
if_power_supply_DC:
interface: power_supply_DC
There are two configuration variables which are common for every API
module. Both are related to communication checks between EVerest and
the client module.
Below, there is the *provides* section, which states that this API
module *is* a :doc:`DC power supply </reference/interfaces/power_supply_DC>`.
This implies that it can be used in the configuration file for EVerest
wherever a DC power supply is expected.
In a product, this would be the :ref:`EvseManager <everest_modules_EvseManager>`
requiring a DC power supply. During development or validation it could also be a
BringUp module.
In contrast to an integrated driver for actual hardware, the API module
creates MQTT topics according to its specification and by this provides
hooks for the client to do the implementation work.
The documentation of the APIs can be found in the respective :doc:`reference
pages </reference/api/autogenerated_api_index>`. Each API module has its own
reference page describing the messages, topics and data structures used.
Let's take a look at an example configuration that uses the API module:
.. code-block:: yaml
active_modules:
ps_dc_1:
module: power_supply_DC_API
config_module:
cfg_communication_check_to_s: 60
cfg_heartbeat_interval_ms: 1000
cli:
module: BUPowerSupplyDC
standalone: true
connections:
psu:
- module_id: ps_dc_1
implementation_id: if_power_supply_DC
It loads two modules:
The :ref:`power_supply_DC_API <everest_modules_power_supply_DC_API>`
and the
:ref:`BringUp module for DC power supplies <everest_modules_BUPowerSupplyDC>`.
Starting EVerest with this configuration enables the API for DC power
supplies and a BringUp module, that can send to and receive messages from the
API. The actual topics on the MQTT will be available under
*everest_api/1/power_supply_DC/ps_dc_1/*.
It is as simple as this.
As explained previously, the client is only loosely coupled to EVerest.
As a consequence, EVerest cannot know by itself whether the client is
available and in good working conditions. For this reason, a
bidirectional communication check is available.
EVerest APIs sends *heartbeat* messages periodically
(*cfg_heartbeat_interval_ms* - with negative values disabling heartbeat
messages) and on the other hand requires the clients to send
*communication_check* messages within the timeout interval specified
(*cfg_communication_check_to_s* - with negative values disabling the
requirement for these messages).
In situations where a request/reply pattern is implemented, the timeout
for a response can be configured (*cfg_request_reply_to_s* - these timeouts
cannot be disabled since internal EVerest timeouts apply) . In general it
is advisable to respond as quickly as possible in to to prevent EVerest
from blocking internally.
AsyncAPI
========
The EVerest API is defined in terms of AsyncAPI 3.0.0.
For a thorough introduction and reference refer to
https://www.asyncapi.com .
All EVerest API modules are located in *EVerest/modules/API* and each
module contains the API definition in
*EVerest/docs/source/reference/EVerest_API/<name-of-api>.yaml* file.
These files are used to generate the
:doc:`HTML-based documentation </reference/api/autogenerated_api_index>`.
In order to build the documentation including the API reference pages,
run the following command in your *build* folder:
.. code-block:: bash
cmake -DEVEREST_BUILD_DOCS=ON .. && make trailbook_everest
Another possible way is to use the AsyncAPI yaml source file in
`AsyncAPI Studio <https://https://studio.asyncapi.com/>`_.
The *<name-of-api>.yaml* is defined for the client implementing the API and
reflects the client's point of view, when using the words *send* and
*receive* in the context of actions and operations.
The MQTT topics of the EVerest API follow a fixed pattern. All topics
are prefixed with *everest_api/1/{api_type}/{module_id}* - with *1*
being the version and *{api_type}* the type of the API.
*{module_id}* is the *module id* of the API module as configured in the
EVerest configuration file.
The prefix is followed by the direction of the message. There are two
options:
- *m2e* for messages from the module (client) to EVerest and
- *e2m* for messages from EVerest to the module (client).
This is finally followed by the name of the message. Here is a complete
example:
.. code-block:: text
everest_api/1/power_supply_DC/ps_dc_1/m2e/voltage_current
In the example, *power_supply_DC* is the API type, *ps_dc_1* is the
*module id* as configured in the EVerest configuration file, the
message (*m2e*) originates from the client and is directed towards
EVerest. The message name is *voltage_current*.
AsyncAPI defines channels which carry messages. A channel can be
addressed via the topic as defined above. Each channel can in principle
carry multiple messages, but concerning EVerest API, there is a
one-to-one mapping between a message and a channel. A message carries
content in the form of payload and possibly headers.
The content type for EVerest API is always JSON. The content is
individual for each message and defined in *components:schemas* within
the same file or sometimes in a referenced file.
AsyncAPI finally specifies operations on channels. The operations define
the action on a specific channel (for EVerest API always from the
client's point of view), which can be *send* or *receive*.
In many situations, the sender of a message is not interested in the
receive status of a message, e.g. in a situation where a meter publishes
its current values. Simple send and receive operations are used in this
case.
There are situations however, where this is not the case, e.g. remote
procedure calls or when a reply is requested. EVerest API handles this
situation with the request/reply pattern offered by AsyncAPI.
The operations are then augmented with a reply property holding the
reply channel and a dynamic reply address.
If the client receives a request, it has to reply to the topic provided
in the header's *replyTo* property within *cfg_communication_check_to_s*
seconds. If it does not, a default response is given by the API to
EVerest and an error is raised.
If the client sends a request, it has to specify the reply topic, where
it expects the answer. This information is communicated via the
*replyTo* property of the headers object. It is the client's
responsibility to ensure that the topic is unique in order to relate
replies to requests.
Using the EVerest API
======================
In order to use the EVerest API, load the required API modules in the
EVerest configuration file and connect its interfaces as presented in
the *Overview* section.
The chosen *module id* becomes part of the MQTT topic. EVerest API
modules can be loaded multiple times, e.g. if two DC power supplies are
connected.
If needed, adjust the heartbeat interval and communication check timeout
via the *cfg_heartbeat_interval_ms* and *cfg_communication_check_to_s*
configuration variables of the module.
Although communication check and heartbeat can be disabled with values
smaller or equal to zero, this is not recommended in a production
environment, since they are the only way to continuously check whether
the client and EVerest are online and responsive.
EVerest API clients are completely independent applications. They have
to be started independently of EVerest, possibly by their own *systemd*
service.
EVerest cannot start them, since it is agnostic of them. On EVerest
startup, the API modules raise an initial communication check error (if
communication check is enabled). This error is cleared with the first
communication check message sent from the client. It is raised again
when a timeout occurs or a request is not answered. Sending a
communication check message clears the error again.
It is the responsibility of the user to ensure that the client is and
remains available. This includes potentially a *watchdog* that restarts
the client in case of crash or deadlock. It is also the client's
responsibility to ensure proper initialization, shutdown and
surveillance of managed hardware, e.g. a DC power supply.

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

View File

@@ -0,0 +1,14 @@
#############
Adapt EVerest
#############
EVerest is designed to be easily adaptable to different use cases and
environments. This section will guide you through the process of adapting
EVerest to your specific needs by explaining the various sub-systems and APIs
available.
.. toctree::
:maxdepth: 1
sub-systems
apis

View File

@@ -0,0 +1,416 @@
###########
Sub-systems
###########
The following sections are a functional description of the subsystems of
EVerest. A subsystem is a group of modules that together implement a
specific functionality. This documentation guides you through some of the
most important subsystems step by step, showing how to build them up from
individual modules. The following subsystems will be explained:
- Charging subsystem
- Authentication subsystem
- OCPP subsystem
- Energy management subsystem
Charging subsystem
==================
The charging subsystem contains all modules that are responsible for the
charging logic, car communication and hardware drivers related to a
single charging port. For multiple charging ports the whole subsystem
can simply be duplicated.
It depends on the Authentication subsystem to authorize charging
sessions and on the Energy management subsystem to allocate the energy
budget for the charging process.
The main module of this subsystem is the
:ref:`EvseManager <everest_modules_EvseManager>`.
It contains all the logic and state machines for one CCS AC or DC charging
port.
.. _ac-charging-port:
AC charging port
------------------
Let's build an AC charging port step by step. Start with the EvseManager
module. For a simple AC charger, the default configuration of the module
is sufficient.
First, we add a board support module. This is the most important
hardware driver.
In this example, we use the :ref:`YetiDriver <everest_modules_YetiDriver>`
module which is the BSP driver for the BelayBox. Check the configuration of
the module; it should be correct for the BelayBox. Click on the (i) button
to learn more about each configuration setting.
.. figure:: images/yeti-driver-config.png
:alt: Yeti Driver Configuration
:width: 320px
The first connection is for the mandatory
:doc:`evse_board_support interface </reference/interfaces/evse_board_support>`.
It implements the following functionality:
- Control Pilot Signal (CP): Set PWM duty cycle in X2, State X1/F,
report states A-F
- Set allow relays on/off flag
- Report PP resistor (if used)
- Report AC input current / phases capabilities of the system
- Stop charging button input
.. figure:: images/yeti-evse-connection.png
:alt: Yeti Driver connected to EVSE Manager
:width: 360px
As you can see, there are two more connections between the
YetiDriver and the EvseManager. Those are optional:
- :doc:`connector_lock </reference/interfaces/connector_lock>`
(if used in the hardware): EvseManager requests
locking/unlocking of the connector lock (for AC Type 2 Sockets)
- :doc:`ac_rcd </reference/interfaces/ac_rcd>` (optional):
Reports information about the RCD module in an
AC charger. This is only used for telemetry and error reporting,
the actual shutdown needs to be handled in hardware.
These two modules already allow basic AC charging functionality. Next
step is to add a power meter to allow for invoicing the charged amount
of energy. It may not be needed e.g. in a private installation.
You can use any hardware driver module that provides a
:doc:`powermeter interface </reference/interfaces/powermeter>`
implementation. For the BelayBox, we could have used the
*powermeter* implementation of the YetiDriver (which uses the Yeti
onboard metering). In most configurations, an external DIN-rail power
meter is used with an RS485/ModBus connection, so we use the
:ref:`GenericPowermeter <everest_modules_GenericPowermeter>`
module here:
.. figure:: images/powermeter-connection.png
:alt: Power meter connection
:width: 420px
:ref:`GenericPowermeter <everest_modules_GenericPowermeter>`
is a module that can easily be adapted to most ModBus-based
AC power meters by specifying the register mappings in the
configuration of that module. It requires a
:ref:`SerialCommHub <everest_modules_SerialCommHub>`
module to do the actual read/write to the serial RS485 port of the system.
This is a separate module as multiple device drivers may use the same
serial bus, so they can all be connected to the same
:ref:`SerialCommHub <everest_modules_SerialCommHub>` module.
Make sure to configure the correct serial device and baud rate.
Note that there are two optional requirements for power meters in the
EvseManager: *powermeter_grid_side* and *powermeter_car_side*. The first
one should be used if the power meter is measuring on the AC input side,
the second one should be used if it is measuring the output power to the
vehicle. It is ok to connect both if you have two power meters. For AC,
one is generally sufficient. For DC, two meters on AC and DC side may be
useful.
Next step is to add ISO communication, if support for AC ISO15118-2 is
desired:
.. figure:: images/iso-communication-connection.png
:alt: ISO Communication Connection
:width: 700px
Three modules have been added:
:ref:`EvseV2G <everest_modules_EvseV2G>`:
Implementation of ISO15118-2(AC/DC) and DIN SPEC70121(DC).
Connects to the *hlc/ISO15118_charger* requirement of EvseV2G. Make sure
to set the “device” config option to the ethernet device of the PLC
modem. Leave the other options on default for now.
:ref:`EvseSecurity <everest_modules_EvseSecurity>`:
Handles certificates and private keys for TLS/PnC. We
will connect it here even though PnC is not enabled yet.
:ref:`EvseSlac <everest_modules_EvseSlac>`:
Implementation of ISO15118-3 (SLAC) to pair the PLC modems
of the EV and the EVSE at the start of the session. Make sure to
configure the same “device” as used for the EvseV2G. The two must point
to the same PLC modem.
To actually enable ISO 15118, some settings in the EvseManager module
need to be adjusted:
----
.. figure:: images/enable-iso15118.png
:alt: Enable ISO 15118
:width: 350px
----
The recommended setting is to use *ac_hlc_enable*, which allows ISO
15118-2 sessions on AC. In this configuration, nominal PWM (>10% duty
cycle) will be used the same way as it is used for basic charging
(non-ISO) sessions. This is the most interoperable way that charges all
cars, even if they do not support ISO 15118-2 on AC. All other options
will only charge a subset of EVs out there.
Some cars however may not use ISO at all if they detect nominal PWM -
even if they support ISO 15118-2. You enable set *ac_hlc_use_5percent*
to start with 5% duty cycle PWM on CP similar to DC charging.
Several cars will now use ISO communication for AC. The downside is that
there are cars that cannot be charged at all, because they allow only DC
to be used when 5% signaling is enabled. (They violate the ISO 15118-3,
but many car implementations do it that way.)
With this setting, EVerest may switch back to nominal PWM from 5% in
accordance with the ISO 15118-3 regulations. Some EVs may cancel ISO
communication in this case, even though the standard clearly states
differently.
For those EVs, you can set the *ac_enforce_hlc* option to “true”. Then,
the 5% PWM will be used throughout the complete charging session as it
is done for DC. This is not allowed according to the standard though.
For completeness, well also add a
:ref:`PersistentStore <everest_modules_PersistentStore>`
module to the *kvs/persistent_store* requirement of the
:ref:`EvseManager <everest_modules_EvseManager>`.
This is needed to persistently store charging session information e.g-
for German Eichrecht requirements. It may not be needed in simple
configurations.
.. figure:: images/persistent-store-connection.png
:alt: Persistent Store Connection
:width: 700px
For AC, we are complete now.
.. _dc-charging-port:
DC charging port
------------------
Let's build a DC charging port using the phyVERSO board. Start with the
:ref:`EvseManager <everest_modules_EvseManager>` again and adjust
one setting to switch it to DC mode:
.. figure:: images/switch-to-dc-mode.png
:alt: Switch to DC Mode
:width: 320px
The basic configuration looks similar to the AC one. We just exchanged
the :ref:`YetiDriver <everest_modules_YetiDriver>` with the
:ref:`PhyVersoBSP <everest_modules_PhyVersoBSP>` driver and connected the
*connector_1* interface for *evse_board_support* to EvseManager. Note
that *connector_lock* and *ac_rcd* are no longer needed on DC.
The second port of the phyVERSO will not be used here but could be
connected to a second EvseManager. Check the configuration options and
especially verify serial port, baud rate, capabilities and reset GPIO.
They should be correct on the default phyVERSO Board.
.. figure:: images/dc-basic-connection.png
:alt: Basic DC Connection
:width: 700px
The power meter has been replaced by a
:ref:`LEM driver <everest_modules_LemDCBM400600>` , which is a power
meter often used for public DC charging. It is connected via ethernet,
so no SerialCommHub is needed. Verify the correct target IP is set.
Then, we will need to add the additional hardware drivers required for
DC charging:
.. figure:: images/dc-additional-hardware-drivers.png
:alt: Additional DC Hardware Drivers
:width: 750px
For the DC power supply, a
:ref:`Huawei driver <everest_modules_Huawei_R100040Gx>` was added here
to the *power_supply_DC* requirement of EvseManager. Set the correct CAN
device. As isolation monitor, the
:ref:`Bender isoCHA driver <everest_modules_Bender_isoCHA425HV>` was added.
As it is a modbus device, it requires a SerialCommHub again - the same way
as the GenericPowermeter in the AC configuration. Make sure the settings
for the serial port are correct.
Both of the AC and DC example configs will not yet charge a car. They
still need two things from the other subsystems:
- Energy from the energy management system
- Authorization from the Auth subsystem. If you don't need any
Authorization, you can also disable this requirement by setting
“disable_authentication: true” for EvseManager.
Authentication subsystem
========================
Let's add a simple authentication subsystem to the charging part we just
created. Start by adding the :ref:`Auth module <everest_modules_Auth>` .
It is the central logic core of this subsystem and manages all incoming
tokens, validations and reservations. Connect it to the *evse/evse_manager*
implementation on EvseManager. Through this interface, it will authorize
the charging sessions. If you have multiple charging ports (multiple EvseManagers)
you can connect all of them to the same Auth module. The Auth module
will then manage multiple ports.
.. figure:: images/multiple-connections.png
:alt: Authentication Subsystem
:width: 750px
To do anything useful, the Auth module requires two things:
1. :doc:`Auth token providers </reference/interfaces/auth_token_provider>`:
They are sources of auth tokens, e.g. RFID readers etc that output tokens
(but do not know whether they are valid or not).
2. :doc:`Auth token validators </reference/interfaces/auth_token_validator>`:
They can tell whether a token is valid or not.
Move the *token_provider* requirement to the right. The first auth token
provider that we will connect is the *auth_token_provider*
implementation of the EvseManager. This is needed for features such as
“Plug and Charge” and “Autocharge”, where the EV is used as a token to
authenticate the charging session.
----
.. figure:: images/add-pnc-autocharge.png
:alt: Add Plug-and-Charge and Autocharge Feature
:width: 350px
----
Next, we can connect an RFID reader as a second auth token provider:
----
.. figure:: images/add-rfid-reader.png
:alt: Add RFID Reader
:width: 380px
--------------
Now, let's add a very simple token validator:
--------------
.. figure:: images/add-token-validator.png
:alt: Add Token Validator
:width: 500px
--------------
The :ref:`LocalAllowlistTokenValidator <everest_modules_LocalAllowlistTokenValidator>`
module takes a simple ASCII file (see config) with one line per token.
All tokens listed in this file are considered valid, all others are invalid.
With this Auth system, we already have a very simple version that can be
used to authenticate RFID tokens, that have been previously added to the
local whitelist file.
OCPP sub-system
==================
Especially for public charging stations, authentication is done via a
cloud backend instead of a simple local whitelist. For this, we use OCPP
1.6 in this example. OCPP 2.0.1 can be used in a similar way by using
the OCPP201 module instead.
OCPP is both a token provider and a token validator.
It provides tokens when a *RemoteStart* /
*RequestStartTransactionRequest* command is issued, and it is used as a
token validator if e.g. an RFID or Plug&Charge contract should be
validated in the CSMS. So we will connect both of those connections and
remove the LocalWhiteListTokenValidator. OCPP has its own internal local whitelist
and authorization cache implementation, that is according to the standard:
.. figure:: images/remove-whitelist.png
:alt: Remove Whitelist
:width: 650px
OCPP requires several connections. Let's go through them step by step:
- :doc:`Auth token providers </reference/interfaces/auth_token_provider>`
and :doc:`Auth token validators </reference/interfaces/auth_token_validator>`
are the main ones for remote start and token validation functionality.
Connect them to the Auth module.
- :doc:`auth interface </reference/interfaces/auth>` needs to be connected to
the *Auth* module. This connection is mostly used to set the connection
timeout setting via the OCPP protocol.
- :doc:`reservation interface </reference/interfaces/reservation>` is used to
reserve/cancel reservations of connectors via OCPP from the CSMS.
- OCPP also requires a connection to the
:ref:`EvseSecurity <everest_modules_EvseSecurity>` module, which
is now shared between OCPP and EvseV2G. OCPP requires it to load the
certificate / keys for TLS to the CSMS. OCPP can also update/install
certificates for both OCPP and ISO 15118 from the CSMS.
- OCPP requires a helper module for system-specific implementations
(OTA update, logfile collection and upload). Here, we use
:ref:`Linux_Systemd_Rauc <everest_modules_Linux_Systemd_Rauc>`
from EVerest, which is the default implementation using systemd for
log collection and RAUC for OTA updates. This in turn requires a
:ref:`PersistentStore <everest_modules_PersistentStore>` module,
which is shared here with the EvseManager.
For more detailed information about the OCPP configuration, check out the
following resources:
- :ref:`OCPP1.6 module documentation <everest_modules_OCPP>`
- :ref:`OCPP2.0.1 module documentation <everest_modules_OCPP201>`
- :doc:`OCPP1.6 tutorial </tutorials/ocpp16>`
- :doc:`OCPP2.0.1 tutorial </tutorials/ocpp2>`
Now, we have a configuration that can be used in public environments. It
supports authentication via OCPP for RFID tags, and - since the LEM
power supply is used on the DC port - German Eichrecht compliant
metering with OCMF-signed meter values forwarding to the cloud.
Energy management subsystem
====================================
The last subsystem missing is the Energy management. In EVerest, the
energy management distributes energy between charging ports. It is also
needed if only one charging port exists.
Please refer to the
:doc:`Energy Management documentation </explanation/energymanagement/index>`
for a detailed explanation of how to set up the energy management
subsystem.
Multiple connectors
===================
In order to support multiple charging ports, the charging subsystem will
need to be loaded multiple times (also duplicating all direct
dependencies), but Auth and Energy management subsystems are used only
once.
Here is an example for two charging ports (leaving out the dependencies
of each functional block / most connections for clarity):
.. figure:: images/multiple-connectors.png
:alt: Multiple Connectors
:width: 750px
Most drivers implement only a single instance. So e.g. drivers for
isolation monitors, SLAC, ISO protocol etc all need to be duplicated
when EvseManager is duplicated.
A few driver modules also have two implementations of one interface. A
good example is the PhyVersoBSP, which is the driver for a dual port
controller in one module - so it supplies two separate CP pins etc to
two EvseManagers (again removing most other modules for clarity):
.. figure:: images/phyverso-bsp.png
:alt: phyVERSO BSP
:width: 600px
----
**Authors**: Cornelius Claussen, Piet Gömpel

View File

@@ -0,0 +1,478 @@
.. _exp_detail_module_concept:
#########################
EVerest Modules in Detail
#########################
This section gives you a bunch of theoretical input about the EVerest module
concept.
Other ways to approach the concepts of EVerest module development are:
1. For a very first glance and understanding of EVerest modules, try to check
the :ref:`Understanding EVerest Modules section <htg_getting_started_sw_understand_modules>` in the Quick
Start Guide.
2. A more hands-on intro to EVerest module development:
:doc:`Develop New EVerest Modules </tutorials/develop-new-module>`.
********
Overview
********
EVerest follows a microservice-like architecture.
A typical EVerest deployment consists of the following components:
* Several **module instances**, which are separate processes offering some
type of functionality;
* A **MQTT broker** (mosquitto), which provides the backbone of communication
between module instances;
* A **manager process**, which orchestrates the execution of module instances.
.. image:: images/everest-manager-modules-mqtt.png
:width: 360px
:align: center
.. note::
EVerest provides integration for modules written in C++, Javascript, Python
or Rust.
We will use notation for C++ below.
********
Concepts
********
Modules and module instances
============================
A **module** is a program providing a specific functionality within EVerest,
e.g. driving a particular type of hardware.
Each module has a unique **name**, assigned at development time.
Modules can accept **config** values, which can be set when executing the
module.
In an EVerest deployment, you launch **instances** of these modules, each
instance is a separate process.
There can be multiple instances of the same module, e.g. for driving multiple
devices of the same type.
Each module instance has a unique **instance ID**, independent of the module
name, which is assigned when configuring the deployment.
Different instances of the same module can also have different config values
set when configuring the deployment.
.. image:: images/everest-modules-and-instances.png
:width: 600px
:align: center
Interfaces: Communication between modules
=========================================
Think of interfaces as specific protocols (or languages) that modules can use
to communicate with each other.
An interface is a set of:
* **Commands**: Synchronous (remote) procedure calls with defined arguments
and return values - in short **CMDs**;
* **Variables**: Useful for asynchronous communication, a variable is a topic
(typically some particular value that changes over time) which users of the
interface can subscribe to, and which the module publishes updates on.
In short **VARs**.
For example, imagine a simple interface offered by a power supply module.
Turning power on or off could be implemented as CMDs, callable by other
modules.
The voltage and current values at the power supply could be implemented as a VAR -
the power supply module publishes this VAR regularly,
and other modules could subscribe to the VAR and observe its value over time.
Providing and requiring interfaces
==================================
Each module defines a set of interface implementations it **provides** to
other modules, and a set of interface implementations it **requires** from
other modules.
Providing interface implementations
-----------------------------------
A module is not simply declared as an implementor of an interface.
Instead, modules have a set of **interface implementations**,
each implementing one interface and having a unique **implementation ID**.
This is done because a module can implement the same interface multiple times.
This is reflected by providing multiple implementations with different IDs for
the same interface.
As an analogy, think of an internet router.
It does not just implement the IP protocol, it has multiple implementations
of it (several Ethernet ports, WLAN antennas, etc.), which can be connected to
different devices, and may even have different purposes, e.g. LAN and WAN
ports.
Requiring interface implementations
-----------------------------------
Just like a module can provide the same interface multiple times, it can also
require multiple implementations of the same interface.
For example, there could be one energy manager component, which communicates
with multiple power supply or EVSE manager modules.
Therefore, modules have a set of **interface requirements** with unique
IDs, each of which is for a particular interface and must be satisfied by
an interface implementation of another module.
Continuing with the router analogy from before, a PC can have
multiple network interfaces - e.g. one WLAN and one Ethernet -
which could easily be connected to different ports on different routers.
The following diagram shows how providing and requiring interface
implementation create relations between modules:
.. image:: images/everest-interfaces-provides-requires.png
:width: 420px
:align: center
Interface communication on the MQTT layer
=========================================
On the MQTT level, interfaces are implemented as follows:
* To listen for incoming CMDs or subscribe to a VAR, a module
subscribes to the corresponding MQTT topic;
* To send a CMD or update to a VAR, a module publishes a message on the
corresponding topic.
The MQTT topic for commands is:
``everest/{module instance ID}/{interface implementation ID}/cmd``
Similarly, the MQTT topic for variables is:
``everest/{module instance ID}/{interface implementation ID}/var``
Note that the path prefix ``everest`` may differ in some end-to-end tests.
Wiring it all together: The run configuration
=============================================
The **run configuration** is a YAML file which specifies the structure of your
deployment.
The run configuration defines the module instances to start:
* Their instance IDs;
* Which modules they are an instance of;
* What to set their configuration values to;
* For each interface requirement of the module instance:
* The instance ID of the module instance which provides the interface;
* The interface implementation ID within the providing module which will be used.
.. _exp-yaml-files:
*************************
Explaining the YAML files
*************************
Now, we will show how the concepts above map to the YAML files
defining modules and interfaces.
Consider the following example: We want two modules, a "ping server" and a
"ping client", to communicate over a "ping interface".
Let us define the ``interfaces/interface_ping.yaml`` first:
.. code-block:: yaml
description: Interface for a ping-pong interaction
cmds: # list of commands in the interface
command_ping: # name of the command
description: Send a ping with a payload to the ping server.
arguments: # list of arguments
payload:
description: An arbitrary string that the server will pong back.
type: string
result: # return value of the command
description: The same payload as the ping
type: string
vars: # list of variables in the interface
var_nping: # name of the variable
description: The number of pings the server has received so far
type: integer
Now, let us define a "ping server" module, which has an implementation of this
interface.
Here is the ``modules/PingServerModule/manifest.yaml`` file:
.. code-block:: yaml
description: Example ping-pong module
config: # list of config values
cfg_publish_number_of_pings: # name of the config value
description: Publish the number_of_pings variable every 5 seconds.
type: boolean
default: false
provides: # list of interface implementations
if_impl_id_ping: # implementation ID
interface: interface_ping # interface name
description: Responds to a ping with a pong
enable_external_mqtt: true # enable this if you want to use the MQTT layer directly in your code
metadata:
license: link-to-your-license.here
authors:
- Max Mustermann, Company Name Here
We can have a "ping client" module, which requires the ``ping_interface``.
Here is the ``modules/PingClientModule/manifest.yaml`` file:
.. code-block:: yaml
description: Example ping-pong client module
requires: # list of interface requirements
requirement_ping_server: # requirement ID
interface: interface_ping # interface name
enable_external_mqtt: true # enable this if you want to use the MQTT layer directly in your code
metadata:
license: link-to-your-license.here
authors:
- Max Mustermann, Company Name Here
And finally, we define a run configuration, where instances of the two modules
connect to each other:
.. code-block:: yaml
settings:
telemetry_enabled: true
active_modules: # list of module instances
instance_id_ping_server: # instance ID
config_module: # list of config parameters
cfg_publish_number_of_pings: true
module: PingServerModule # module which this is an instance of
instance_id_ping_client: # next instance ID
connections: # list of providers for interface requirements
requirement_ping_server: # requirement ID
- implementation_id: if_impl_id_ping # implementation ID
module_id: instance_id_ping_server # module instance ID of the provider
module: PingClientModule # module which this is an instance of
Graphically, this would look as follows:
.. image:: images/everest-runtime-config.png
:width: 480px
:align: center
For a tutorial where you implement and experiment a similar example,
refer to :doc:`Develop New EVerest Modules </tutorials/develop-new-module>`
********************************
Explaining the generated sources
********************************
When starting a project, you will typically use ``ev-cli`` to generate a
source code skeleton.
Here, we will explain the purpose and structure of the files
created by this code generation step.
Interface headers
=================
Using ``ev-cli generate-headers`` for the ``interface_ping`` from above,
three header files are generated::
.
└── build
└── generated
└── include
└── generated
└── interfaces
└── interface_ping
├── Implementation.hpp
├── Interface.hpp
└── Types.hpp
We will not list the contents of these files completely,
but we will explain the contents of the files generally.
``Interface.hpp`` contains a class called ``interface_pingIntf``
(in general, ``${INTERFACE_NAME}Intf``),
which is used when *requiring* the interface.
It contains the following functions:
* ``call_command_ping`` (in general ``call_${COMMAND_NAME}``), to call the
respective command;
* ``subscribe_var_nping`` (in general ``subscribe_${VAR_NAME}``) to register a
callback each time an update to the variable is published.
``${INTERFACE_NAME}Intf`` is essentially a proxy which routes command calls
and variable subscriptions to the EVerest framework.
``Implementation.hpp`` contains an abstract class called
``interface_pingImplBase``
(in general, ``${INTERFACE_NAME}ImplBase``), which is used
when *providing* the interface.
It contains the following functions:
* ``publish_var_nping`` (in general ``publish_${VAR_NAME}``), to publish an
update to the variable;
* ``handle_command_ping`` (in general ``handle_${COMMAND_NAME}``), which is
virtual - this function is called to handle the respective command.
Interface implementations extend ``${INTERFACE_NAME}ImplBase``, and must
implement all command handlers (``handle_${COMMAND_NAME}``).
The EVerest framework takes care of publishing variable updates,
listening for commands, calling the appropriate handler, and sending back its
return value to the caller.
The ``Types.hpp`` file contains custom type definitions.
Module files
============
Using ``ev-cli module create`` for the two modules from above generates
the following new files (we omit the ``manifest.yaml here``)::
.
└── modules
├── PingServerModule
│ ├── CMakeLists.txt
│ ├── PingServerModule.cpp
│ ├── PingServerModule.hpp
│ ├── doc.rst
│ ├── docs
│ │ └── index.rst
│ └── if_impl_id_ping
│ ├── interface_pingImpl.cpp
│ └── interface_pingImpl.hpp
└── PingClientModule
├── CMakeLists.txt
├── PingClientModule.cpp
├── PingClientModule.hpp
├── doc.rst
└── docs
└── index.rst
Focusing on the source and header files, generally, the tool generates:
* One source-header pair describing a class for the whole module:
``${MODULE_NAME}.{cpp, hpp}``;
* One source-header pair describing a class for each interface implementation
in the module: ``${IMPLEMENTATION_ID}/${INTERFACE_NAME}Impl.{cpp, hpp}``
The module class
----------------
The module class, which carries the same name as the module itself, is defined
in ``${MODULE_NAME}.hpp``.
Apart from a constructor (called by the EVerest framework on startup), it has
a few notable members:
* ``config`` of type ``Conf`` (defined in the same file): Config values for
the module;
* ``mqtt``: handle for MQTT communication, if ``enable_external_mqtt`` was
enabled in the manifest;
* ``init()``: Function called by the framework after initializing this module
- you may add code to it to add more initialization steps;
* ``ready()``: Function called by the framework when the deployment is ready
- you may initiate application logic in it;
* For each interface implementation:
``std::unique_ptr<${INTERFACE_ID}ImplBase> p_${IMPLEMENTATION_ID}``
- reference to the interface implementation;
* For each interface requirement:
``std::unique_ptr<${INTERFACE_ID}Intf> r_${REQUIREMENT_ID}`` - use this to
trigger commands or subscribe to variables on the provider.
The header file contains designated areas where further members or other
definitions can be added.
Code added to these areas will be preserved if the headers are
overwritten by the ``ev-cli module update`` command (e.g. if you updated
the module manifest).
``${MODULE_NAME}.cpp`` initially only contains stub implementations of the
``init()`` and ``ready()`` functions, which just call the ``init()`` and
``ready()`` functions in each interface implementation:
.. code-block:: c++
void PingServerModule::init() {
invoke_init(*p_if_impl_id_ping);
}
void PingServerModule::ready() {
invoke_ready(*p_if_impl_id_ping);
}
Further logic can be freely added to this file - ``${MODULE_NAME}.cpp`` is
not overwritten by ``ev-cli module update``, unless the ``--force`` option
is specified.
Definitions related to the module class are placed in the ``module`` namespace.
Interface implementations
-------------------------
For each interface implementation, a class is defined in
``${IMPLEMENTATION_ID}/${INTERFACE_NAME}Impl.hpp``.
This class extends ``${INTERFACE_NAME}ImplBase``, declaring overriding methods
for all command handlers, as well as a few additional notable members:
* ``config`` of type ``Conf`` (defined in the same file): Config values of
the implementation
* ``mod``: reference to the module instance (e.g. to call methods of the
module class);
* ``init()`` and ``ready()``, which have the same semantics as the module
class's ``init()`` and ``ready()``.
Like the module class, interface implementation classes are also
constructed by the framework at startup.
``${IMPLEMENTATION_ID}/${INTERFACE_NAME}Impl.cpp`` initially contains stub
implementations of the
``init()`` and ``ready()`` functions - recall from the previous subsection
that these are called by the module class's ``init()`` and ``ready()``
functions, therefore they are called at (roughly) the same point.
It also contains stubs for the command handlers, which return dummy values -
this way, the code generated by ``ev-cli`` can be built and ran
even if you have not yet written any code.
As was the case for the module class's files, you may freely extend the
``${IMPLEMENTATION_ID}/${INTERFACE_NAME}Impl.cpp`` file as it will not be
overwritten by subsequent ``ev-cli module update`` commands - however,
``${IMPLEMENTATION_ID}/${INTERFACE_NAME}Impl.hpp`` does get overwritten, so
you should only add your changes to the designated areas in that file.
Definitions related to the interface implementation are in the
``module.${IMPLEMENTATION_ID}`` namespace.
Note on concurrency
-------------------
Parts of the module logic may run in parallel. By itself, the EVerest
framework starts:
* One thread to execute the ``ready()`` function of the module class, which
you can freely use to start logic of your own (e.g. an endless loop, or
spawning worker threads);
* A thread pool to handle commands (these will call the command handlers in
interface implementations);
* A thread pool to watch for variable updates the module has subscribed to
(these will call the callbacks you provide to ``subscribe_${VAR_NAME}``)
In general, assume functions called by the framework may be running in
parallel. If data structures need to be shared between such functions
(especially for writing), you should use some form of locking.
------------------------------------------------
Authors: Valentin Dimov, Manuel Ziegler, Piet Gömpel

View File

@@ -0,0 +1,487 @@
.. _exp_dev_tools_edm:
###
edm
###
edm stands for EVerest dependency manager. It helps you orchestrating the
dependencies between the different EVerest repositories.
.. note::
The EDM tool was developed at a time when the EVerest source code was
organized in many different repositories. Since 2026, EVerest has essentially
changed to a (quasi-)mono-repository layout. Manual installation of EDM is
usually not necessary to work with recent versions of EVerest.
Dependency Manager for EVerest
##############################
Install and Quick Start
***********************
To install the **edm** dependency manager for EVerest you have to perform the
following steps.
Please make sure you are running a sufficiently recent version of **Python3 (>=3.6)** and that you are able to install Python packages from source.
See the *python3* command below for upgrading the required packages. Refer to
the
`Python Installing Packages <https://packaging.python.org/tutorials/installing-packages/#requirements-for-installing-packages>`_
documentation for indepth guidance if any problems arise. You may want to create and activate a virtual environment
using `venv <https://docs.python.org/3/library/venv.html>`_
.. code-block:: bash
python3 -m venv venv
source venv/bin/activate
before executing the commands below.
.. code-block:: bash
python3 -m pip install --upgrade pip setuptools wheel jstyleson jsonschema
Python packages needed to run edm
*********************************
The following Python3 packages are needed to run **edm**. If you install edm
using this guide they will be installed automatically.
+ Python >= 3.6
+ Jinja2 >= 3.0
+ PyYAML >= 5.4
Installing edm
**************
Now you can clone this repository and install **edm**:
.. code-block:: bash
git clone https://github.com/EVerest/EVerest.git
cd EVerest/applications/dependency_manager
python3 -m pip install . --break-system-packages
or in short
.. code-block:: bash
python3 -m pip install git+https://github.com/EVerest/EVerest.git@main#subdirectory=applications/dependency_manager --break-system-packages
.. note::
Alternatively, you can also install ``edm`` in a python virtual environment.
Make sure edm is available in your PATH after the installation. You can verify
this by running ``edm --version``.
Next you could run
.. code-block:: bash
edm init --workspace ~/checkout/everest-workspace
This creates a workspace in the ``~/checkout/everest-workspace``
directory from the most recent release of EVerest. If you want the most recent
main you can use:
.. code-block:: bash
edm init main --workspace ~/checkout/everest-workspace
The workspace will have the following structure containing all current
dependencies for EVerest:
.. code-block:: bash
everest-workspace/
├── everest-cmake
├── EVerest
├── everest-dev-environment
├── everest-framework
├── everest-sqlite
├── everest-utils
├── Josev
├── libcbv2g
├── libevse-security
├── libfsm
├── libiso15118
├── liblog
├── libnfc-nci
├── libocpp
├── libslac
├── libtimer
└── workspace-config.yaml
The ``workspace-config.yaml`` contains a copy of the config that was used to create
this workspace.
Enabling CPM_SOURCE_CACHE and setting PATH
******************************************
The EVerest dependency manager uses
`CPM <https://github.com/cpm-cmake/CPM.cmake>`_
for its CMake integration. This means you *can* and **should** set the
``CPM_SOURCE_CACHE`` environment variable. This makes sure that dependencies
that you do not manage in the workspace are not re-downloaded multiple times.
For detailed information and other useful environment variables please
refer to the `CPM Documentation <https://github.com/cpm-cmake/CPM.cmake/blob/master/README.md#CPM_SOURCE_CACHE>`_.
Also set the PATH variable:
.. code-block:: bash
export CPM_SOURCE_CACHE=$HOME/.cache/CPM
export PATH=$PATH:/home/$(whoami)/.local/bin
Building EVerest
****************
Make sure you have installed :doc:`ev-cli <ev-cli>` first.
You can now use the following commands to build the repository EVerest:
.. code-block:: bash
cd ~/checkout/everest-workspace/EVerest
mkdir build
cd build
cmake ..
make -j$(nproc) install
.. _cmake_integration_setup:
Setting up and updating a workspace
###################################
For letting **edm** do the work of setting up an initial EVerest workspace,
do this:
.. code-block:: bash
edm init --workspace ~/checkout/everest-workspace
If you are currently in the everest-workspace directory the following command
has the same effect:
.. code-block:: bash
edm init
For using a dedicated release version, you can do this:
.. code-block:: bash
edm init 2023.7.0
In this example, version 2023.7.0 is pulled from the server. This will only work
if your previous code is not in a "dirty" state.
Using the edm CMake module and dependencies.yaml
################################################
To use edm from CMake you have to add the following line to the top-level
CMakeLists.txt file in the respective source repository:
.. code-block:: bash
find_package(EDM REQUIRED)
To define dependencies you can now add a dependencies.yaml file to your source
repository. It should look like this:
.. code-block:: bash
---
sigslot:
git: https://github.com/palacaze/sigslot
git_tag: v1.2.3
cmake_condition: "EVEREST_DEPENDENCY_ENABLED_SIGSLOT"
options:
- "SIGSLOT_COMPILE_EXAMPLES OFF"
- "SIGSLOT_COMPILE_TESTS OFF"
pugixml:
git: https://github.com/zeux/pugixml
git_tag: v1.15
cmake_condition: "EVEREST_DEPENDENCY_ENABLED_PUGIXML"
If you want to conditionally include some dependencies, e.g. for testing, you can
do this in the following way:
.. code-block:: bash
catch2:
git: https://github.com/catchorg/Catch2.git
git_tag: v3.4.0
cmake_condition: "BUILD_TESTING"
Here *cmake_condition* can be any string that CMake can use in an if() block.
Please be aware that any variables you use here must be defined before a call to
*evc_setup_edm()* is made in your ``CMakeLists.txt``
Additionally you can set the ``EVEREST_MODIFY_DEPENDENCIES`` environment variable
to a file containing modifications to the projects ``dependencies.yaml`` files when
running cmake:
.. code-block:: bash
EVEREST_MODIFY_DEPENDENCIES=../dependencies_modified.yaml cmake -S . -B build
The ``dependencies_modified.yaml`` file can contain something along these lines:
.. code-block:: bash
nlohmann_json:
git: null # this makes edm look for nlohmann_json via find_package
libfmt:
rename: fmt # if find_package needs a different dependency name you can rename it
git: null
catch2:
git_tag: v1.2.3 # select a different git tag for a build
Selective library consumption
#############################
If your external project only needs specific everest-core libraries (e.g.
``liblog``, ``everest-util``, ``everest-io``, ``libocpp``, ``libiso15118``)
without building the full module framework, you can use the
``EVEREST_LIBS_ONLY`` and ``EVEREST_INCLUDE_LIBS`` CMake options.
**CMake options:**
.. list-table::
:header-rows: 1
* - Option
- Default
- Description
* - ``EVEREST_LIBS_ONLY``
- OFF
- Skip modules, applications, config, and code generation. Only build
libraries under ``lib/everest/``.
* - ``EVEREST_INCLUDE_LIBS``
- (empty)
- Semicolon-separated allowlist of libraries to build. Transitive
internal dependencies are resolved automatically. When empty, all
libraries are built.
* - ``EVEREST_EXCLUDE_LIBS``
- (empty)
- Semicolon-separated blocklist of libraries to skip.
**Example: building only liblog, everest-util, and everest-io**
.. code-block:: bash
cmake -S . -B build \
-DEVEREST_LIBS_ONLY=ON \
-DEVEREST_INCLUDE_LIBS="log;util;io"
cmake --build build
Transitive dependencies are resolved automatically. For example, requesting
``io`` will automatically include ``util`` (since ``everest-io`` depends on
``everest-util``).
**Example: building only libocpp**
.. code-block:: bash
cmake -S . -B build \
-DEVEREST_LIBS_ONLY=ON \
-DEVEREST_INCLUDE_LIBS="ocpp"
cmake --build build
This resolves the full dependency chain: ``ocpp`` -> ``log``, ``timer``,
``evse_security``, ``sqlite``, ``cbv2g``.
**Using from an external project's dependencies.yaml:**
.. code-block:: yaml
everest-core:
git: https://github.com/EVerest/everest-core.git
git_tag: 2026.02.0
options:
- "EVEREST_LIBS_ONLY ON"
- "EVEREST_INCLUDE_LIBS log;util;io"
The internal dependency map is defined in ``cmake/ev-lib-dependencies.cmake``.
.. note::
Libraries that depend on framework code generation (``tls``, ``helpers``,
``conversions``, ``slac``, ``external_energy_limits``, ``everest_api_types``)
are **not available** in ``EVEREST_LIBS_ONLY`` mode. Use
``EVEREST_EXCLUDE_MODULES`` instead if you need those libraries.
Framework thread pool scaling policy
####################################
The framework message handler uses a dynamically scaling thread pool for
operation messages such as variable updates, commands, errors, GetConfig and
ModuleReady messages. Its scaling policy can be selected at CMake configure
time.
**CMake options:**
.. list-table::
:header-rows: 1
* - Option
- Default
- Description
* - ``EVEREST_FRAMEWORK_THREAD_POOL_SCALING_POLICY``
- ``latency``
- Selects the policy. Supported values are ``latency``, ``greedy``,
``conservative``, ``fixed_size`` and ``custom``.
* - ``EVEREST_FRAMEWORK_THREAD_POOL_SCALING_LATENCY_THRESHOLD_MS``
- ``50``
- Maximum queued task wait time, in milliseconds, before the ``latency``
policy adds another worker.
* - ``EVEREST_FRAMEWORK_THREAD_POOL_SCALING_LATENCY_TICK_MS``
- ``5``
- Supervisor tick, in milliseconds, for the ``latency`` policy.
* - ``EVEREST_FRAMEWORK_THREAD_POOL_SCALING_FIXED_SIZE_THRESHOLD``
- ``3``
- Queue size threshold at which the ``fixed_size`` policy adds another
worker.
* - ``EVEREST_FRAMEWORK_THREAD_POOL_SCALING_POLICY_CUSTOM_HEADER``
- (empty)
- Header to include when the policy is ``custom``.
* - ``EVEREST_FRAMEWORK_THREAD_POOL_SCALING_POLICY_CUSTOM_TYPE``
- (empty)
- Fully-qualified C++ policy type to use when the policy is ``custom``.
* - ``EVEREST_FRAMEWORK_THREAD_POOL_SCALING_POLICY_CUSTOM_INCLUDE_DIR``
- (empty)
- Additional include directory for the custom policy header.
The built-in policies are:
.. list-table::
:header-rows: 1
* - Policy
- Behavior
* - ``latency``
- Default. Adds workers when queued work has waited longer than the
framework latency threshold.
* - ``greedy``
- Adds workers as soon as backlog is detected.
* - ``conservative``
- Adds workers only when the queue depth significantly exceeds the current
worker count.
* - ``fixed_size``
- Adds workers once the queue size reaches the configured
``EVEREST_FRAMEWORK_THREAD_POOL_SCALING_FIXED_SIZE_THRESHOLD``.
**Example: selecting a built-in policy for a full EVerest build**
.. code-block:: bash
cmake -S . -B build \
-DEVEREST_FRAMEWORK_THREAD_POOL_SCALING_POLICY=latency \
-DEVEREST_FRAMEWORK_THREAD_POOL_SCALING_LATENCY_THRESHOLD_MS=50 \
-DEVEREST_FRAMEWORK_THREAD_POOL_SCALING_LATENCY_TICK_MS=5
cmake --build build
**Example: selecting fixed-size scaling**
.. code-block:: bash
cmake -S . -B build \
-DEVEREST_FRAMEWORK_THREAD_POOL_SCALING_POLICY=fixed_size \
-DEVEREST_FRAMEWORK_THREAD_POOL_SCALING_FIXED_SIZE_THRESHOLD=3
cmake --build build
**Example: using a custom policy**
.. code-block:: bash
cmake -S . -B build \
-DEVEREST_FRAMEWORK_THREAD_POOL_SCALING_POLICY=custom \
-DEVEREST_FRAMEWORK_THREAD_POOL_SCALING_POLICY_CUSTOM_HEADER=my_policy.hpp \
-DEVEREST_FRAMEWORK_THREAD_POOL_SCALING_POLICY_CUSTOM_TYPE=my_project::MyPolicy \
-DEVEREST_FRAMEWORK_THREAD_POOL_SCALING_POLICY_CUSTOM_INCLUDE_DIR=/path/to/include
cmake --build build
A custom policy must provide the same interface as the built-in policies:
.. code-block:: cpp
struct MyPolicy {
static constexpr std::optional<std::chrono::milliseconds> supervisor_tick =
std::nullopt;
static bool should_grow(
std::size_t current_workers,
std::size_t queue_size,
std::optional<std::chrono::steady_clock::time_point> oldest_arrival);
};
Create a workspace config from an existing directory tree
#########################################################
Suppose you already have a directory tree that you want to save into a config
file. You can do this with the following command:
.. code-block:: bash
edm --create-config custom-config.yaml
This is a short form of:
.. code-block:: bash
edm --create-config custom-config.yaml --include-remotes https://github.com/EVerest/*
and only includes repositories from the EVerest namespace. You can add as many
remotes to this list as you want.
For example, if you only want to include certain repositories you can use the
following command.
.. code-block:: bash
edm --create-config custom-config.yaml --include-remotes https://github.com/EVerest/everest* https://github.com/EVerest/ext-switchev-iso15118.git
If you want to include all repositories, including external dependencies, in
the config you can use the following command:
.. code-block:: bash
edm --create-config custom-config.yaml --external-in-config
.. _git_information_at_a_glance:
Git information at a glance
###########################
You can get a list of all git repositories in the current directory and their
state using the following command:
.. code-block:: bash
edm --git-info --git-fetch
If you want to know the state of all repositories in a workspace you can use
the following command:
.. code-block:: bash
edm --workspace ~/checkout/everest-workspace --git-info --git-fetch
This creates output that is similar to the following example:
.. code-block:: bash
[edm]: Git info for "~/checkout/everest-workspace":
[edm]: Using git-fetch to update remote information. This might take a few seconds.
[edm]: "everest-dev-environment" @ branch: main [remote: origin/main] [behind 6] [clean]
[edm]: "everest-framework" @ branch: main [remote: origin/main] [dirty]
[edm]: "everest-deploy-devkit" @ branch: main [remote: origin/main] [clean]
[edm]: "libtimer" @ branch: main [remote: origin/main] [dirty]
[edm]: 2/4 repositories are dirty.
Further information can be seen as shell output by calling edm with parameter
**-h** or **--help**.
----
**Authors**: Kai-Uwe Hermann, Stefan Wahren, Andreas Heinrich, Manuel Ziegler

View File

@@ -0,0 +1,229 @@
.. _exp_dev_tools_evcli:
######
ev-cli
######
``ev_cli`` has mainly two purposes:
- Generate C++ header files for defined interfaces
- Create/update auto generated files for modules (C++ only).
Generating the header files is done in the build process of ``EVerest``. For this
you don't need to install ``ev-dev-tools`` by yourself, it happens automatically during the build process.
For creating and updating auto generated files for modules you need to install ``ev-dev-tools`` to use it during development.
.. _evcli_install:
*******
Install
*******
There are two possibilites to use/install ``ev-dev-tools``.
You can use the automatically installed version from python venv in build directory or
you can install the python package manually.
Use automatically installed `ev-dev-tools` from python venv
===========================================================
Build ``EVerest`` as explained in the :ref:`Quick Start Guide <htg_getting_started_sw>`.
This will create a python venv in your build directory.
You can activate it with:
.. code-block:: bash
cd build/
source ./venv/bin/activate
Install `ev-dev-tools` manually
===============================
To install ``ev_cli`` manually from github repository:
.. code-block:: bash
python3 -m pip install git+https://github.com/everest/EVerest.git@main#subdirectory=applications/utils/ev-dev-tools
*****************************
ev-cli command line interface
*****************************
The ``ev_cli`` package comes with a command line tool, named ``ev-cli``.
It has the following subcommands
- ``module``:
auto generation and update of EVerest modules from its interface and
manifest definitions
- ``interface``:
auto generation of C++ header files for defined interfaces
- ``helpers``:
utility commands
- ``types``:
auto generation of C++ header files for types
To see a list of all subcommands and options, simply call:
.. code-block:: bash
ev-cli --help
The `module`, `interface` and `types` commands have the following options in
common:
- ``--work-dir``:
work directory which also contains the manifest definitions (default: ``.``)
- ``--everest-dir``:
root directory of EVerest core or any directory containing interface
and module definitions (default: ``.``)
- ``--schemas-dir``:
schemas directory of the EVerest framework, containing the schema
definitions (default: ``../everest-framework/schemas``)
- ``--clang-format-file``:
if C++ output should be formatted, set this to the path of the
``.clang-format`` file
Generating C++ header files for defined interfaces
==================================================
Assuming that the interface definitions in yaml format are located at
``./interfaces/*.yaml``, simply::
ev-cli interface generate-headers
This will generate the c++ header files for all interfaces and output them
to ``./generated/include/generated``. To generate only a single interface, call::
ev-cli interface generate-headers InterfaceName
For each interface an ``Implementation.hpp`` and ``Interface.hpp``
header file will be generated. The former represents the `implementers`
view, and the latter the `users` view of the interface, when used in a
module.
Creating and updating auto generated files for modules (C++ only)
=================================================================
Assuming the modules are located at ``./modules`` and the initial
skeleton for a module named `Example` with its manifest in
``./modules/Example/manifest.yaml`` should be created, call::
ev-cli module create Example
This will create the following files inside the ``./modules/Example``
subdirectory
- ``CMakeLists.txt``:
build instruction file for CMake
- ``ld-ev.hpp``/``ld-ev.cpp``:
glue code files for this module to get hooked up by the EVerest
framework
- ``Example.hpp``/``Example.cpp``:
header and source file for the module
Furthermore, for each interface provided by the module a subdirectory
with the name of the `interface id` will be created. If, for example,
the manifest looks like:
.. code-block:: yaml
description: Example module
provides:
main:
description: SampleInterface implementation
interface: SampleInterface
# ...
# ...
a subdirectory named ``main`` will be created, including two files
``SampleInterfaceImpl.hpp`` and ``SampleInterfaceImpl.cpp``. The header
file declares the implementation of `SampleInterface`, which derives
from the auto generated interface header files from the previous
subsection.
Now it is up to the user to implement logic in the module and interface
implementation `cpp` source files.
If the modules' ``manifest.yaml`` or interface definitions, used by the
module, change, you can update the generated files by using::
ev-cli module update Example
**Note**:
1.
``cpp`` source files will never be changed or overwritten by the
`update` subcommand. The `create` subcommand only resets / overrides
the files when using the ``--force`` option
2.
``hpp`` header files and the ``CMakeLists.txt`` file will get
updated, if its interface dependencies definitions change and the
`update` subcommand is used. You can force an update by using the
``--force`` option. During an update, the sections marked like::
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
.....
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
will be kept. If you want to completely reset / override these
files, you need to recreate the using `create` subcommand with the
``--force`` option.
3.
Generated files will never be deleted. So make sure, you do this if
you, for example, change the interface ids or remove interfaces from
the module
These additional options might be useful for the `create` and `update`
subcommands:
1. ``--force``:
force creation or update
2. ``--diff``:
don't touch anything, only show a `diff` of what would be changed
3. ``--only``:
this option takes a comma separated list of files, that should be
touched only. This is especially helpful, if you want to recreate
only a single interface implementation ``cpp`` file, because you
changed the corresponding interface a lot. To get a list of possible files, you can simply call::
ev-cli module create Example --only which
this would output for the above mentioned example::
Available files for category "core"
cmakelists
ld-ev.hpp
ld-ev.cpp
module.hpp
module.cpp
Available files for category "interfaces"
main.hpp
main.cpp
So calling::
ev-cli module create Example --only main.cpp,cmakelists --force
would recreate the ``CMakeLists.txt`` and the
``main/SampleInterfaceImpl.cpp`` files, whereas::
ev-cli module update Example --only module.hpp
would update only the module header file ``Example.hpp``.
----
**Authors**: Kai-Uwe Hermann, Andreas Heinrich, Manuel Ziegler, Christoph Burandt

View File

@@ -0,0 +1,118 @@
##############################################
Internals of the EVerest Development Container
##############################################
This document explains the internal working of the EVerest
development container (devcontainer) for different setup variants
and how things are connected.
For a more hands-on explanation, consider reading:
- :doc:`Tutorial: Setup the EVerest Development Container </tutorials/setup-devcontainer/index>`
- :doc:`How-to Guide: How to use a development container for EVerest development and sil testing </how-to-guides/devcontainer-usage/index>`
*******************************
The Docker Compose Project Name
*******************************
The Docker Compose project name determines how containers are
named and grouped. By default, it uses the
**current folder name with _devcontainer suffix**
(consistent with VSC behavior), but can be customized.
Set the environment variable ``DOCKER_COMPOSE_PROJECT_NAME``
to your desired project name before building/starting the devcontainer.
For example:
.. code-block:: bash
DOCKER_COMPOSE_PROJECT_NAME=my-project ./applications/devrd/devrd Start
This will name the containers with the pattern:
``{project_name}-{service}-1``, where
- ``{project_name}`` is the value of ``DOCKER_COMPOSE_PROJECT_NAME``
- ``{service}`` is the service name defined in the docker-compose files
- ``1`` is the instance number (default is 1)
*********************
Environment Variables
*********************
You can generate an ``.env`` file with auto-detected values using this command:
.. code-block:: bash
./applications/devrd/devrd env
This will create a ``.devcontainer/.env`` file with the following content:
.. code-block:: bash
# Auto-generated by setup script
ORGANIZATION_ARG=EVerest
REPOSITORY_HOST=github.com
REPOSITORY_USER=git
COMMIT_HASH=<..>
EVEREST_TOOL_BRANCH=main
UID=<..>
GID=<..>
HOST_WORKSPACE_FOLDER=<..>
These variables are automatically mapped in the container to the following environment variables:
- ``ORGANIZATION_ARG``: Maps to ``EVEREST_DEV_TOOL_DEFAULT_GIT_ORGANIZATION``
- ``REPOSITORY_HOST``: Maps to ``EVEREST_DEV_TOOL_DEFAULT_GIT_HOST``
- ``REPOSITORY_USER``: Maps to ``EVEREST_DEV_TOOL_DEFAULT_GIT_SSH_USER``
- ``EVEREST_TOOL_BRANCH``: Maps to ``EVEREST_TOOL_BRANCH``
- ``HOST_WORKSPACE_FOLDER``: The directory mapped to ``/workspace`` inside the container
Use this mechanism if you have a different organization or git host or user.
This is useful if you have forked and you are hosting your development outside github.
************************
Workspace Folder Mapping
************************
The ``/workspace`` directory inside the container can be mapped
to any folder on your host system. The workspace folder is
determined in the following priority order:
1. **Command line option**: ``-w`` or ``--workspace`` flag
2. **Environment variable**: ``HOST_WORKSPACE_FOLDER`` environment variable
3. **`.env` file**: ``HOST_WORKSPACE_FOLDER`` value in ``.devcontainer/.env``
4. **Current directory**: Falls back to the current working directory
The ``.devcontainer`` directory (containing ``.env`` and Docker Compose files)
is always located relative to where the ``devrd`` script is installed.
This allows you to:
- Run ``devrd`` from any directory in your workspace
- Use a single ``devrd`` installation to manage multiple workspaces by changing ``HOST_WORKSPACE_FOLDER`` in the ``.env`` file
One can set workspace mapping via command line:
.. code-block:: bash
./applications/devrd/devrd env -w /path/to/workspace
Or edit ``.devcontainer/.env`` directly and set ``HOST_WORKSPACE_FOLDER``
Then run from anywhere:
.. code-block:: bash
cd /path/to/workspace/subfolder
../applications/devrd/devrd start # Works correctly, uses workspace from .env file
***************
Troubleshooting
***************
See the :doc:`separate troubleshooting section </tutorials/setup-devcontainer/troubleshooting>` for help
on devcontainer-specific issues.
----
**Authors:** Florian Mihut, Andreas Heinrich

View File

@@ -0,0 +1,122 @@
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="361px" height="351px" viewBox="-0.5 -0.5 361 351" content="&lt;mxfile&gt;&lt;diagram id=&quot;LkL3k23o4wMPku4sNzY7&quot; name=&quot;Page-1&quot;&gt;5ZfBcpswEIafhjsgm9jH2CXpITl5pj2rsEVqBcvIwth9+opoZaySNJnpxM64F4/07Qppf/3egYit6/295q14xBJUlMblPmKfojRdJjP7O4CDAxmbO1BpWTqUjGAjfwHBmGgnS9gGiQZRGdmGsMCmgcIEjGuNfZj2HVW4a8srmIBNwdWUfpWlEY4u5vHIP4OshN85iSlSc59MYCt4if0JYnnE1hrRuFG9X4MatPO6uHV3L0SPB9PQmLcsSN2CHVcd1UbnMgdfLJS2dpqiNgIrbLjKR7rS2DUlDE+M7WzMeUBsLUws/AHGHOgieWfQImFqRVFoytvhWuy0wQYcuZNK0SOnVVGhW+x0QeckTxmuK6CszKGhgpNlpMQ9YA1GH2yCBsWN3IW3y8kk1TFv1NEOSMrnZWVXLeviUrLOnpE1U/ZUq292UA2DtdRFJ43NWmngP0EfM7RP8cTudlyWsdsotfvHrBWTqwovohfSwKblTwL1tr+For8o6g60gf1fBaMom1Oh1B5n1Cz6sdckvoGIkz6Txf8u8fx6nJtNnbu8lHOzd3MuSz+Qc1MWOjdZntG6N9dj3cXUuv7d5/zeXfwf3p3dXNC7y9c1zr9s8uRtuibZB9L1z57A2Bl19f+a14RNr8Cw7ymsnY4fJU+xky87lv8G&lt;/diagram&gt;&lt;/mxfile&gt;">
<defs/>
<g>
<path d="M 180 60 L 180 105 L 60 105 L 60 150" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 180 60 L 180 105 L 300 105 L 300 150" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
<rect x="120" y="0" width="120" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 30px; margin-left: 121px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
<b>
Circuit Breaker
<br/>
</b>
63A, 3ph
</div>
</div>
</div>
</foreignObject>
<text x="180" y="34" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
Circuit Breaker...
</text>
</switch>
</g>
<path d="M 60 210 L 60 290" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
<rect x="0" y="150" width="120" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 180px; margin-left: 1px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
<b>
Circuit Breaker
<br/>
</b>
32A, 3ph
</div>
</div>
</div>
</foreignObject>
<text x="60" y="184" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
Circuit Breaker...
</text>
</switch>
</g>
<path d="M 300 210 L 300 290" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
<rect x="240" y="150" width="120" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 180px; margin-left: 241px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
<b>
Circuit Breaker
<br/>
</b>
32A, 3ph
</div>
</div>
</div>
</foreignObject>
<text x="300" y="184" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
Circuit Breaker...
</text>
</switch>
</g>
<rect x="0" y="290" width="120" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 320px; margin-left: 1px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
<b>
EVSE1
<br/>
</b>
16A, 3ph
</div>
</div>
</div>
</foreignObject>
<text x="60" y="324" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
EVSE1...
</text>
</switch>
</g>
<rect x="240" y="290" width="120" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 320px; margin-left: 241px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
<b>
EVSE2
<br/>
</b>
32A, 3ph
</div>
</div>
</div>
</foreignObject>
<text x="300" y="324" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
EVSE2...
</text>
</switch>
</g>
</g>
<switch>
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
<a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank">
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
Text is not SVG - cannot display
</text>
</a>
</switch>
</svg>

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@@ -0,0 +1,256 @@
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="580px" height="471px" viewBox="-0.5 -0.5 580 471" content="&lt;mxfile&gt;&lt;diagram id=&quot;LkL3k23o4wMPku4sNzY7&quot; name=&quot;Page-1&quot;&gt;3VnLctsgFP0abz1Cr1jL1HHSzjStZ7Jou/JQiUi0WGgQfqhfXxTAeqA0jiNLbrzwiANc4JzDRdgTZ77e3zGYJfc0QmRiW9F+4txMbDsArvgugUICvuNJIGY4khCogAf8BynQUugGRyhvNOSUEo6zJhjSNEUhb2CQMbprNnukpDlqBmNkAA8hJCb6DUc8kejMsyr8I8JxokcGlqpZQ91YAXkCI7qrQc5i4swZpVw+rfdzREruNC+y3+0ztYeJMZTyYzrYssMWko1am5oXL/RiUSTWroqU8YTGNIVkUaEfGN2kESojWqJUtflMaSZAIMBfiPNCCQk3nAoo4WuialEaXZeyiGJKUySRW0yICmmuSi00pxsWqnkqT3HIYqRa+RIqV1Drppi4Q3SNOCtEA4YI5HjbVBcqk8SHdhWP4kFR2U2r865pnY1Fq9tBq0/ErD78FA9x+bBIEYuLLyLjHKqYrtOIGObQvkwwK5UnME1XGcWCjLZYTSl2CeboIYNPFO1EhmvS/iytW8Q42v+TMlXreGqpKkG6Kl3sqmwDdApJapnGt95OMgheZ15lrb5955u+A2As4/n9Gw9meJXj9PcKGOwO7TbbaboNBAPaTccd2W6zDrvZY9ltdka7mewObTf3akS7dSW3NrXbHN3DVLzEseO4RaLDai17XOB2dr0hT4+u7dwrwZdn4EEJfu0r+5nypT6L6wkzGCtf6sn0mTBpmGWXekA79pCG6yJ3BMPZHSe0NZrjunZhb467vBQ3qOOAeX2+Xn4yTVfZKiQwz3HYtp0Y97ui7KnwoyxMPV282dcrb4qX7HjKTws1vrwOujR2tEHVCEt5TdVyAX1BLPQh0ZJB7qTl4XJ7e2wgqxVIcmAEepL0sOzjVDZv81/ny+X4Ml+Kpk5TCsc7UVNxekyt2gc0w7rB1JudTWTz5nySyJculh+4Bqu+f5pe3hGxnlGoiqUb0sfHHL1ZxateEvL/JqK4mU59L6g+V/0I+lLcgcU1f214l1vUa73S+P2I235TeiHsWbUVxeqvI9m8+v/NWfwF&lt;/diagram&gt;&lt;/mxfile&gt;">
<defs/>
<g>
<path d="M 291 60 L 291 105 L 171 105 L 171 150" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 291 60 L 291 105 L 411 105 L 411 150" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
<rect x="231" y="0" width="120" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 30px; margin-left: 232px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
<b>
EnergyNode
<br/>
</b>
grid_connection_point
</div>
</div>
</div>
</foreignObject>
<text x="291" y="34" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
EnergyNode...
</text>
</switch>
</g>
<path d="M 171 210 L 171 280" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
<rect x="111" y="150" width="120" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 180px; margin-left: 112px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
<b>
EnergyNode
<br/>
</b>
api_sink_1
</div>
</div>
</div>
</foreignObject>
<text x="171" y="184" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
EnergyNode...
</text>
</switch>
</g>
<path d="M 411 210 L 411 280" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
<rect x="351" y="150" width="120" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 180px; margin-left: 352px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
<b>
EnergyNode
<br/>
</b>
api_sink_2
</div>
</div>
</div>
</foreignObject>
<text x="411" y="184" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
EnergyNode...
</text>
</switch>
</g>
<rect x="111" y="410" width="120" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 440px; margin-left: 112px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
<b>
EvseManager
<br/>
</b>
evse_manager_1
</div>
</div>
</div>
</foreignObject>
<text x="171" y="444" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
EvseManager...
</text>
</switch>
</g>
<rect x="351" y="410" width="120" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 440px; margin-left: 352px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
<b>
EvseManager
<br/>
</b>
evse_manager_2
</div>
</div>
</div>
</foreignObject>
<text x="411" y="444" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
EvseManager...
</text>
</switch>
</g>
<path d="M 171 340 L 171 410" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
<rect x="111" y="280" width="120" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 310px; margin-left: 112px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
<b>
EnergyNode
<br/>
</b>
ocpp_sink_1
</div>
</div>
</div>
</foreignObject>
<text x="171" y="314" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
EnergyNode...
</text>
</switch>
</g>
<path d="M 411 340 L 411 410" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
<rect x="351" y="280" width="120" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 310px; margin-left: 352px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
<b>
EnergyNode
<br/>
</b>
ocpp_sink_2
</div>
</div>
</div>
</foreignObject>
<text x="411" y="314" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
EnergyNode...
</text>
</switch>
</g>
<path d="M 17 180 L 104.63 180" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 109.88 180 L 102.88 183.5 L 104.63 180 L 102.88 176.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 180px; margin-left: 64px;">
<div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">
API
</div>
</div>
</div>
</foreignObject>
<text x="64" y="183" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle">
API
</text>
</switch>
</g>
<path d="M 7 310 L 104.63 309.61" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 109.88 309.58 L 102.9 313.11 L 104.63 309.61 L 102.87 306.11 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 310px; margin-left: 59px;">
<div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">
OCPP
</div>
</div>
</div>
</foreignObject>
<text x="59" y="313" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle">
OCPP
</text>
</switch>
</g>
<path d="M 571 309.66 L 477.37 309.66" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 472.12 309.66 L 479.12 306.16 L 477.37 309.66 L 479.12 313.16 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 310px; margin-left: 521px;">
<div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">
OCPP
</div>
</div>
</div>
</foreignObject>
<text x="521" y="313" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle">
OCPP
</text>
</switch>
</g>
<path d="M 571 179.66 L 477.37 179.66" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 472.12 179.66 L 479.12 176.16 L 477.37 179.66 L 479.12 183.16 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 180px; margin-left: 521px;">
<div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">
API
</div>
</div>
</div>
</foreignObject>
<text x="521" y="183" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle">
API
</text>
</switch>
</g>
<path d="M 451 29.66 L 357.37 29.66" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 352.12 29.66 L 359.12 26.16 L 357.37 29.66 L 359.12 33.16 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 30px; margin-left: 401px;">
<div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">
OCPP
</div>
</div>
</div>
</foreignObject>
<text x="401" y="33" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="11px" text-anchor="middle">
OCPP
</text>
</switch>
</g>
</g>
<switch>
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
<a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank">
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
Text is not SVG - cannot display
</text>
</a>
</switch>
</svg>

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -0,0 +1,161 @@
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="361px" height="481px" viewBox="-0.5 -0.5 361 481" content="&lt;mxfile&gt;&lt;diagram id=&quot;LkL3k23o4wMPku4sNzY7&quot; name=&quot;Page-1&quot;&gt;7VpNc5swEP01vmYkBAKOiWOnh2YmM5lpm6NiFKDByCPLsd1fXwECxEcC8Qd23OaQkR4r0O6+1T5sj9B4vrnjZBHcM49GIwN4mxG6HRmGC035PwG2GYCRlQE+D70MgiXwGP6hCgQKXYUeXVYMBWORCBdVcMbimM5EBSOcs3XV7IVF1acuiE8bwOOMRE30Z+iJIEMdC5T4Nxr6Qf5kCNSVOcmNFbAMiMfWGoQmIzTmjIlsNN+MaZTELo9Ltm76ztViY5zGos8CI1vwRqKV8k3tS2xzZ6knfVdTxkXAfBaTaFKiN5ytYo8mdwRyVtp8Z2whQSjB31SIrUokWQkmoUDMI3WVxt51khY5jVlMM2QaRpG6ZdMr5eiSrfhM7VNxShDuU2WFMyjxQFumInFH2ZwKvpUGnEZEhG/V7BJFEr+wK+MoByqU7WFFFx1W51Rh7QijclKPwCYUvxJvrwxLTZ+U88n4dqNPtvkkltvSVyXzpyKmclKuS2f5wqXg7LU4EVCKEC5qOUgxLQtalmYRWS7DWSVRyUNf5HDMIsZTnxEGxIV28UDtinFrY7BPavNzafjcmi0lgyO5rZtnOfCTwTjks1UopNUNp+SV8sKC5yY5Ip9WLMPoemTI5wO0CBr8qRbZOggFfVyQNEJr2buqdHo3qm+UC7r5MGDqKsKq1anWV/S0ddlIYJ6FQGsiGOwfY+tyjiXc5K57KupCtPOxZA9wLH3+gAHAIQC0HTAAWDZoP+t2TZt56LSlS6XHZKsZLFgYi6V254cE0OoSVesSoZpwqtu7H9rLQbaDkkCFK/04hQdudRBVSXUFZJ/vIpacPVAeSr8of7cJdtV7pUmWDNX7ZAtHiQFA6mmDo3gKQP8meLaMNOwaw0AHIx34kf3+jHQOw0jYl5HVUw50kHEo8XVE3p2sgeGjaS9knJH2MsxaReEBtZd9OdrLaVIXnuy9If8sabBGaX9Z8dWSt/NodRb8nPgyrSOLL3tnTu0m6F2Aa+JL0vog4qunuOo6F47UBM+XkZ8UX6ZtHld8uYdhZG/xZV+0+DqrDub8G+rLdE6oviBs+H/cE/3rqgS3WRsn+x7F7S6NyY/HCexXDhCfUTnUX0YsMGQ5WAOL5q/7iWXeF87iC7B8M10FYVxAfxi0IIwD9Yf/+ioLZ0vRHFzY985tn6KJKfe39yQmfimu9FLZrzxqYYbPBFIjwVksNPwl/Ws/xfDkenqYQqt/Bekcr87ktPwtS/baU/4gCE3+Ag==&lt;/diagram&gt;&lt;/mxfile&gt;">
<defs/>
<g>
<path d="M 180 190 L 180 235 L 60 235 L 60 280" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 180 190 L 180 235 L 300 235 L 300 280" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 150 130 L 150 70.1" fill="none" stroke="#2d7600" stroke-width="3" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 150 63.35 L 154.5 72.35 L 150 70.1 L 145.5 72.35 Z" fill="#2d7600" stroke="#2d7600" stroke-width="3" stroke-miterlimit="10" pointer-events="all"/>
<rect x="120" y="130" width="120" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 160px; margin-left: 121px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
<b>
Circuit Breaker
<br/>
</b>
63A, 3ph
</div>
</div>
</div>
</foreignObject>
<text x="180" y="164" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
Circuit Breaker...
</text>
</switch>
</g>
<path d="M 60 340 L 60 420" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 90 280 L 90 260 Q 90 250 100 250 L 140 250 Q 150 250 150 240 L 150 200.1" fill="none" stroke="#005700" stroke-width="3" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 150 193.35 L 154.5 202.35 L 150 200.1 L 145.5 202.35 Z" fill="#005700" stroke="#005700" stroke-width="3" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 30 269.9 L 30 230 Q 30 220 40 220 L 126 220 Q 136 220 136.07 210 L 136.2 191.68" fill="none" stroke="#6f0000" stroke-width="3" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 30 276.65 L 25.5 267.65 L 30 269.9 L 34.5 267.65 Z" fill="#6f0000" stroke="#6f0000" stroke-width="3" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 30 340 L 30 409.9" fill="none" stroke="#6f0000" stroke-width="3" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 30 416.65 L 25.5 407.65 L 30 409.9 L 34.5 407.65 Z" fill="#6f0000" stroke="#6f0000" stroke-width="3" stroke-miterlimit="10" pointer-events="all"/>
<rect x="0" y="280" width="120" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 310px; margin-left: 1px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
<b>
Circuit Breaker
<br/>
</b>
32A, 3ph
</div>
</div>
</div>
</foreignObject>
<text x="60" y="314" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
Circuit Breaker...
</text>
</switch>
</g>
<path d="M 300 340 L 300 420" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 270 280 L 270 260 Q 270 250 260 250 L 220 250 Q 210 250 210 240 L 210 200.1" fill="none" stroke="#005700" stroke-width="3" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 210 193.35 L 214.5 202.35 L 210 200.1 L 205.5 202.35 Z" fill="#005700" stroke="#005700" stroke-width="3" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 330 269.9 L 330 230 Q 330 220 320 220 L 239 220 Q 229 220 228.9 210 L 228.72 190.84" fill="none" stroke="#6f0000" stroke-width="3" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 330 276.65 L 325.5 267.65 L 330 269.9 L 334.5 267.65 Z" fill="#6f0000" stroke="#6f0000" stroke-width="3" stroke-miterlimit="10" pointer-events="all"/>
<path d="M 330 340 L 330 409.9" fill="none" stroke="#6f0000" stroke-width="3" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 330 416.65 L 325.5 407.65 L 330 409.9 L 334.5 407.65 Z" fill="#6f0000" stroke="#6f0000" stroke-width="3" stroke-miterlimit="10" pointer-events="all"/>
<rect x="240" y="280" width="120" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 310px; margin-left: 241px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
<b>
Circuit Breaker
<br/>
</b>
32A, 3ph
</div>
</div>
</div>
</foreignObject>
<text x="300" y="314" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
Circuit Breaker...
</text>
</switch>
</g>
<path d="M 90 420 L 90 350.1" fill="none" stroke="#005700" stroke-width="3" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 90 343.35 L 94.5 352.35 L 90 350.1 L 85.5 352.35 Z" fill="#005700" stroke="#005700" stroke-width="3" stroke-miterlimit="10" pointer-events="all"/>
<rect x="0" y="420" width="120" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 450px; margin-left: 1px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
<b>
EVSE1
<br/>
</b>
16A, 3ph
</div>
</div>
</div>
</foreignObject>
<text x="60" y="454" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
EVSE1...
</text>
</switch>
</g>
<path d="M 270 420 L 270 350.1" fill="none" stroke="#005700" stroke-width="3" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 270 343.35 L 274.5 352.35 L 270 350.1 L 265.5 352.35 Z" fill="#005700" stroke="#005700" stroke-width="3" stroke-miterlimit="10" pointer-events="all"/>
<rect x="240" y="420" width="120" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 450px; margin-left: 241px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
<b>
EVSE2
<br/>
</b>
32A, 3ph
</div>
</div>
</div>
</foreignObject>
<text x="300" y="454" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
EVSE2...
</text>
</switch>
</g>
<path d="M 210 60 L 210 119.9" fill="none" stroke="#6f0000" stroke-width="3" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 210 126.65 L 205.5 117.65 L 210 119.9 L 214.5 117.65 Z" fill="#6f0000" stroke="#6f0000" stroke-width="3" stroke-miterlimit="10" pointer-events="all"/>
<rect x="120" y="0" width="120" height="60" fill="#1ba1e2" stroke="#006eaf" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 30px; margin-left: 121px;">
<div data-drawio-colors="color: #ffffff; " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(255, 255, 255); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
<b>
EnergyManager
</b>
</div>
</div>
</div>
</foreignObject>
<text x="180" y="34" fill="#ffffff" font-family="Helvetica" font-size="12px" text-anchor="middle">
EnergyManager
</text>
</switch>
</g>
</g>
<switch>
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
<a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank">
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
Text is not SVG - cannot display
</text>
</a>
</switch>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,36 @@
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="121px" height="61px" viewBox="-0.5 -0.5 121 61" content="&lt;mxfile&gt;&lt;diagram id=&quot;LkL3k23o4wMPku4sNzY7&quot; name=&quot;Page-1&quot;&gt;jZNNb4QgEIZ/DXeFXbM9ttbdXnoyac9UpkKCYlh2dfvri2VQ7KZJL2Z45gveGQkru+lk+SBfjQBNaCYmwp4JpQ/5zn9ncAugYPsAWqtEQPkKavUFCDOkFyXgvAl0xminhi1sTN9D4zaMW2vGbdin0duuA2/hDtQN1/f0XQknAz3ss5W/gGpl7Jxn6Ol4DEZwllyYMUGsIqy0xrhgdVMJetYu6hLyjn94l4tZ6N1/EmhIuHJ9wbcRWmif+vThjXY2qre6itAXWvgSaH8Toa4RMfpIaOk7sEEmRZII1MHdorjWXHoB8/0y7x6lclAPvJm9o98mz6TrtD/lS3b6XpTgCtbBlCB8/wlMB87efAh62R63EZfxsMPZjOto8zgvmYy1QMZxm9ql9Cq4N1DzeFxn++NLfhBWfQM=&lt;/diagram&gt;&lt;/mxfile&gt;">
<defs/>
<g>
<rect x="0" y="0" width="120" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 30px; margin-left: 1px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
<b>
EVSE
</b>
<br/>
<div>
32A, 3ph
</div>
</div>
</div>
</div>
</foreignObject>
<text x="60" y="34" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
EVSE...
</text>
</switch>
</g>
</g>
<switch>
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
<a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank">
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
Text is not SVG - cannot display
</text>
</a>
</switch>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,56 @@
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="121px" height="191px" viewBox="-0.5 -0.5 121 191" content="&lt;mxfile&gt;&lt;diagram id=&quot;LkL3k23o4wMPku4sNzY7&quot; name=&quot;Page-1&quot;&gt;zZRNb6MwEIZ/DXfABXWPTUqzh+4pUnt28RR71zBoMoRkf/2aMoSg9EuqKu0F2c+8tmfesYnUuj5sSLf2FxrwURqbQ6RuozT9kVyF7wCOI8hVNoKKnBlRMoOt+wsCY6GdM7BbCBnRs2uXsMSmgZIXTBNhv5Q9o1+e2uoKLsC21P6SPjrDdqTXWTzzn+AqO52cxBKp9SQWsLPaYH+GVBGpNSHyOKoPa/CDd5Mv47q7N6KnxAga/syCVNLg41QbmFCqTJHYYoWN9sVMV4RdY2DYIA6zWXOP2AaYBPgbmI/SN90xBmS59hKFxtwMXQjTBhsYyZ3zXra8LELq2mFHpeSppO+aKhCV3KqhgrNlUvgGsAamYxAQeM1uv2ymljtRnXSzbWEgzr3uouSy176TTaM09yGr1VMYVMNg7ajsHAfVikD/ATopaJJMJJx2WpbkN1Eazo9Vay9atWxEbx3DttUvBvXh3S1Nf9PUPRDD4V3DJKoycViebTbd4n5+BMnE7NkDyOOve3z1scfFw7b4nK0q/Y9tzbPvszVM55/LS+zsD62Kfw==&lt;/diagram&gt;&lt;/mxfile&gt;">
<defs/>
<g>
<path d="M 60 60 L 60 130" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
<rect x="0" y="0" width="120" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 30px; margin-left: 1px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
<b>
Circuit Breaker
<br/>
</b>
16A, 3ph
</div>
</div>
</div>
</foreignObject>
<text x="60" y="34" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
Circuit Breaker...
</text>
</switch>
</g>
<rect x="0" y="130" width="120" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 160px; margin-left: 1px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
<b>
EVSE
<br/>
</b>
32A, 3ph
</div>
</div>
</div>
</foreignObject>
<text x="60" y="164" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle">
EVSE...
</text>
</switch>
</g>
</g>
<switch>
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
<a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank">
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
Text is not SVG - cannot display
</text>
</a>
</switch>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -0,0 +1,135 @@
<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="492px" height="192px" viewBox="-0.5 -0.5 492 192" content="&lt;mxfile&gt;&lt;diagram id=&quot;Z5-atGQC2fyeHDTmyhzX&quot; name=&quot;Page-1&quot;&gt;5Zddb9sgFIZ/jW8nf8e5bNO0m7RN1SJt18ic2GjYRJg0yX79jmuwjXHaqkv3oeUiMi9wgOc94ThetKqOd5Lsyk+CAvdCnx696MYLw0Ua4ncrnDohCZedUEhGOykYhA37AVr0tbpnFBproBKCK7azxVzUNeTK0oiU4mAP2wpur7ojBTjCJifcVb8xqspOzRJ/0N8DK0qzcuDrnoqYwVpoSkLFYSRFay9aSSFU91QdV8BbdoZLN+/2TG+/MQm1etEEPeOB8L0+nN6YOpnTSrGvKbQTfC+6PpRMwWZH8rb3gPaiVqqKYyvARx0OpILj2T0F/UkxQ0BUoOQJh5gJC50NOjsWkYZ1GFjHS62VY85GJNrfoo89IMAHTWGeSOgAWdcgi9NnTOJfQ7MVtdK5HKQ9KofLDL2zqKI0tlBlsYsqCGdQpRcglTik7tqf75QRHkXZIBolxXdYCS4kKrWooaXDOJ9IhLOixmaOgAD16xYMw5/hle6oGKXtMrPkbW/eAH6fbgZ+kjjw0xn20QXYp26Wfv1/yKf+nyO/cMkfd0Iqhz7U9KqtNQPVJ+6CRhGpzPCck6ZhuZFvGTfTMKZu+U+RBGrVL5fjiFMyw8loEjhR7MGuenPw9Ar3guFOepv6OMYmf8K/EXuZg541rlKTQI7f00CIqQDlBHr0sj/2i+zNHHs/VM/ZO/j1mtv+bzErXr5bjj92DZ4WjNda5wS6nHVLx7qPQLaobNilSzclTfk4N9CNe6LwomxvRkwgP7vMNRdnz1f3OH2b4m5etUc0v7Rvpf8uzSjIfhdNbA6v8F0mD/+DovVP&lt;/diagram&gt;&lt;/mxfile&gt;">
<defs/>
<g>
<rect x="0" y="0" width="490" height="190" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
<rect x="185" y="110" width="120" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 140px; margin-left: 186px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
EnergyNode
</div>
</div>
</div>
</foreignObject>
<text x="245" y="145" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="16px" text-anchor="middle">
EnergyNode
</text>
</switch>
</g>
<rect x="11" y="125" width="60" height="30" fill="none" stroke="none" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 140px; margin-left: 12px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
Grid
</div>
</div>
</div>
</foreignObject>
<text x="41" y="145" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="16px" text-anchor="middle">
Grid
</text>
</switch>
</g>
<rect x="421" y="125" width="60" height="30" fill="none" stroke="none" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 140px; margin-left: 422px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
EV
</div>
</div>
</div>
</foreignObject>
<text x="451" y="145" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="16px" text-anchor="middle">
EV
</text>
</switch>
</g>
<path d="M 77.37 70 L 421 70" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 72.12 70 L 79.12 66.5 L 77.37 70 L 79.12 73.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 70px; margin-left: 246px;">
<div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">
Export
</div>
</div>
</div>
</foreignObject>
<text x="246" y="75" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="16px" text-anchor="middle">
Export
</text>
</switch>
</g>
<path d="M 71 30 L 414.63 30" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/>
<path d="M 419.88 30 L 412.88 33.5 L 414.63 30 L 412.88 26.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 30px; margin-left: 246px;">
<div data-drawio-colors="color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; background-color: rgb(255, 255, 255); white-space: nowrap;">
Import
</div>
</div>
</div>
</foreignObject>
<text x="246" y="35" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="16px" text-anchor="middle">
Import
</text>
</switch>
</g>
<rect x="305" y="110" width="46" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-dasharray="8 8" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 44px; height: 1px; padding-top: 140px; margin-left: 306px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
Leaf Side
</div>
</div>
</div>
</foreignObject>
<text x="328" y="145" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="16px" text-anchor="middle">
Leaf S...
</text>
</switch>
</g>
<rect x="139" y="110" width="46" height="60" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-dasharray="8 8" pointer-events="all"/>
<g transform="translate(-0.5 -0.5)">
<switch>
<foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 44px; height: 1px; padding-top: 140px; margin-left: 140px;">
<div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;">
<div style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;">
Root Side
</div>
</div>
</div>
</foreignObject>
<text x="162" y="145" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="16px" text-anchor="middle">
Root S...
</text>
</switch>
</g>
</g>
<switch>
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
<a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank">
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
Text is not SVG - cannot display
</text>
</a>
</switch>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,743 @@
.. _exp-energymanagement:
############################
Energy Management in EVerest
############################
This section provides an in-depth explanation of the energy management concept
implemented in EVerest.
It covers the representation of energy systems as energy trees, the handling of
energy requests and distribution, and the configuration of energy trees within
EVerest. The central modules involved in this concept are the
:ref:`EnergyManager <everest_modules_EnergyManager>` and
:ref:`EnergyNode <everest_modules_EnergyNode>` modules.
One of its central ideas of EVerest's energymanagement to represent the energy
system for which power is distributed as an energy tree containing energy nodes.
This enables the representation of arbitrarily complex configurations of
physical and logical components within the targeted energy system.
The following sections present this concept in more detail.
Energy nodes
------------
An energy node can be either a logical or physical component within the energy
system.
Energy nodes can typically be classified into the following categories:
* **Physical Components**: Circuit breakers, electrical fuses
* **Logical Components**: Limits from OCPP, EEBus, or other external sources
* **Charging Stations**: Unidirectional or bidirectional charging stations
(or in general any sink or source of power)
An EVerest module becomes an energy node by implementing the
:doc:`energy </reference/interfaces/energy>` interface.
.. note::
At the time of writing, two EVerest modules are considered energy nodes as
per the above definition: **EnergyNode** and **EvseManager**.
More may be added in the future.
The **EnergyNode** module fulfills central aspects of the energy management
concept.
When the term **EnergyNode** is used, it refers to the actual module,
whereas **energy node** refers to the general definition above.
The **EnergyNode** module both requires and provides the
:doc:`energy </reference/interfaces/energy>` interface.
This design enables the representation of arbitrary energy tree configurations
within the EVerest configuration file as explained in detail in a later
section.
Energy trees
------------
Energy trees are used to model various energy system configurations.
Below are examples demonstrating how energy systems can be represented in
EVerest.
The simplest energy tree consists of a single leaf node representing an EVSE
with a physical hardware capability of 32 A on 3 phases.
.. image:: images/single_node.drawio.svg
:name: single-node-label
:align: center
Typically, the electrical connection of charging stations is protected by a
circuit breaker.
Adding this to the representation results in:
.. image:: images/single_node_with_circuit_breaker.drawio.svg
:name: single-node-with-circuit-breaker-label
:align: center
In this example, the circuit breaker limits the current to 16 A, even though
the EVSE supports 32 A.
The module managing power distribution must enforce this limitation.
For a more complex setup, consider the following example:
.. image:: images/energy_tree.drawio.svg
:name: energy-tree-label
:align: center
Here, a top-level circuit breaker limits the line to 63 A.
Two additional circuit breakers protect the lines to two EVSEs, each fused at
32 A.
EVSE1 can consume 16 A on three phases, while EVSE2 can consume 32 A on three
phases.
This module accounts for all existing limitations when distributing power to
energy nodes.
All the scenarios above can be represented within EVerest.
The power distribution to the EVSEs is managed by this module, considering the
limitations of each individual node.
How these setups above can be represented in EVerest is presented in section
:ref:`'Configuration of energy trees in EVerest' <configuration_of_energy_trees_in_everest>`.
Energy requests and distribution
--------------------------------
The EnergyManager module requires exactly one module implementing the
:doc:`energy </reference/interfaces/energy>` interface.
This interface defines:
* A single variable **energy_flow_request** of type **EnergyFlowRequest**
* A single command **enforce_limits**
The concept of the usage of this interface is further described in the
following sections.
Energy flow request variable
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The **EnergyFlowRequest** type is recursive, containing a list of child
**EnergyFlowRequest**.
It defines the power and current requested by an energy node, along with its
limitations (e.g., hardware or software constraints).
In essence, a module specifies its requirements and limitations through this
type, which are then communicated to its parent node.
The parent node creates an aggregated **EnergyFlowRequest**, incorporating its
own limitations and the requests from its children.
Energy flow requests are constructed from the leaves to the root of the energy
tree, resulting in a single **EnergyFlowRequest** that contains all child
requests.
This final request serves as input for this module, which calculates the limits
to enforce down the tree.
The following diagram illustrates how energy nodes communicate requests, with
green arrows representing energy flow requests:
.. image:: images/energy_tree_request_and_distribution.drawio.svg
:name: energy-tree-request-and-distribution-label
:align: center
Enforcing limits
^^^^^^^^^^^^^^^^
The **enforce_limits** command propagates limits down the tree.
Each energy node calls this function on its child nodes to enforce calculated
limits.
Note that the EnergyManager itself does not represent an energy node.
It communicates the resulting **EnergyFlowRequest** to a single connected
energy node, which then propagates the limits further down the tree.
Details of the EnergyFlowRequest type
-------------------------------------
Energy nodes may have varying types of limits.
To understand this better, consider a zoomed-in view of an energy node:
.. image:: images/zoom_in_energy_node.drawio.svg
:name: zoom-in-energy-node-label
:align: center
In reality, an energy node may have different limits for charging (import) and
discharging (export).
The **EnergyFlowRequest** type accounts for this distinction:
* **Import**: Energy flow direction from the grid to the consumer/EV (charging)
* **Export**: Energy flow direction from the EV to the grid (discharging)
Additionally, each direction may have separate limits for the root and leaf
sides of the energy node.
For example, a DC power supply may have AC limits on the root side (facing the
grid) and DC limits on the leaf side (facing the EV).
Limits may also change over time, which is why the *schedule_import* and
*schedule_export* properties are lists containing multiple limit
specifications.
Besides the limiting schedules for import and export, it also contains
a *setpoint_schedule*. This is a list (time series) just like the limiting schedules
and it may contain setpoints for each timeslot, see below for a more detailed description.
Each value that is given for a limit or schedule has a source property (string).
It is used to track which value is the actual limiting factor for the result in the
optimization algorithm.
This is useful to understand how the result that is sent to the EvseManager is composed.
Below is an example JSON representation of an **EnergyFlowRequest** for a leaf node:
.. code-block:: json
{
"children": [],
"evse_state": "Charging",
"node_type": "Evse",
"priority_request": false,
"schedule_export": [
{
"limits_to_leaves": {
"ac_max_current_A": {
"value": 10.0,
"source": "EVSE1_leave"
}
},
"limits_to_root": {
"ac_max_current_A": {
"value": 16.0,
"source": "EVSE1_root"
},
"ac_max_phase_count": {
"value": 3,
"source": "EVSE1_phase"
},
"ac_min_current_A": {
"value": 0.0,
"source": "EVSE1_mincurrent"
},
"ac_min_phase_count": {
"value": 1,
"source": "EVSE1_minphase"
},
"ac_number_of_active_phases": 3,
"ac_supports_changing_phases_during_charging": true
},
"timestamp": "2024-12-17T13:08:36.479Z"
}
],
"schedule_import": [
{
"limits_to_leaves": {
"ac_max_current_A": {
"value": 32.0,
"source": "EVSE1_leave"
}
},
"limits_to_root": {
"ac_max_current_A": {
"value": 32.0,
"source": "EVSE1_root"
},
"ac_max_phase_count": {
"value": 3,
"source": "EVSE1_phase"
},
"ac_min_current_A": {
"value": 6.0,
"source": "EVSE1_mincurrent"
},
"ac_min_phase_count": {
"value": 1,
"source": "EVSE1_minphase"
},
"ac_number_of_active_phases": 3,
"ac_supports_changing_phases_during_charging": true
},
"timestamp": "2024-12-17T13:08:36.479Z"
}
],
"schedule_setpoints": [],
"uuid": "evse1"
}
Setpoints
---------
Setpoints can optionally be specified for each time slot. Note that the schedule_setpoints list
may have different timestamp entries then the limiting schedules.
A setpoint entry may have an ampere or watt limit or specify a Watt-Grid Frequency table as setpoint.
Only one of those three options may be set in each timeslot, but they may be different for each timeslot.
The priority property is intended to be used if multiple conflicting setpoints exist in the energy tree.
This is not implemented for now, the priority property will be ignored for now.
In most cases a setpoint is not neccessary as the same functionality can also be implemented by setting the
limits at the node appropriately.
It is especially useful for the bidirectional use case, as it selects whether charging or discharging should be performed:
E.g. with watt limits of -10kW to +10kW and a setpoint of -2kW, it will discharge at 2kW.
A setpoint of +3kW will switch to charging without changing the limits.
The Frequency based watt limit table can be set through e.g. OCPP 2.1 smart charging. It is intended to
specifiy a grid stabilizing mode, in which the charger charges when the grid frequency is too high and discharges
to support the grid if the frequency is too low.
External limits
---------------
External limits can be added to the energy system using EVerest modules
implementing the
:doc:`external_energy_limits </reference/interfaces/external_energy_limits>`
interface.
At the time of writing, the **EnergyNode** module is the sole module that
provides this functionality.
The `external_energy_limits` interface defines the **set_external_limits**
command, which modules like OCPP or API can use to specify external energy
limits.
These limits are then considered by the **EnergyNode** module when creating
its energy flow request.
To apply external limits, a module must require the `external_energy_limits`
interface and invoke the **set_external_limits** command.
The next section details how to configure these limits in EVerest.
.. _configuration_of_energy_trees_in_everest:
Configuration of energy trees in EVerest
----------------------------------------
The following section describes how to configure the EVerest configuration file
in order to represent the targeted energy tree.
In order to do that we are using a complex energy tree example and implement
this in the configuration step by step.
This is the energy tree that we are going to represent in the EVerest
configuration:
.. _energy_tree_complex_label:
.. image:: images/energy_tree_complex.drawio.svg
:alt: Example of a complex energy tree
:align: center
This energy tree represents a setup with two EVSEs.
There are two external sources that are able to provide external energy limits:
OCPP and the API module.
OCPP is able to set external limits for each EVSE as well as for the whole
charging station.
This is indicated by the three arrows labeled with OCPP.
The API module is only able to set the limits for the two EVSEs, but not for
the whole charging station.
.. note::
To improve readability, unrelated module configurations and connections are
omitted in the examples below.
First, we add two EvseManager modules to the config file representing our
energy leaf nodes.
.. code-block:: yaml
active_modules:
evse_manager_1:
module: EvseManager
evse_manager_2:
module: EvseManager
The two EVSEs can receive limits from OCPP.
Therefore, we add two **EnergyNode** modules that represent the sinks for the
external limits.
The **EnergyNode** module requires a connection to a module implementing the
:doc:`energy </reference/interfaces/energy>` interface.
This is implemented by connecting the previously added EvseManager modules to it.
Any external limit applied to the added EnergyNode modules will be applied to
its energy child nodes (the EvseManager modules) now.
.. code-block:: yaml
active_modules:
evse_manager_1:
module: EvseManager
evse_manager_2:
module: EvseManager
ocpp_sink_1:
module: EnergyNode
connections:
energy_consumer:
- module_id: evse_manager_1
implementation_id: energy_grid
ocpp_sink_2:
module: EnergyNode
connections:
energy_consumer:
- module_id: evse_manager_2
implementation_id: energy_grid
We continue with adding **EnergyNode** modules that represent the sinks for the
limits received by the API module.
Note that the **EnergyNode** module provides and requires the
:doc:`energy </reference/interfaces/energy>` interface at the same time.
This allows us to connect **EnergyNode** modules and therefore fullfill the
requirement of others.
Note that the modules **ocpp_sink_1** and **ocpp_sink_2** are connected to the
**api_sink_1** and **api_sink_2**.
This means that both limits can be considered by this module without
overriding each other.
.. code-block:: yaml
active_modules:
evse_manager_1:
module: EvseManager
evse_manager_2:
module: EvseManager
ocpp_sink_1:
module: EnergyNode
connections:
energy_consumer:
- module_id: evse_manager_1
implementation_id: energy_grid
ocpp_sink_2:
module: EnergyNode
connections:
energy_consumer:
- module_id: evse_manager_2
implementation_id: energy_grid
api_sink_1:
module: EnergyNode
connections:
energy_consumer:
- module_id: ocpp_sink_1
implementation_id: energy_grid
api_sink_2:
module: EnergyNode
connections:
energy_consumer:
- module_id: ocpp_sink_2
implementation_id: energy_grid
We are now only missing a represention for the complete charging station.
Therefore, we add another **EnergyNode** module with a fuse limit of 63 A and
we name it **grid_connection_point**.
We connect **api_sink_1** and **api_sink_2** to it.
.. code-block:: yaml
active_modules:
evse_manager_1:
module: EvseManager
evse_manager_2:
module: EvseManager
ocpp_sink_1:
module: EnergyNode
connections:
energy_consumer:
- module_id: evse_manager_1
implementation_id: energy_grid
ocpp_sink_2:
module: EnergyNode
connections:
energy_consumer:
- module_id: evse_manager_2
implementation_id: energy_grid
api_sink_1:
module: EnergyNode
connections:
energy_consumer:
- module_id: ocpp_sink_1
implementation_id: energy_grid
api_sink_2:
module: EnergyNode
connections:
energy_consumer:
- module_id: ocpp_sink_2
implementation_id: energy_grid
grid_connection_point:
module: EnergyNode
config_module:
fuse_limit_A: 63
phase_count: 3
connections:
energy_consumer:
- module_id: api_sink_1
implementation_id: energy_grid
- module_id: api_sink_2
implementation_id: energy_grid
Now we have the complete energy tree represented, but we're still missing to
include the modules that set the external energy limits, so the OCPP and API
module.
Since these modules require (optionally multiple) connections to modules
implementing the
:doc:`external_energy_limits </reference/interfaces/external_energy_limits>`
interface, we need to also add the connections to the **EnergyNode** modules we
have added previously.
Finally, we also add the **EnergyManager** module and connect the
**grid_connection_point** to it.
.. code-block:: yaml
active_modules:
evse_manager_1:
module: EvseManager
evse_manager_2:
module: EvseManager
ocpp_sink_1:
module: EnergyNode
connections:
energy_consumer:
- module_id: evse_manager_1
implementation_id: energy_grid
ocpp_sink_2:
module: EnergyNode
connections:
energy_consumer:
- module_id: evse_manager_2
implementation_id: energy_grid
api_sink_1:
module: EnergyNode
connections:
energy_consumer:
- module_id: ocpp_sink_1
implementation_id: energy_grid
api_sink_2:
module: EnergyNode
connections:
energy_consumer:
- module_id: ocpp_sink_2
implementation_id: energy_grid
grid_connection_point:
module: EnergyNode
config_module:
fuse_limit_A: 63
phase_count: 3
connections:
energy_consumer:
- module_id: api_sink_1
implementation_id: energy_grid
- module_id: api_sink_2
implementation_id: energy_grid
ocpp:
module: OCPP
connections:
evse_energy_sink:
- module_id: grid_connection_point
implementation_id: external_limits
- module_id: ocpp_sink_1
implementation_id: external_limits
- module_id: ocpp_sink_2
implementation_id: external_limits
api:
module: API
connections:
evse_energy_sink:
- module_id: api_sink_1
implementation_id: external_limits
- module_id: api_sink_2
implementation_id: external_limits
energy_manager:
module: EnergyManager
connections:
energy_trunk:
- module_id: grid_connection_point
implementation_id: energy_grid
We have now added all the required modules and connections to represent the
energy tree example :ref:`shown above <energy_tree_complex_label>`.
One important detail is still missing, which is the module mapping.
For detailed information about the module mapping please see
:doc:`3-tier module mappings </explanation/tier-module-mappings>`.
Since the connections of a module in the EVerest config does not automatically
map to a specific EVSE (or the whole charging station, represented by EVSE#0),
the **EnergyNode** modules must have a module mapping.
This allows the modules that make use of the **set_external_limits** command to
call it for the correct node.
Modules like OCPP and API can only know at which requirement the command
**set_external_limit** shall be called in case the energy node that is
connected to it has a specified module mapping in the EVerest config.
This is a full example including the module mappings:
.. code-block:: yaml
active_modules:
evse_manager_1:
module: EvseManager
mapping:
module:
evse: 1
evse_manager_2:
module: EvseManager
mapping:
module:
evse: 2
ocpp_sink_1:
module: EnergyNode
mapping:
module:
evse: 1
connections:
energy_consumer:
- module_id: evse_manager_1
implementation_id: energy_grid
ocpp_sink_2:
module: EnergyNode
mapping:
module:
evse: 2
connections:
energy_consumer:
- module_id: evse_manager_2
implementation_id: energy_grid
api_sink_1:
module: EnergyNode
mapping:
module:
evse: 1
connections:
energy_consumer:
- module_id: ocpp_sink_1
implementation_id: energy_grid
api_sink_2:
module: EnergyNode
mapping:
module:
evse: 2
connections:
energy_consumer:
- module_id: ocpp_sink_2
implementation_id: energy_grid
grid_connection_point:
module: EnergyNode
mapping:
module:
evse: 0
config_module:
fuse_limit_A: 63
phase_count: 3
connections:
energy_consumer:
- module_id: api_sink_1
implementation_id: energy_grid
- module_id: api_sink_2
implementation_id: energy_grid
ocpp:
module: OCPP
connections:
evse_energy_sink:
- module_id: grid_connection_point
implementation_id: external_limits
- module_id: ocpp_sink_1
implementation_id: external_limits
- module_id: ocpp_sink_2
implementation_id: external_limits
api:
module: API
connections:
evse_energy_sink:
- module_id: api_sink_1
implementation_id: external_limits
- module_id: api_sink_2
implementation_id: external_limits
energy_manager:
module: EnergyManager
connections:
energy_trunk:
- module_id: grid_connection_point
implementation_id: energy_grid
Energy distribution
-------------------
The EnergyManager module implements an algorithm to distribute available power
to energy leaf nodes:
* It calculates and enforces limits for each energy leaf node in the tree.
* It ensures that no node exceeds its specified limits for current, power, or
phase count.
* It distributes power equally among child nodes, if their collective request
exceeds the parent node's limits.
* The algorithm prefers charging over discharging if the specified limits allow
for both.
* It supports phase switching between single-phase and three-phase modes,
optimizing power usage for low-demand scenarios if
**switch_3ph1ph_while_charging_mode** is enabled.
Phase switching
---------------
This module supports switching between single-phase (1ph) and three-phase (3ph)
configurations during AC charging.
.. warning::
Some vehicles (such as the first generation of Renault Zoe) may be
permanently damaged when switching from 1ph to 3ph during charging.
Use at your own risk!
To use this feature, several configurations must be enabled across different
EVerest modules:
- **EvseManager**: Adjust the following configuration options to your needs:
- ``switch_3ph1ph_delay_s``
- ``switch_3ph1ph_cp_state``
- **Module implementing the `evse_board_support </reference/interfaces/evse_board_support>` interface:**
- Set ``supports_changing_phases_during_charging`` to ``true`` in the reported capabilities.
- Define the minimum number of phases as 1 and the maximum as 3.
- Ensure the ``ac_switch_three_phases_while_charging`` command is implemented.
- **EnergyManager**: Adjust the following config options to your needs:
- switch_3ph1ph_while_charging_mode
- switch_3ph1ph_max_nr_of_switches_per_session
- switch_3ph1ph_switch_limit_stickyness
- switch_3ph1ph_power_hysteresis_W
- switch_3ph1ph_time_hysteresis_s
Refer to the manifest.yaml for documentation of these configuration options.
If all of these are properly configured, the EnergyManager will handle the
1ph/3ph switching.
To enable this, an external limit must be set.
There are two ways to configure the limit:
1. **Watt-based limit (preferred option):** The limit is set in Watts (not
Amperes), even though this involves AC charging.
This provides the EnergyManager with the flexibility to decide when to
switch.
The limit can be defined by an OCPP schedule or through an additional
EnergyNode.
2. **Ampere-based limit:** The limit is defined in Amperes, along with a
restriction on the number of phases (e.g., ``min_phase=1`` and
``max_phase=1``).
This enforces switching and allows external control over the switching time,
but the EnergyManager loses its ability to choose when to switch.
Best practices
^^^^^^^^^^^^^^
In general, this feature works best in a configuration with 32 A per phase and
a Watt-based limit.
In this setup, there is an overlap between the single phase and three phase
domain:
- Single-phase charging: 1.3 kW to 7.4 kW
- Three-phase charging: 4.2 kW to 22 kW (or 11 kW)
This avoids switching too often in the most elegant way.
Other methods to reduce the number of switch cycles can be configured in the
EnergyManager, see config options above.
Current limitations
-------------------
* The algorithm does not account for real-time power meter readings from
individual nodes.
* It does not redistribute unused power when the actual consumption is below
the assigned target value.

View File

@@ -0,0 +1,179 @@
.. error_framework:
###############
Error Framework
###############
This explains the general idea and the components provided by the error framework.
For practical hints on the usage of the error framework, consult the
:ref:`error framework how-to guide <htg_error_framework>`.
*******
Purpose
*******
The error framework is used to handle errors in the EVerest framework.
As not every module can "decide" by itself how to react to an error and how
to handle it, the error framework provides functionality that allows modules
to react to errors that are raised in required other modules.
The other main purpose of the error framework is to provide a way to monitor
and report errors in the system. This can be used for example for displaying
or reporting to an OCPP backend.
*****
Usage
*****
General
=======
The Error class
---------------
The Error class is a simple struct that holds all required information
required to handle the error. Data members include:
- type
- sub-type
- arbitrary message + description
- the raising module's ID
- vendor_id
- severity
- timestamp
- uuid
- state (active, cleared, ...)
Raise an error
--------------
Each implementation of an interface can raise errors that are defined in the
interface. There is one function `raise_error` that takes an error object as
argument. The error object is an instance of the class `Error`. To create
the initial error object, the `ErrorFactory` is used.
Clear an error
--------------
An error can be cleared by the same implementation that raised the error. There
are multiple functions to clear an error.
The function `clear_error` provides two different overloads. The first
overload takes the `ErrorType` as argument and clears errors with matching
`ErrorType`.
The second overload takes `ErrorType` and `ErrorSubType` as arguments.
In this case, only the error with matching `ErrorType` and `ErrorSubType`
is cleared.
The function `clear_all_errors_of_impl` clears all errors of the current
implementation.
Subscribe to an error
---------------------
A module can subscribe to errors of its requirements. This way the module can
react to those errors. There are two functions to subscribe to errors.
The function `subscribe_error` takes the `ErrorType` and two callback functions
as arguments.
The `ErrorType` is the type of the error that the module wants to subscribe to.
The first callback is called when the error is raised.
The second callback is called when the error is cleared.
The function `subscribe_all_errors` takes again two callback functions as
arguments. The first callback is called when an error of any type is raised by
the requirement. The second callback is called when an error is cleared.
Subscribe globally to all errors
--------------------------------
This feature is currently only available for C++ modules. It allows a module
to subscribe globally to all errors of all modules. This can be used for
example for logging purposes or error reporting.
To enable this functionality, the flag `enable_global_errors` in the module's
manifest file must be set to `true`.
With this, the function `subscribe_global_all_errors` is added to the
auto-generated code. This way the function can be used as the other subscribe
functions, with two callback functions as arguments.
The ErrorFactory
----------------
Since a module does not have direct access to some information that is required
to create an error object, as for example the `module_id`, the class
`ErrorFactory` is used, which is provided for each implementation of an
interface, with correct default values.
The `ErrorFactory` is used to create the initial error object.
This error object can be raised as it is, or can be modified before raising.
The ErrorFactory provides five signatures for the create_error function:
- *Default*: Takes no arguments and initializes with default values.
- *Standard*: Requires `ErrorType`, `ErrorSubType`, and `message`.
- *Contextual*: Extends the *standard* signature by adding either `severity`, `state`, or *both*.
The ErrorStateMonitor
---------------------
The `ErrorStateMonitor` is a class that is used to monitor the error state of
implementations and requirements.
It is provided for each implementation of an interface and for each requirement
of an module.
To check if an error is currently active, the function `is_error_active` is
used. This function takes the `ErrorType` and `ErrorSubType` as arguments and
returns a boolean value. If the error is active, the function returns `True`,
otherwise `False`.
To check if a specific set of errors is in a specific state, the struct
`StateCondition` is defined.
This struct has the members `ErrorType`, `ErrorSubType` and `active: boolean`.
The function `is_condition_satisfied` can either take a single `StateCondition`
or a list of `StateCondition` as argument.
If a single `StateCondition` is passed, the function returns `True` if the
error is in the state as defined in the `StateCondition`.
If a list of `StateCondition` is passed, the function returns `True` if all
conditions are satisfied.
***********
Usage Guide
***********
Creating Error objects
======================
Error objects may always be created using the `ErrorFactory` of the
implementation.
Error objects can be edited after creation, before raising them.
The following attributes may not be changed after creation:
- `timestamp`
- `origin.module_id`
- `origin.implementation_id`
- `uuid`
The global subscription
=======================
If a module is subscribed to global all errors, it may do only "reporting"
actions, but no "handling" actions. This means that the module does not change
its behavior based on the error, but only reports the error for example to a
log file.
Side effects of raising errors
==============================
The error framework allow module implementations to get notified about an error
from one of their requirements by subscribing to the error. This can be used for
reporting purposes (e.g. via OCPP) or it can be used to adjust the control flow
of the module based on the raised error.
It is important to note that raising errors can therefore lead to side effects
within other modules. The side effects shall be documented as part of the module
documentation (see e.g. EvseManager or OCPP).

View File

@@ -0,0 +1,320 @@
.. _exp-hardware-architecture:
#####################
Hardware Architecture
#####################
This page gives some ideas and guidance on the general architecture of AC or DC
charging stations and helps to choose the best components.
***************
DC architecture
***************
The following block diagram shows a typical architecture for a DC charger:
.. image:: images/dc-architecture.png
:align: left
On the top, the Linux high-level controller runs EVerest plus all customer
specific software.
EVerest connects to the hardware components through the EVerest-integrated
hardware drivers or external custom drivers that use the EVerest APIs to
communicate with EVerest.
The hardware components are typically connected to the Linux controller by
CAN, RS458, Ethernet or similar. They may be different in your design.
On the bottom, a typical low-level controller design is shown.
Handling the electrical safety in the low-level design is crucial, as the
high-level Linux controller cannot guarantee timings or even that it is running
at all.
The safety MCU shall handle at least the following functionality:
* Control pilot signal I/O:
It outputs the PWM according to the duty cycle controlled by EVerest and reads
states A-F back.
* Contactor close signal:
It receives an “on/off” flag from EVerest and also internally creates a second
“on/off” flag.
As an example, the internal flag is only “on” if e.g. CP is in state C and no
overtemperature is detected with the PT1000 on the connector pins.
It outputs a contactor close signal only if both flags are “on”.
It is responsible for opening the output contactors in case of CP state not C,
over-temperature errors, loss of PE connection and all other critical
faults - independent from the Linux high-level control.
* The isolation monitor and the over-voltage protection circuitry shall also be
able to directly open the output contactors, independent of other components.
This fault signal may also be routed through the safety MCU.
EVerest will read values from the isolation monitor and the OVP module as well
and will issue a shutdown, but this will come (1) too late and (2) the safety
shutdowns shall be working even if Linux is down.
The safety MCU may require certification for e.g. UL as it contains safety in
software functionality.
The output contactors should be the last component before the plug to the EV.
Then they fully disconnect the user from all internal circuitry, so as long as
the contactors are open, no internal fault causes a safety hazard on the output
plug pins.
The block diagram above shows only two output contactors to fully switch the
output on and off.
Some power supplies may require a third contactor that switches a precharge
resistor in the output path.
This is required if the DC power supply does not have an accurate and fast
current limit functionality at very low limits (e.g. 1A).
If the DC power supply cannot ramp down the voltage quickly, an additional
contactor may be required that switches a load resistor on the output for
active discharge.
Both are not shown here as they are typically not required with most EV
charging power supplies.
***************
AC architecture
***************
The typical architecture for an AC charger is similar to that of a DC
charger, but has fewer components on the power path. The requirements for the
safety MCU apply here as well.
.. image:: images/ac-architecture.png
:align: left
************************
Choosing components (AC)
************************
Output contactors
=================
The output contactors shall have a mirror feedback contact.
With many DIN rail components, the mirror contact (or auxiliary contact) can be
mounted as a snap on device.
Ensure that the minimum current requirements are met.
Some contactors require between 10 mA and 50 mA of current flowing through the
mirror contact to ensure the contacts remain clean.
Especially for PCB mount contactors, check the contact air gap.
It should be at least 3 mm (check IEC 61851-1:2017 8.1 for alternatives).
The 3 mm found in the IEC 61851-1 originates from IEC 60664-1 Table F.2 for
4 kV rated impulse voltage, overvoltage category 3, inhomogeneous field.
For a homogeneous field, 1.2 mm would be enough.
In general, a 4-pole contactor should be used.
Some applications (such as solar-based charging) may want to use two 2-pole
contactors to allow for 1 ph/3 ph switching.
See the chapter on RCD below on limitations when using this configuration.
A significant part of the generated heat comes from the coil current.
It is recommended to lower the coil voltage as per specifications from the
manufacturer after the switching.
Examples for PCB mount 4-pole relays:
Panasonic AHER4191, Omron G9KC.
RCD
===
Integration of an RCD is optional; but if it is not in the charging station it
has to be installed in the upstream installation outside of the charging
station.
RCDs shall comply with one of the following standards:
IEC 61008-1, IEC 61009-1, IEC 60947-2 and IEC 62423.
For AC charging, a type B RCD is generally required to protect against both
AC and DC fault currents.
The RCD Type B may also be integrated into the charging station, simplifying
the installation requirements.
As Type B RCDs are quite expensive, a common solution is to integrate a
Type A RCD for AC faults and a DC fault current detector as a separate module.
In this case, the 6 mA DC fault detection module should follow IEC 62955
(check IEC 61851-1:2017 8.5; the standard says the IEC 62955 is an example to
be compliant).
Such modules are available as PCB mount or as individual modules, e.g. from
Bender/Vacuumschmelze (Benvac), Würth Elektronik, Western automation and
several others.
.. note::
The fault output of these modules shall directly open the output contactor
without waiting for Linux and EVerest.
EVerest should be informed after the switching off so that the error can be
reported.
If the general output contactor is used for RCD switch off, IEC 62955 requires
a 4-pole relay.
E.g., two 2-pole relays are no longer allowed by this standard.
In this configuration a combination of two 2-pole contactors for 1 ph/3 ph
switching followed by a 4-pole relay for RCD switch off may be required.
Power meter
===========
For AC applications, a lot of different DIN rail components are available from
many different manufacturers.
Typically, they have a ModBus RS-485 interface to the host.
Most of them can be easily added to EVerest by simple register mapping in the
GenericPowerMeter module if not supported already.
Make sure they are MID-compliant for CE.
If German Eichrecht is required, it is a bit harder to find power meters.
They are available from Bauer or EMH, for example.
************************
Choosing components (DC)
************************
Isolation monitor
=================
Most isolation monitoring devices come as DIN rail devices.
Check the following specifications:
- Certified to IEC 61557-8 or equivalent (see IEC 61851-23: 2023 CC 4.1.5)
- Measures the isolation resistance (total to PE or individually for
negative to PE and positive to PE).
Measurement range should include 100 kOhm with some margin,
e.g. 50 kOhm - 500 kOhm.
- Voltage range >= maximum voltage of DC power supply
- Communication interface with host system (e.g. ModBus RS485, CAN, ...)
- Self-test functionality via communication interface (trigger start of
self test, read result).
Relying on automatic periodic self-testing is no longer allowed in the 2023
edition of IEC 61851-23.
- Self-test should be quick (e.g. < 10 s), long self-tests may lead to
timeout issues with certain vehicles
- Time needed to detect a fault should be short (e.g. <5 s)
- Measures the voltage between DC positive and negative wire and report
via communication interface
- Separate fault output that can be used to trigger an emergency shutdown
independently from the charge controller (and EVerest!)
- Ideally: Over-voltage detection and shutdown according to IEC 61851-23:
2023 6.3.1.106 (we are not aware of a product that has this at the time of
writing)
.. note::
We are not aware of a product that fulfills all of these specifications, so
some trade-offs may need to be made and additional hardware may be required.
Example devices are Bender isoCHA425, Dold RN 5897/021 or Acrel AIM-D100.
Over voltage monitor
====================
IEC61851-23:2023 has stricter requirements than the earlier version.
One of the new safety requirements is a fast over-voltage protection, that
triggers if the DC voltage is above the limits specified in the standard for
9 ms.
The actual detection and shutdown needs to be handled outside of EVerest (e.g.
in dedicated hardware).
EVerest can only provide the value of the over-voltage limit (as it depends on
the maximum voltage reported by the EV) and start/stop the monitoring.
Refer to IEC61851-23:2023 6.3.1.106.2 for requirements.
.. note::
We are not aware of an off-the-shelf product that fits this requirement.
AC/DC converter / DC power supply
=================================
For new products we highly recommend to have a voltage range of 150 V to 1000 V
for best compatibility for CCS.
Charin suggests 920 V as the high limit, which may be on the edge already with
e.g. Lucid Air.
The lower limit is a bit flexible, but we recommend not to have more than
200 V.
Some vehicles refuse to charge if the lower limit is too high, even if they do
not require such a low voltage.
Another important topic is the current capability.
Some power supplies have quite low current limits, e.g. 30 A for a 30 kW power
supply.
This means it can only reach 30 kW on a 1000 V vehicle, while it will be
limited to 9 kW on a 300 V vehicle.
Many power supplies actually have two 500 V converters internally, and they
can be arranged in a serial or parallel configuration.
In this case, it is often possible to get a higher current output for the low
voltage vehicles in parallel mode.
The driver code should use this functionality and switch automatically between
the two modes.
High quality power supplies often have a constant power output, e.g. they can
deliver the full 30 kW over the full voltage range.
Those will give the best user experience.
Other important features are full protection (fully protected against
shorts / load dump under full load), noise, efficiency and reliability.
Example devices are UUGreenPower, Huawei, SCU, Tonhe or Infypower.
Output contactors
=================
Output contactors shall have the capability to open the contact at the maximum
current possible in the system.
Most contactors survive this only a limited number of times, e.g. three times
before they need to be replaced.
We recommend that the low-level safety architecture ensures that the DC power
supplies ramp down shortly before the contactors open in an emergency shutdown
to protect the contactors.
If this is not possible, the recommendation is to use contactors that are
robust enough to withstand this quite often.
Under normal conditions the contactors always switch at zero current, but due
to the poor quality of EV side implementations emergency shutdowns under full
load will happen.
The contactors shall also have mirror feedback contacts so that EVerest knows
when they are fully open/closed and stuck contactors can be detected.
Ensure that the minimum current requirements are met.
Most contactors require between 10 mA and 50 mA of current flowing through the
mirror contact to ensure the contacts remain clean.
Verify that the contact gap is in accordance with IEC 60664-1 for the maximum
voltage.
Power meter
===========
For DC applications, different DIN rail components are available from many
different manufacturers.
Typically, they have a ModBus RS-485 interface to the host.
Make sure they are MID-compliant for CE.
If German Eichrecht is required, it is a bit harder to find power meters.
They are available from LEM, DZG, AST, Isabellenhütte or Carlo Gavazzi, for
example.
----
**Authors**: Cornelius Claussen

View File

@@ -0,0 +1,395 @@
================
Hardware Drivers
================
This chapter describes Hardware Driver modules that are supported
natively by EVerest. The presented components have been prequalified
by Pionix to work with EVerest to ensure a quick path to production.
----------------------------
Isolation Monitoring Devices
----------------------------
Bender ISOMETER isoCHA425
-------------------------
*Hardware Driver Module* :ref:`Bender_isoCHA425HV <everest_modules_Bender_isoCHA425HV>`
You can find more information about the device here:
`<https://www.bender.de/produkte/isolationsueberwachung/isometerr-isocha425hv-mit-agh420-1>`_
Here are the most important specifications:
- RS485/ModBus connection
- Up to 1000 V DC with AGH420-1/AGH421-1
- \< 10s response time
- Firmware \< 5.00: CableCheck time: Self test ~22s + 10s response time,
can be used with IEC 61851-23:2014, but cannot be used with IEC
61851-23:2023 certification
- Firmware 5.00 improves CableCheck time to allow usage with IEC
61851-23:2023 (available now from Bender as of Q1/2025). Usage of
older 4.x firmware is not recommended.
- AGH421-1 allows full disconnection from the DC wires to allow for
multiple IMDs on the same wires, one active at a time
- Measures DC output voltage
- Measures voltage between DC+/DC- and PE as well
- Two separate relays to trigger in case of errors
| EVerest supports this device with the "Bender_isoCHA425HV" module.
The module requires a "SerialCommHub" module to be loaded as well for
the ModBus communication.
| All settings of the device can be adjusted in the module
configuration.
The error output relays should be wired directly to the DC output relay
of the charger to enable a low-level emergency shutdown functionality
which works independently of EVerest.
EVerest will read the isolation resistance values and switch off if
they fall below 100 kOhm as well, but safety certification should not
rely on the Linux system.
Dold RN5893 IMD
-------------------------
*Hardware Driver Module* :ref:`Dold RN5893 <everest_modules_DoldRN5893>`
You can find more information about the device here:
`<https://www.dold.com/en/products/relay-modules/monitoring-devices/insulation-monitors/rn-5893>`_
Here are the most important specifications:
- Width: 52,5 mm
- Classification: For DC charging stations
- Response value: 1 - 500 kΩ
- IMD type: AC, DC, AC/DC
- Nominal voltage IT system: AC 0 - 230, AC 0 - 690 (Coupling device), DC 0 - 230, DC 0 - 1000 (Coupling device) V
- Auxiliary voltage: AC/DC
- Earth fault indicator: Yes
- Approvals: UL-Recognized
- Response value type: Adjustable
- Bus interface: Modbus RTU
- Enclosure design: Distribution board
- Type: RN 5893
| EVerest supports this device with the "DoldRN5893" module.
The module requires a "SerialCommHub" module to be loaded as well for
the ModBus communication.
| All settings of the device can be adjusted in the module
configuration.
---------------
NF/RFID Readers
---------------
Many NXP chips are be supported.
All modules implement the *auth_token_provider* interface and publish a
token to be consumed by matching modules as soon as the NFC chip detects
a compatible RFID card.
NxpNfcFrontendTokenProvider
---------------------------
*Hardware Driver Module* :ref:`NxpNfcFrontendTokenProvider <everest_modules_NxpNfcFrontendTokenProvider>`
The variety of hardware supported by the underlying NxpNfcFrontendWrapper
is limited by the time of writing (only CR663), but can be extended.
This module relies on NXP's proprietary NxpNfcRdLib which users need
to obtain from NXP, due to license reasons (Download is free, but requires
accepting the license terms).
PN532TokenProvider
------------------
*Hardware Driver Module* :ref:`PN532TokenProvider <everest_modules_PN532TokenProvider>`
Supports the PN532 integrated tranceiver.
PN7160TokenProvider
-------------------
*Hardware Driver Module* :ref:`PN7160TokenProvider <everest_modules_PN7160TokenProvider>`
Supports the PNC7160 NFC Controller via the NCI interface.
No Linux kernel module required.
--------------------
AC/DC power supplies
--------------------
Huawei R100040Gx
----------------
*Hardware Driver Module* :ref:`Huawei_R100040Gx <everest_modules_Huawei_R100040Gx>`
The device is supported by EVerest with the "Huawei_R100040Gx" module.
Most important specs:
- 40 kW ACDC with 150 V - 1000 V output
- low noise fan design
- ultra compact
- automatic switching between series and parallel mode
- stackable
- CAN bus interface
In the driver configuration, set the addresses of the modules that are
used by this driver. If empty (default), it will use all modules that it
can find on the CAN bus.
If using multiple modules in a stacked configuration, connect the
outputs in parallel and connect all modules to the same CAN bus. Then
specify all module addresses in the module configuration.
Huawei V100R023C10
------------------
*Hardware Driver Module* :ref:`Huawei_V100R023C10 <everest_modules_Huawei_V100R023C10>`
This device is supported in EVerest with the Huawei production firmware.
The setup of this device is complex.
The development kit with unencrypted firmware is not supported by this driver.
Most important specs:
- Central power unit architecture with satellites, up to 740 kW
- 150 V - 1000 V output, up to 12 satellites
- Ethernet communication with EVerest
- Support for multiple connectors on one satellite
- Full support for production firmware with TLS encryption and GOOSE
security
Infypower BEG1K075G
-------------------
*Hardware Driver Module* :ref:`InfyPower_BEG1K075G <everest_modules_InfyPower_BEG1K075G>`
Supported by EVerest with the "InfyPower_BEG1K075G" module. Stacking of
multiple modules is not yet supported by the driver.
Most important specs:
- 22 kW bidirectional AC/DC
- up to 1000 V output voltage
Make sure to update the module to the latest firmware version - older
firmware versions on this converter are known to have bugs that could
result in hardware damages. New firmware is available from InfyPower.
UUGreenPower UR1000X0
---------------------
*Hardware Driver Module* :ref:`UUGreenPower_UR1000X0 <everest_modules_UUGreenPower_UR1000X0>`
Both the 30 kW and 40 kW uni-directional modules from UUGreenPower are
fully supported by EVerest with the "UUGreenPower_UR1000X0" module.
The bidirectional versions are not yet supported, but support is planned
for an upcoming release of EVerest.
Most important specs:
- 30 / 40 kW AC/DC
- up to 1000 V output voltage
- automatic series / parallel switching implemented in driver. Can be
fixed to series or parallel mode in configuration.
If multiple modules are used in a stacked configuration, you must set
the "module\\addresses" configuration parameter to the
addresses of all modules in the stack.
By default, it uses the broadcast address. With multiple modules, this
will result in each module delivering the full current to the EV instead
of sharing the current.
Other AC/DC power supplies
--------------------------
- :ref:`DPM1000 <everest_modules_DPM1000>`
- :ref:`InfyPower <everest_modules_InfyPower>`
- :ref:`Winline <everest_modules_Winline>`
------------
Power meters
------------
DC: LEM DCBM400/600
-------------------
*Hardware Driver Module* :ref:`LemDCBM400600 <everest_modules_LemDCBM400600>`
This power meter is fully supported by EVerest a (LemDCBM400600)
driver.
It supports German Eichrecht regulations and Eichrecht-compliant fault
recovery:
- After power failure of the complete unit, the transaction is closed
with the correct signed meter value from the moment the power loss
happened. It is then also closed in the CSMS if OCPP is used.
- If a communication loss happens during charging, charging is stopped.
If the communication is re-established before the EV unplugs, the
signed meter value is used to close the transaction in the CSMS. If
it does not re-establish before the EV unplugs, the transaction
cannot be billed (and no signed meter value will be used to close the
CSMS transaction).
Version V2 is required to really be Eichrecht-compliant. The driver
auto-detects V1 and V2 hardware versions and supports both.
DC: Acrel DJSF1352
------------------
*Hardware Driver Module* :ref:`Acrel_DJSF1352_RN <everest_modules_Acrel_DJSF1352_RN>`
This is a simple DC power meter with no Eichrecht support. It uses
ModBus/RS485 and requires a SerialCommHub module to work.
DC: AST DC650
-------------
*Hardware Driver Module* :ref:`AST_DC650 <everest_modules_AST_DC650>`
The driver is implemented in the "AST_DC650" module using the SLIP
protocol. Eichrecht support is not complete in the driver in the current
release for all fault cases, but this will come in an upcoming release.
There is a possibility to use it with a REST-based API similar to the
LEM.
DC: DZG GSH01
-------------
*Hardware Driver Module* :ref:`DZG_GSH01 <everest_modules_DZG_GSH01>`
The driver is implemented in the "DZG GSH01" module using the SLIP
protocol.
Note that you need an extra serial port - it cannot be shared with
e.g. other ModBus-based devices.
Eichrecht and OCMF handling are fully implemented.
DC: Isabellenhütte IEM-DCC
--------------------------
*Hardware Driver Module* :ref:`IsabellenhuetteIemDcr <everest_modules_IsabellenhuetteIemDcr>`
There is a driver for the Isabellenhütte IEM-DCC meter in EVerest.
AC: GenericPowermeter
---------------------
*Hardware Driver Module* :ref:`GenericPowermeter <everest_modules_GenericPowermeter>`
The GenericPowermeter driver has support for most ModBus-based AC power
meters. It supports a yaml configuration file for register mapping to
adapt to new power meters.
Example register mappings are included for the following power meters:
- Eastron SDM72DM, SDM72DM-V2, SDM230, SDM630-V2
- Klefr 693x - 694x
For all non-Eichrecht power metering, this should be easy to adapt.
AC: Iskra WM3M4 & WM3M4C
------------------------
*Hardware Driver Module* :ref:`RsIskraMeter <everest_modules_RsIskraMeter>`
There is a community driver in the "RsIskraMeter" module. Eichrecht
support is implemented, some fault cases may require additional
handling.
-----------------
Power line modems
-----------------
The following power line modems are supported in the
:ref:`EvseSlac module <everest_modules_EvseSlac>`.
The chip is detected automatically, but each chip may require some
configuration for a real product.
Qualcomm QCA7000/7005/7006
--------------------------
Fully supported including proprietary extensions for link detection and
chip reset.
It is recommended to enable "do_chip_reset" for all Qualcomm chips. This
will reset them after each charging session for improved stability.
If it is booting from the internal flash and correctly configured for
EVSE, it only requires the SPI Linux kernel driver to be loaded (which
is included in the Linux main line).
If it is configured for host boot, additional software outside of
EVerest is required to do firmware loading and configuration for EVSE.
If connected via the Ethernet port on QC7006, no special driver is
needed in Linux (except for the Ethernet interface driver).
Lumissil CG5317
---------------
Fully supported including proprietary extensions for link detection.
Soft reset extensions are not yet in the release, but should not be
necessary for this chip if the latest firmware is being used. Contact
Lumissil to ensure you have the latest firmware.
If it is booting from an externally attached SPI Flash (attached to the
CG5317) and correctly configured for EVSE, it only requires the SPI
Linux kernel driver to be loaded. The kernel driver is open source
licensed but not included in the main line.
If it is configured for host boot, additional software outside of
EVerest is required to do firmware loading and configuration for EVSE.
You can get these tools under NDA from Lumissil.
If connected via the Ethernet port, no special driver is needed in Linux
(except for the Ethernet interface driver).
Vertexcom MSE-102x
------------------
It is auto-detected and supported by the EvseSlac module. Proprietary
extensions for link detection and reset are not yet implemented.
It may be used without those extensions.
If connected via the Ethernet port, no special driver is needed in Linux
(except for the Ethernet interface driver).
----------------------------
BSP for complete controllers
----------------------------
Some controllers are supported natively by EVerest and require no
additional work for Control Pilot, PLC, relay switching etc.
PHYTEC phyVERSO controller
--------------------------
*Hardware Driver Module* :ref:`PhyVersoBSP <everest_modules_PhyVersoBSP>`
phyVERSO is fully supported with all features of the hardware for both
charging ports (AC and DC).
Pionix test hardware
--------------------
All testing hardware from Pionix is fully supported:
- BelayBox
- uMWC
Other BSP modules
-----------------
- :ref:`AdAcEvse22KwzKitBsp <everest_modules_AdAcEvse22KwzKitBsp>`
- :ref:`TIDA010939 <everest_modules_TIDA010939>`
- :ref:`YetiDriverBsp <everest_modules_YetiDriver>`

View File

@@ -0,0 +1,77 @@
.. exp_high_level_overview:
##############################
High-Level Overview of EVerest
##############################
EVerest is the open source firmware stack for EV charging stations. By digitally abstracting
the complexity of multiple standards and use cases, EVerest runs on any device, from unmanaged
AC home chargers to complex multi-EVSE satellite public DC charging stations with battery and
solar support. EVerest supports all the standards and protocols needed for standards-compliant,
interoperable and secure charging. In other words, EVerest does the hard work of translating
standards into working code to ensure that every car works with every charger with every
charging app and network.
Key features of EVerest
========================
All in all, you can expect the following:
* Modular and extensible architecture
* Support for AC and DC charging
* Support for EV charging protocols
* OCPP 1.6, OCPP 2.0.1 and OCPP 2.1
* ISO 15118-2, -3 and -20
* IEC 61851
* DIN SPEC 70121
* Ready-to-use hardware drivers for many compatible hardware components
* BSPs for charge controllers
* Powermeters
* Isolation monitors
* DC Power supplies
* RFID/NFC readers
* Payment terminals
* Energy management implementations and API
* Standardized and stable APIs to allow easy integrations
* Bring-up modules for custom hardware testing and integration
* Ensured standards compliance
* OTA service to keep EV chargers up-to-date
* Security best practices following OpenSSF
* ISO / IEC 5230 open source license compliance
* Secure communication channels through TPM
* Yocto support for custom embedded Linux images
EVerest Architecture
=====================
EVerest contains a rich set of modules that can be combined to build a full EV charging station software stack.
The architecture is modular and based on loosely coupled components that communicate via MQTT.
.. image:: images/0-2-everest-top-level-diagram-module-communication.png
Each module runs as an independent process and communicates with other modules via well-defined interfaces.
This allows module to subscribe to variables published by other modules and to call commands provided by other modules.
A more detailed explanation of the EVerest architecture and module concept can be found in the
explaination about :doc:`EVerest modules in detail </explanation/detail-module-concept>`.
Hardware Requirements
=============================
EVerest can run on any Linux-based operating system that comes with the required dependencies.
Our (strong) recommendation is Yocto.
Find more information on how to set up your Yocto-based environment in the respective
:doc:`EVerest Linux and Yocto guides </explanation/linux-yocto/index>`.
The hardware requirements to run EVerest very much depend on the use case and the modules
that are used in the specific scenario. As a general guideline, the following minimum
requirements should be met:
* CPU: minimum imx6ULL or comparable, recommended is imx93 or AM62x
* RAM: minimum of 512 MB, recommended is 1 GB or more
* Flash: minimum of 1 GB, recommended is 4 GB or more
Getting Started
=====================
Please refer to the :doc:`Quick Start Guides </how-to-guides/getting-started/get-started-sw>` to get started with EVerest.

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

View File

@@ -0,0 +1,31 @@
sequenceDiagram
autonumber
participant Powermeter
participant EvseManager
title Start of Powermeter or recovery after communication loss
Note over Powermeter: Device communication (re)established
Powermeter->>Powermeter: Request status from device
Powermeter->>Powermeter: Detects a running transaction
Powermeter->>Powermeter: Mark need_to_stop_transaction to true
alt Next command is startTransaction
EvseManager->>Powermeter: startTransaction
Powermeter-->>Powermeter: stopTransaction
Note over Powermeter: internal triggered stopTransaction will not send <br>a response to EvseManager since no stopTransaction was issued
Powermeter->>Powermeter: Mark need_to_stop_transaction to false
Powermeter-->>EvseManager: startTransaction Response (OK/ID)
Powermeter->>Powermeter: Mark need_to_stop_transaction to true
Note over EvseManager: Transaction started successfully
else Next command is stopTransaction
EvseManager->>Powermeter: stopTransaction
Powermeter-->>EvseManager: stopTransaction Response (OK/OCMF)
Powermeter->>Powermeter: Mark need_to_stop_transaction to false
end
Note over Powermeter: In case of CommunicationError during start/stop<br> transaction please check the start/stop transaction diagrams

View File

@@ -0,0 +1,50 @@
sequenceDiagram
autonumber
participant Powermeter
participant EvseManager
participant OCPP
participant CSMS
title Start of a Transaction
Note over EvseManager: User plugs in EV and authorizes
EvseManager->>OCPP: Event(SessionStarted)
OCPP->>CSMS: StatusNotification.req(Preparing)
CSMS-->>OCPP: StatusNotification.conf
alt successful case
EvseManager->>Powermeter: startTransaction
Powermeter-->>EvseManager: startTransaction Response (OK/ID)
EvseManager->>OCPP: Event(TransactionStarted)
OCPP->>CSMS: StartTransaction.req
CSMS-->>OCPP: StartTransaction.conf
Note over EvseManager: Transaction started successfully
else startTransaction failing due to power loss
EvseManager->>Powermeter: startTransaction
Powermeter-->>EvseManager: startTransaction Response (FAIL)
EvseManager->>OCPP: Event(Deauthorized)
OCPP->>CSMS: StatusNotification.req(Finishing)
CSMS-->>OCPP: StatusNotification.conf
EvseManager->>OCPP: raiseError (PowermeterTransactionStartFailed)
OCPP->>CSMS: StatusNotification.req(Finishing, PowermeterTransactionStartFailed)
CSMS-->>OCPP: StatusNotification.conf
Note over EvseManager: Transaction did not start
end
alt EvseManager configured to become inoperative in case of Powermeter CommunicationError
Powermeter->>EvseManager: raise_error(CommunicationError)
Note over Powermeter,EvseManager: Powermeter raises a CommunicationError <br/>and EvseManager is registered for notification
EvseManager->>OCPP: raise_error (Inoperative)
OCPP->>CSMS: StatusNotification.req(Faulted)
CSMS-->>OCPP: StatusNotification.conf
end

View File

@@ -0,0 +1,45 @@
sequenceDiagram
autonumber
participant Powermeter
participant EvseManager
participant OCPP
participant CSMS
title Stopping Transaction in Error
Note over Powermeter, CSMS: Transaction is running
Powermeter->>Powermeter: detects a <br/> CommunicationError
Note over Powermeter,EvseManager: Powermeter raises a CommunicationError <br/>and EvseManager is registered for notification
Powermeter->>EvseManager: raise_error (CommunicationFault)
Powermeter->>OCPP: raise_error (CommunicationFault)
OCPP->>CSMS: StatusNotification.req(Charging, CommunicationFault)
CSMS-->>OCPP: StatusNotification.conf
alt EvseManager configured to become inoperative in case of PowermeterCommError
EvseManager->>EvseManager: Pause charging
EvseManager->>OCPP: raiseError (Inoperative)
OCPP->>CSMS: StatusNotification.req(Faulted)
Note over EvseManager: Note that we would just continue charging otherwise
end
Note over Powermeter, CSMS: User stops the transaction
alt successful case (Powermeter has no CommunicationError)
EvseManager->>Powermeter: stopTransaction (ID)
Powermeter-->>EvseManager: stopTransaction Response (OK/OCMF)
EvseManager->>OCPP: Event(TransactionFinished(OCMF))
OCPP->>CSMS: StopTransaction.req(OCMF)
CSMS-->>OCPP: StopTransaction.conf
else stopTransaction failing due to subsequent power loss (this applies as well when Powermeter still in CommunicationError)
EvseManager->>Powermeter: stopTransaction (ID)
Powermeter->>EvseManager: stopTransaction Response (FAIL)
EvseManager->>OCPP: Event(TransactionFinished)
Note right of OCPP: In this case we can't stop the transaction including the OCMF
OCPP->>CSMS: StopTransaction.req()
CSMS-->>OCPP: StopTransaction.conf
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

View File

@@ -0,0 +1,117 @@
###########
Explanation
###########
The explanation pages will give you detailed information about the features of
EVerest.
Let us have a look at the most important topics first.
Below that, you will be presented with a categorized list of all articles.
.. grid:: 1 2 2 3
:gutter: 2
.. grid-item-card:: Framework Overview
:link: high-level-overview
:link-type: doc
Get a high-level overview of the EVerest framework.
.. grid-item-card:: Error Framework
:link: error-framework
:link-type: doc
How to communicate error states between modules.
.. grid-item-card:: EVerest Modules in Detail
:link: detail-module-concept
:link-type: doc
Learn about the module concept of EVerest.
.. grid-item-card:: Tier Module Mapping
:link: tier-module-mappings
:link-type: doc
EVerest's 3-tier module mapping explained.
.. grid-item-card:: Adapt EVerest
:link: adapt-everest/index
:link-type: doc
Learn how EVerest can be adapted to your use-case.
.. grid-item-card:: The EVerest Dependency Manager
:link: dev-tools/edm
:link-type: doc
Tool helping to orchestrate dependencies between the different EVerest repositories.
.. grid-item-card:: The ev-cli Development Tool
:link: dev-tools/ev-cli
:link-type: doc
Command line tool to generate C++ code from interface and manifest definitions.
.. grid-item-card:: The Plug&Charge Process in EVerest
:link: pnc-process
:link-type: doc
Learn how Plug&Charge is implemented in EVerest.
.. grid-item-card:: Linux / Yocto and EVerest
:link: linux-yocto/index
:link-type: doc
Learn how to integrate EVerest in your embedded application via Yocto and allow for secure OTA updates.
.. grid-item-card:: Hardware Architecture
:link: hardware-architecture
:link-type: doc
Some ideas and guidance on the general architecture of AC or DC chargers.
.. grid-item-card:: Powermeter OCMF Handling
:link: powermeter-ocmf
:link-type: doc
How OCMF records are expected to be handled by modules implementing powermeters.
.. grid-item-card:: A Selection of included Hardware Drivers
:link: hardware-drivers
:link-type: doc
Description of natively supported hardware driver modules included in EVerest.
.. grid-item-card:: Structure of the EVerest Documentation
:link: the-everest-documentation
:link-type: doc
How this documentation is structured.
.. grid-item-card:: Use the EVerest Development Container
:link: devcontainer-internal/index
:link-type: doc
Internal working of the EVerest development container
for different setup variants and how things are connected.
.. toctree::
:hidden:
:maxdepth: 1
high-level-overview
error-framework
detail-module-concept
tier-module-mappings
adapt-everest/index
energymanagement/index
dev-tools/edm
dev-tools/ev-cli
pnc-process
linux-yocto/index
hardware-architecture
hardware-drivers
the-everest-documentation
powermeter-ocmf
devcontainer-internal/index

View File

@@ -0,0 +1,19 @@
.. _exp_linux_yocto:
#########################
Linux / Yocto
#########################
You will find different explanations about using EVerest with
Linux and Yocto in the following sections:
.. toctree::
:maxdepth: 1
setting-up-linux-os
building-yocto
ota-updates
partitioning-schemes-for-rauc-ota
Make sure to check out the :doc:`How-to-guide on cross-compilation </how-to-guides/yocto-cross-compilation>`
for a step-by-step guide on how to cross-compile EVerest for a Yocto-based Linux system.

View File

@@ -0,0 +1,248 @@
.. _exp_linux_yocto_ota_updates:
##########################
Over-the-air-updates (OTA)
##########################
One of the most important (and often underestimated) features of a
charging station is the ability to remotely update the software when the
charger is installed. Updates can provide:
- General bug fixes
- Fixing compatibility issues with new EVs (or old EVs with new
firmware versions)
- Fixing compatibility issues with OCPP backends (or new versions
deployed on the backend side)
- Security issues
- New features
Updates may be delivered remotely over a network, called Over-the-Air (OTA),
or may be provided locally where supported by the charging station.
EVerest supports *RAUC* as an update tool, which has the following advantages:
- Open source project with a large community:
https://rauc.io
- Secure by design: The update files are cryptographically signed (and
optionally encrypted). Signature is checked during installation, so
the source of the update file can be trusted. This simplifies the
update delivery process a lot compared to other tools that only rely
on transport mechanism security. Updates can be downloaded from a
simple unencrypted HTTP server or even a local USB flash drive
without compromising security.
- Robust: Uses A/B partitioning and does full image updating
- Atomic switching between A/B slots can be implemented
- Support partial downloads by HTTP streaming: Block based partial
downloads reduce the bandwidth needed
There are some considerations to make when choosing an update system:
+-----------------------------------+-----------------------------------+
| Full image updates | Partial component / individual |
| | file updating |
+===================================+===================================+
| Very robust. The complete image | Risk of producing an installed |
| always has the correct | combination where one component |
| dependencies built in. | is too old to work with the other |
| | recently updated component. |
| | Requires careful tracking of |
| | compatibility between components. |
+-----------------------------------+-----------------------------------+
| Writing full images to A/B slots | Often quite complex |
| is straightforward. Combined with | implementations. That can |
| an atomic switch between the | introduce a lot of room for bugs |
| boot slots, there is no critical | which brick devices during failed |
| time where e.g. a power loss | updates, power losses during |
| could brick a device. | updates or upgrading to |
| | incompatible updater software |
| | versions. |
+-----------------------------------+-----------------------------------+
| Simple versioning: a single | Complex versioning: Always a |
| version number is enough to | combination of the different |
| specify which software image | components / files. |
| version is installed. | |
+-----------------------------------+-----------------------------------+
| Recovers from file system errors | Relies entirely on the filesystem |
| in the root partition: It writes | implementation to repair itself |
| a new clean FS on every update | and may brick if that fails. |
+-----------------------------------+-----------------------------------+
| Updates everything: rootfs, | Often limited to e.g. application |
| kernel, bootloader, … | update. It may e.g. not update |
| | kernel or base system. |
+-----------------------------------+-----------------------------------+
| Downside: Full image updates | Advantage: only download changed |
| require more download | files and thus have the smallest |
| bandwidth/data. Can be mitigated | possible download. |
| to some extent by block based | |
| partial download. | |
+-----------------------------------+-----------------------------------+
An update process should consider the bootloader, loading the Linux kernel, and
the root file system. A root file system can be a standard Linux partition (ext4).
Other solutions are available including: squashfs, file system snapshots, and
bundle based solutions (NixOS, Snap). The root file system is usually read-only
and an overlay file system is used to support charger specific updates.
An OTA solution needs to consider how configuration information is maintained
across root file system updates.
EVerest has chosen RAUC as the most suitable update system, mainly due to its
robust, brick-free mechanisms and its inherent security features.
RAUC can support adaptive updates that use HTTP streaming to only download
blocks that have changed between releases. This can reduce the overheads of using
full images.
Security is provided on a block-based level, so there is no need to
first download the complete image and validate signature etc. It is done
on the fly.
This also means that no extra disk space is needed to store the update
image: It will be directly streamed from the source into the inactive
slot partition.
RAUC implementation in EVerest
------------------------------
EVerest interacts with RAUC via its D-Bus interface. This is provided by the
`Linux Systemd Rauc module </reference/modules/Linux_Systemd_Rauc>`_.
In EVerest the update process is fully integrated with OCPP.
In the OCPP use case, the CPO will need to provide storage for the
update file that is accessible via HTTP with range requests. The CSMS
then sends this URL in the update request to EVerest, and EVerest will
trigger RAUC on the D-Bus to actually perform the update.
You will need to implement the following in your Yocto system as this is
very system dependent:
- A partitioning setup that provides A/B slots for rootfs, A/B boot and
data partitions (more details about this is covered in the appendix!)
- RAUC configuration file for your setup (system.conf)
- RAUC backend that performs switching slots/marking good/bad. RAUC
already comes with backend support for many bootloaders such as
U-Boot and Barebox etc.
- PKI to be used for signing / optionally encrypting the update files
- A recipe that builds RAUC bundles (update files) directly in Yocto
If you use PHYTEC SoMs: Their *ampliPHY* distribution already has working
examples for all of the above in *ampliphy-rauc* or *ampliphy-secure*
distributions.
Refer to RAUC's integration documentation for more information:
https://rauc.readthedocs.io/en/latest/integration.html
RAUC has support for atomic switching between slots and uses features from
the bootloader. It is important to understand this interaction since the
bootloader may be able to automatically rollback if an update is not successful.
Some processors also support secure and encrypted boot options which can ensure
that only valid images are loaded. They may also provide mechanisms to support
dual boot loaders.
.. tip::
Look at the documentation for your processor and chosen bootloader to
understand what options are provided for slot switching and automatic boot
failure recovery.
Test your integration locally first using RAUC on the command line:
.. tip::
rauc install http://myudateserver.com/version1.raucb
RAUC should perform a successful installation on the currently unused
slot. Once that is done, issue a reboot and verify it cleanly boots into
the new slot.
Once booted successfully into the new slot, you need to mark the slot as
“good”, otherwise it may fall back to the previous one on the next
boot.
Some implementations do this in a *systemd* service that runs at the end
of the boot process. This is not recommended in production. EVerest
will take care of marking the slot as "good" when EVerest starts up
successfully. It will then also report the status to the OCPP backend
automatically etc.
To mark it "good", manually use:
.. code-block:: bash
rauc status mark-good
You also may want to check RAUC's status before and after the update to
verify it is configured correctly. It shows an output like this:
.. code-block:: bash
root@mysystem:~# rauc status
=== System Info ===
Compatible: mysystem-v1
Variant:
Booted from: rootfs.0 (system0)
=== Bootloader ===
Activated: rootfs.0 (system0)
=== Slot States ===
[bootloader.0] (/dev/mmcblk1, boot-emmc, inactive)
o [rootfs.1] (/dev/mmcblk1p6, ext4, inactive)
bootname: system1
boot status: good
[boot.1] (/dev/mmcblk1p2, vfat, inactive)
x [rootfs.0] (/dev/mmcblk1p5, ext4, booted)
bootname: system0
mounted: /
boot status: good
[boot.0] (/dev/mmcblk1p1, vfat, active)
Also try to use *mark-bad* and test if it falls back to the previous one
on the next boot.
EVerest interacts with RAUC via D-Bus, so make sure it is running as a
D-Bus service. The D-Bus interface is also the boundary between
EVerest and the underlying Linux system here.
Once you verified that RAUC performs updating and fall-backs in manually
controlled command line mode, you should be all set up for EVerest
updates.
Custom Update Mechanism
------------------------
In case you do not want to use RAUC and/or integrate your custom update
mechanism into EVerest, you can also implement the
`EVerest System API <../../reference/api/system_API/index.html>`_.
This would still allow you to update EVerest via OCPP, but you would need to handle
the actual update process yourself and provide status updates to EVerest via the
System API.
Optimize the base system
------------------------
If you have a lot of processes running in the Linux system and a very
high CPU load (which easily happens on small embedded systems), take
some time to select the correct nice levels for all services running on
the system. You can set the nice level in the systemd unit files.
.. tip::
Being "nicer" means getting CPU less often if lots of processes are scheduled.
Especially for high-level communication (aka ISO 15118), run EVerest at
e.g. a nice level of -20 to ensure it is getting enough CPU slices
during the charging process. If you have other tasks outside of
EVerest, make sure they have a higher nice level.
Using a preemptive kernel is also a good idea to ensure low latencies in
user space. Check *CONF_PREEMT* documentation in the Linux kernel.
--------------------------------
**Authors**: Cornelius Claussen, Manuel Ziegler, Piet Gömpel

View File

@@ -0,0 +1,61 @@
.. _partitioning-schemes-for-rauc-ota:
#################################
Partitioning schemes for RAUC OTA
#################################
As there are many ways to set up partitions on the storage device for
RAUC-based updates, this chapter will only provide a few ideas. Your
actual implementation may be different in the end.
As a target, we would like to have:
- two full-size A/B rootfs partitions
- two full-size A/B boot partitions for bootloader and FIT image
(containing kernel/initrd/device tree for secure boot)
- one/two user data partition(s)
- one small factory data partition that contains (read only) files that
are programmed once during production and will never change
(e.g. serial numbers, certification region config etc)
The user data partition can be mounted as overlayfs on specific folders
to store run time-generated data (e.g. log files, user configuration
files, certificates, ...).
A factory reset should be implemented that clears the overlayfs.
The most simple version of this is to use a single user data partition
and mount it as overlay (e.g. on */var*) both for slot A and B. Then all
changes in the overlay will survive an update of the underlying rootfs.
For an example on how to do this, refer to the BelayBox Yocto sources.
The Raspberry Pi uses three boot partitions, for most other boards only
two are needed.
.. code-block:: shell
part --source bootimg-partition --ondisk mmcblk0 --fstype=vfat --label boot --active --align 4096 --fixed-size 512
part --source bootimg-partition --ondisk mmcblk0 --fstype=ext4 --label boot_a --align 4096 --fixed-size 512
part --source bootimg-partition --ondisk mmcblk0 --fstype=ext4 --label boot_b --align 4096 --fixed-size 512
part --source rootfs --ondisk mmcblk0 --fstype=ext4 --label root_A --align 4096 --fixed-size 3000
part --source rootfs --ondisk mmcblk0 --fstype=ext4 --label root_B --align 4096 --fixed-size 3000
part --ondisk mmcblk0 --fstype=ext4 --label factory_data --align 4096 --fixed-size 128
part --ondisk mmcblk0 --fstype=ext4 --label overlay --align 4096 --fixed-size 7000
The disadvantage of this is the following: If the configuration file
format changes due to an update of the underlying rootfs, a separate job
may need to be run on the first boot into the new slot to transfer the
configuration files to the new format. If the boot into the new slot
fails, it will fall back to the old slot. The older version then is
maybe not compatible with the new config file format, so a full fallback
is not possible in this case.
To allow for better config file migration with fallback, consider to use
two user data partitions and a separate migration task (e.g. in initrd)
that transfers the files from the old user data partition to the new
one. It may adapt the file format in the process. In this case, falling
back to the old rootfs will work as it will also use the older overlay
user partition.
If you have an eMMC device, consider using the hardware boot partition
feature that eMMC offers for the bootloader. This will enable atomic
switching between the active boot slots.

View File

@@ -0,0 +1,105 @@
.. _exp_linux_yocto_setup_linux_os:
#####################################
Setting up the Linux operating system
#####################################
In principle, you can use any Linux-based operating system as long as it
comes with the required dependencies to run EVerest.
We strongly recommend using Yocto as it has some advantages over other
distributions:
- It can be set up to do reproducible builds with versioning.
- Most CPUs and SoMs already come with Yocto board support packages
(BSP).
- EVerest comes with full support for selected Yocto long term support
releases (LTS) (scarthgap as of the time of writing).
- It can be nicely integrated with your CI/CD to build complete
production images and update packages.
- It provides a software bill of materials of all packages in the Linux
system for managing licenses.
- Broad community
- Automatic generation of cross-compile toolchains, that can be used
during the development phase.
You can find more information about the Yocto project here:
https://www.yoctoproject.org
.. warning::
Setting up the Linux base system for your product is a quite complex task
that should be performed by domain experts. In case you do not have experts
in your team, consider getting help from a company specialized on this.
The end product's reliability, security and user experience strongly depends
on a sound architecture, implementation and maintenance strategy of the base
Linux system. This should not be underestimated.
Covering all aspects of setting up a Linux base system is out of the
scope of this documentation, but we would like to give some examples and ideas
and point out some typical solutions to questions you will have on your
journey. Do not consider this complete!
Setup a Yocto build environment
-------------------------------
Yocto has comprehensive caching capabilities that mean build times are substantially
reduced for successive builds. However an initial build will take hours since initial
versions need to be fetched and built so that caches are populated. There is support
for sharing downloads and caches that can reduce build times and are worth
considering where you have a co-located team.
A good build machine will have lots of RAM, SSD storage and multi-core processor as
well as a fast Internet connection.
It is possible to use a high-performance laptop especially for incremental builds
once the initial build is complete.
.. warning::
Running this inside of a virtual machine is not recommended.
A full Yocto build easily requires 50-100 GB of disk space, and it will
use multiple cores. So, make sure you have enough RAM per core (e.g. 2-3
GB per (hyperthread) core).
Install a Linux distribution supported by Yocto and install all
necessary dependencies. See here for more information about that:
https://docs.yoctoproject.org/ref-manual/system-requirements.html
Alternatively, consider building in a container. Once you move to
production, a build container will probably be needed anyway to build
images in your CI/CD.
It is also recommended to archive the containers to be able to do fully
reproducible builds of older versions in the future.
Yocto itself can produce builds that are completely tagged (i.e. each
source package is tagged with a fixed version or Git hash), so they are
in principle reproducible.
There are - however - a few build dependencies to the host system that
may prevent you from building your released 1.0 version in ten years
from now. As an example, the Python version in ten years from now may
not run the old bitbake correctly anymore. Also, the Yocto recipes
contain only download URLs and version tags, but not the source packages
itself.
Let's start with an example and set up the Yocto build environment that
we use for EVerest on the BelayBox hardware.
Building the BelayBox Yocto image
---------------------------------
An example can be found here for the BelayBox:
https://github.com/PionixPublic/dev-hardware-yocto
Check out the *README* in this repository on how to build and install
this Yocto on the BelayBox.
--------------------------------
**Authors**: Cornelius Claussen, Manuel Ziegler

View File

@@ -0,0 +1,149 @@
.. _exp-pnc-process:
##################################
The Plug&Charge Process in EVerest
##################################
This is an explaination how Plug&Charge is technically implemented in EVerest.
For a tutorial on how to do Plug&Charge in the EVerest software-in-the-loop, please refer to
the :doc:`Plug&Charge tutorial </tutorials/plug-and-charge>`.
For a goal oriented how-to-guide, pleaser refer to :doc:`/how-to-guides/configure-pnc`.
*************************
Plug&Charge Authorization
*************************
There are a lot of resources available on Plug&Charge and ISO15118 PKI involved in this process,
so this guide is not going to repeat how Plug&Charge actually works.
It rather explains what EVerest provides with respect to Plug&Charge and how EVerest needs to
be configured in order to suit your Plug&Charge use case.
************************************
The Authorization process in EVerest
************************************
In essence, the Plug&Charge Authorization runs like any other authorization in EVerest,
like local RFID authorization or remote authorization. Have a look at how the authorization
process in EVerest in designed within the :ref:`Documentation of the Auth module <everest_modules_handwritten_Auth>`.
************************
Involved EVerest modules
************************
The E2E Plug&Charge process involves communication from the EV to systems in the cloud. The
main protocols involved are ISO15118 and OCPP. In EVerest, several modules and interfaces
are involved in the Plug&Charge process. Here is an overview of how everything comes together
in EVerest:
.. image:: images/plug_and_charge_modules.png
:align: center
.. note::
This visualization only presents the interfaces and connections between them that are
relevant for Plug&Charge.
Let's have a look step by step:
Step 0
======
Before a Plug&Charge session can start, the following certificates and keys should be installed on
the charger:
* V2G Root certificate
* SECC Leaf certificate
* SECC Leaf private key
* MO Root certificate (optional)
These certificates and keys can be installed during provisioning of the charger, or they can be
installed using OCPP1.6 or OCPP2.x. The paths to store these files can be configured in the
EvseSecurity module. Please see the :ref:`Documentation of the EvseSecurity <everest_modules_EvseSecurity>`
for further information on how to do the configuration for this module.
In the visualization, step (0) shows the process that represents the previously described process of
provisioning the charger with the correct certificates, before there is a physical
connection to the EV. The OCPP/OCPP201 and EvseV2G module require a module that implements
the :doc:`evse_security interface </reference/interfaces/evse_security>`,
in order to execute the following commands:
* install_ca_certificate (Used by OCPP to install root certificates. This process is initiated by the OCPP CSMS)
* update_leaf_certificate (Used to install or update SECC leaf certificates)
* generate_certificate_signing_request (Used to generate a CSR that is used in the SignCertificate.req of OCPP)
* verify_certificate (Used by EvseV2G to verify the contract certificate and by OCPP to verify new leaf certificates)
* get_mo_ocsp_request_data (Used by EvseV2G and OCPP to get the OCSP request data of the contract certificate (chain))
There are more commands provided by the :doc:`evse_security interface </reference/interfaces/evse_security>`,
which are not included in the Plug&Charge process.
For a successful Plug&Charge authorization process, the following certificates need to be installed on the charger:
* SECC leaf certificate (including sub cas)
* V2G Root Certificate(s)
* MO Root Certificates(s) (only if the EV contract shall be verified locally).
This can be controlled by the OCPP configuration keys described in the section
:ref:`how-to-configure-pnc-ocpp-configuration` for more information.
As mentioned above, these certificates can be installed manually or by the CSMS. In case Plug&Charge is enabled
and no (valid) SECC leaf certificate is installed or it expires within the next 30 days, the charging station
will attempt to retrieve a SECC leaf certificate from the CSMS automatically. This process can also be triggered
manually by the CSMS by using a *TriggerMessage(SignCertificate).req* message.
Step 1
======
This step is triggered by a physical connection between the EV and the charger. A TLS connection is required
between the EV and the charger to allow Plug&Charge, so the EvseV2G module retrieves the SECC leaf certificate
chain and private key from via the evse_security.yaml interface and sets up a TLS server, to which the EV
can connect as a TLS client.
Step 2
======
When charger and EV have agreed on Contract being the selected payment option, we have something going on
that we can call a Plug&Charge process. The EV sends its contract certificate chain and requests authorization
from the charger. The EvseV2G module generates a
:ref:`ProvidedIdToken <authorization-ProvidedIdToken>`,
which is the EVerest type that contains data about the authorization request, including the contract
certificate and OCSP request data.
The *ProvidedIdToken* is transmitted via the *evse_manager* interface to the EvseManager module.
Step 3
======
The EvseManager module implements the *token_provider* interface and can therefore publish the
:ref:`ProvidedIdToken <authorization-ProvidedIdToken>`
containing the contract certificate and OCSP data within EVerest to the central authorization module
in EVerest: Auth.
Step 4
======
The Auth module sends commands containing the *ProvidedIdToken* to its registered
:doc:`token_validator(s) </reference/interfaces/auth_token_validator>`,
which are OCPP/OCPP201 in the case of Plug&Charge. The OCPP module(s) validate the token based on the requirements
specified in the OCPP protocol (either validating locally or by the CSMS).
Step 5
======
In case the validation was successful, the Auth module notifies the EvseManager using the authorize command,
that authorization is present and the charging session can be started.
Step 6
======
The EvseManager forwards the authorization response to the EvseV2G module, which can then send the
awaited ISO15118 response to the EV.
.. note::
We have taken some shortcuts and ignored some further communication going on during the full process,
but these steps cover what's important for Plug&Charge in EVerest.
----
**Authors**: Piet Gömpel

View File

@@ -0,0 +1,41 @@
.. _exp-powermeter-ocmf:
########################
Powermeter OCMF Handling
########################
This document explains how EVerest modules implementing the :doc:`powermeter interface </reference/interfaces/powermeter>`
shall handle OCMF report generation and transmission when used in conjunction with the
:ref:`EvseManager module <everest_modules_EvseManager>`.
The following sequence diagrams illustrate the interactions between the involved modules
during the start and stop of a transaction, including error handling scenarios:
- :ref:`Start of a transaction <exp-powermeter-ocmf-start-transaction>`
- :ref:`Stopping transaction in error <exp-powermeter-ocmf-stopping-transaction-error>`
- :ref:`Start of Powermeter or recovery after communication loss <exp-powermeter-ocmf-start-recovery>`
.. _exp-powermeter-ocmf-start-transaction:
Start of a transaction
======================
.. mermaid:: images/ocmf_start_of_transaction.mmd
.. _exp-powermeter-ocmf-stopping-transaction-error:
Stopping Transaction in Error
=============================
.. mermaid:: images/ocmf_stopping_transaction_in_error.mmd
.. _exp-powermeter-ocmf-start-recovery:
Start of Powermeter or recovery after communication loss
========================================================
.. mermaid:: images/ocmf_start_of_pmeter_or_transaction_after_powerloss.mmd
----
**Authors**: Florin Mihut, Piet Gömpel

View File

@@ -0,0 +1,161 @@
.. _exp_the_everest_documentation:
#########################
The EVerest Documentation
#########################
This section explains how different files in different places are compiled
into to html document you are reading. The general structure that is being
aimed for is also explained. If you only read one subsection on this page
it should be
:ref:`Structure of the Documentation <exp_the_everest_documentation_structure_of_doc>`.
If you only want to modify existing documents, this may well be sufficient.
Practical instructions on working on the documentation are located in the
:ref:`How-to section <documenting_everest>`.
.. _exp_the_everest_documentation_structure_of_doc:
******************************
Structure of the Documentation
******************************
Our documentation is structured according to the `Diátaxis <https://diataxis.fr/>`_ framework:
* *tutorials*: Learn by doing through guided practice.
* *how-to guides*: Practical steps to achieve specific tasks.
* *reference*: Technical facts, APIs, and configuration details.
* *explanations*: Conceptual deep-dives and background theory.
**Tutorials** shall allow a new user to successfully do *something*. No concrete
real-world problem needs to be solved at this point (as the Diátaxis authors put
it: a driving lesson is just not about getting from A to B but about the driving
itself). Deep explanations are to be avoided. It's important to provide a
safe route to some success and allow the reader to gain confidence in his developing
practical skills.
**How-to guides** show how real-world problems are solved by giving practical directions.
The target audience are users which already gained some knowledge through other means.
The instructions are practical and serve to achieve specific goals that many users need.
**Reference** material may be worthless to the novice because it requires an
understanding of the EVerest framework and does not give any practical advice.
For users who are familiar with the basics, the reference is a goal-agnostic, precise and
effective source of facts for all the decisions to be made in everydays work.
**Explanations** provide the necessary context and background to understand.
Things learned in *tutorials* and *how-to guides* will often require further
knowledge to be put in a bigger picture and reveal the *why* of many technical
decisions encoded in the EVerest frameworks architecture.
Please keep this framework in mind when contributing new content. We encourage you
to split your contributions into multiple documents that align with the Diátaxis philosophy.
Linking between these documents ensures users have quick access to related material
without cluttering a single page.
By keeping individual documents focused and concise, they become much more readable.
Tutorials, in particular, should remain brief and link to the Explanations section for
deeper background information.
Since this structure was not chosen from the outset, it is quite possible that
some sections of the EVerest documentation do not conform to this structure in
an exemplary manner. These should therefore not be regarded as good examples that
should be followed without further consideration.
************
Source Files
************
EVerest documentation uses Sphinx as documentation generator. As input format,
reStructuredText is used. See here for more information about Sphinx:
https://www.sphinx-doc.org/en/master/
The :ref:`Sphinx Style Guide <everest_doc_sphinx_style_code>` included in this
documentation serves as a reference for the syntax.
.. note::
It is not required to get a deep understanding of Sphinx to create
documentation for EVerest. You can check existing pages and you will
see how easy it is to start documenting. In the end always make sure
the end result (html) looks as intended!
The locations of the source files that make up the documentation you are reading,
are within the `EVerest/EVerest repository <https://github.com/EVerest/EVerest>`_.
.. note::
You will find a number of documentation files that are not part of the documentation you are reading
but still reside inside the `EVerest/EVerest repository <https://github.com/EVerest/EVerest>`_.
See :ref:`below <documenting_everest_doc_near_source_code>`.
Main EVerest Documentation
==========================
This is a coherent documentation that helps you with getting a fast overview
of the EVerest framework, the EVerest tools and also contains some tutorials.
Reference Documentation
=======================
EVerest interfaces, modules, types and the EVerest API contain documentation
as part of their definitions, right inside the corresponding yaml files.
Those files may also contain configuration settings along with short explanations.
In the `EVerest/EVerest repository <https://github.com/EVerest/EVerest>`_:
* ``types/*.yaml``: Definitions of the internal EVerest types for inter-module communication.
This adds to the *reference* section.
* ``interfaces/*.yaml``: Definitions of the internal interfaces for inter-module communication.
This adds to the *reference* section.
* ``modules/.../manifest.yaml``: Definition of the individual modules. This adds to the *reference*
section.
* ``docs/source/reference/EVerest_API``: This specific subfolder contains the definitions of the
*EVerestAPI*. They are transformed to html to become part of the *reference* section.
The generated pages can be found in
:ref:`the reference section of the main documentation <everest_reference>`.
Optionally, EVerest modules can contain additional handwritten documentation.
See next subsection for more information on this.
Handwritten Documentation
=========================
Each module directory can contain additional handwritten documentation.
- ``modules/.../docs/index.rst``: Handwritten explanations for individual modules.
The contents will automatically be hyperlinked from the page containing the
automatically generated reference docs (explained in the subsection before).
It's considered good practice to also link back from the handwritten
text to the auto-generated reference page of the respective module.
As an example, see the auto-generated
:ref:`reference page of the EvseManager <everest_modules_EvseManager>`.
In the second paragraph, you see a link to the detailed handwritten
documentation.
General documentation that is not associated with a specific module:
- ``docs/source``: Find *tutorials*, *how-to-guides* and *explanations* source files here.
.. _documenting_everest_doc_near_source_code:
Documentation Near Corresponding Source Code
============================================
The documentation parts explained up to now are all part of the main EVerest
documentation you are reading right now. Some documentation snippets can also
be found directly in different GitHub repositories of the EVerest organisation.
These are often README.md files stored near the corresponding source code.
Those docs snippest are not being pushed to the EVerest main documentation.
Examples:
- md files in certain places the EVerest repository
- ``docs/README.md``: How to build the documentation you are reading
- ``applications/utils/everest-testing/README.md``: How to use pytest with EVerest
- md/general doc files in other repos (`everest-admin-panel <https://github.com/EVerest/everest-admin-panel>`_,
`ext-switchev-iso15118 <https://github.com/EVerest/ext-switchev-iso15118>`_, ...)

View File

@@ -0,0 +1,95 @@
.. _tier_module_mapping:
**********************
3-tier Module Mappings
**********************
EVerest modules and even individual interface implementations can have mappings
assigned to them. These mappings are inspired by the OCPP 3-tier model and are
available for error handling since `everest-framework v0.16.0 <https://github.com/EVerest/everest-framework/releases/tag/v0.16.0>`_,
which is included in EVerest since `release 2024.7.0. <https://github.com/EVerest/EVerest/releases/tag/2024.7.0>`_.
These mappings are exposed for usage in module code since `everest-framework v0.18.0 <https://github.com/EVerest/everest-framework/releases/tag/v0.18.0>`_,
which is included in EVerest since `release 2024.10.0. <https://github.com/EVerest/EVerest/releases/tag/2024.10.0>`_.
Following an example how a mappping for the EvseManager could look like:
.. code-block:: yaml
connector_1:
module: EvseManager
mapping:
module:
evse: 1
connector: 1
This would result in a mapping of the whole module,
including its implementations for e.g. evse and token_provider to "evse = 1"
and "connector = 1".
By default, a module is mapped to the whole charging station.
So to ensure that only the parts of the module that should belong
to a specific evse/connector are actually mapped to it,
you could replace this simple mapping with a more detailed one
as shown in the following example:
.. code-block:: yaml
connector_1:
module: EvseManager
mapping:
implementations:
evse:
evse: 1
connector: 1
Here, the module stays mapped to the whole charging station
and therefore an implementation as well. For the "evse" implementation,
this mapping is now overwritten to indicate that it belongs to
a specific "evse = 1" and "connector = 1".
Modules can access the mapping information in the following ways depending
on which specific information is required.
If the mapping of a requirement is of interest it can be accessed via a
get_mapping() function:
.. code-block:: cpp
r_name_of_the_requirement->get_mapping()
This returns an optional Mapping struct.
If the mapping of an interface implementation is of interest it can
also be accessed via a get_mapping() function:
.. code-block:: cpp
p_name_of_an_implementation->get_mapping()
This returns an optional Mapping struct.
If the mapping of the current module is of interest it can be accessed via the
module info:
.. code-block:: cpp
this->info.mapping
This returns an optional Mapping struct.
Mapping information is also available in error reporting via
"error.origin.mapping":
.. code-block:: cpp
const auto error_handler = [this](const Everest::error::Error& error) {
const auto evse_id = error.origin.mapping.has_value() ? error.origin.mapping.value().evse : 0;
};
const auto error_cleared_handler = [this](const Everest::error::Error& error) {
const auto evse_id = error.origin.mapping.has_value() ? error.origin.mapping.value().evse : 0;
};
subscribe_global_all_errors(error_handler, error_cleared_handler);