Files
Eric F d398a6ced2 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
2026-06-08 00:38:27 -04:00

744 lines
26 KiB
ReStructuredText

.. _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.