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:
@@ -0,0 +1,109 @@
|
||||
# California Pricing Requirements
|
||||
|
||||
OCPP has several whitepapers, which can be found here: https://openchargealliance.org/whitepapers/
|
||||
|
||||
One of them is OCPP & California Pricing Requirements. This can be optionally enabled in libocpp, for OCPP 1.6 as well as OCPP 2.0.1.
|
||||
|
||||
## Callbacks in libocpp
|
||||
|
||||
To be kind of compatible with eachother, the callbacks for OCPP 1.6 and 2.0.1 use the same structs with the pricing
|
||||
information.
|
||||
|
||||
### User-specific price / SetUserPrice
|
||||
|
||||
The User-specific price is used for display purposes only and can be sent as soon as the user identifies itself with an
|
||||
id token. It should not be used to calculate prices.
|
||||
Internally, the messages in the DataTransfer json (for 1.6) is converted to a `TariffMessage`, defined in
|
||||
`common/types.hpp`. In case of multi language messages, they are all added to the TariffMessage vector.
|
||||
|
||||
In order to be able to use information from `SetUserPrice` before a transaction is started (e.g. for the OCMF TT field), OCPP1.6 implements a mechanism to wait for `DataTransfer.req(SetUserPrice)` from
|
||||
the CSMS before the `authorize_id_token` function returns. This is required because the `Authorize.conf` in OCPP1.6 does not contain this information (unlike OCPP2.x). This allows to integrate the information
|
||||
from `DataTransfer.req(SetUserPrice)` in the response of the authorization request. The timeout for waiting for this message from the CSMS can be controlled using the `WaitForSetUserPriceTimeout`
|
||||
configuration key, which is specified in milliseconds. If no `DataTransfer.req(SetUserPrice)` is received within the specified timeout, the result is returned and a transaction may still start.
|
||||
|
||||
If the message is sent when a transaction has already started, the session id will be included in the session cost message and the `IdentifierType` will be set to `SessionId`. If it has not started yet, the id token is sent with
|
||||
`IdentifierType` set to `IdToken`.
|
||||
|
||||
### Running cost and Final / total cost
|
||||
|
||||
The running cost and final cost messages are converted to a `RunningCost` struct, also defined in `common/types.hpp`.
|
||||
The triggers in the message (running cost) are handled in libocpp itself.
|
||||
The prices are converted to integers, because floating point numbers are not precise enough for pricing calculations.
|
||||
To set the number of decimals to calculate with, you should set NumberOfDecimalsForCostValues (1.6, in CostAndPrice /
|
||||
2.0.1, TariffCostCtrlr). Default is 3. There might be messages in multiple languages, they are all added to the messages
|
||||
vector.
|
||||
|
||||
## OCPP 1.6
|
||||
|
||||
OCPP 1.6 mostly uses DataTransfer to send the pricing messages, and also has some extra configuration items. In libocpp,
|
||||
the DataTransfer message is converted to internally used structs as described above.
|
||||
|
||||
### Configuration Items
|
||||
|
||||
| Name | Description |
|
||||
| ---- | ----------- |
|
||||
| `CustomDisplayCostAndPrice` | Set to `true` to enable California Pricing (readonly) |
|
||||
| `DefaultPrice` | Holds the default price and default price text in a json object. Can be updated by the CSMS. Not used by libocpp. See the specification for the specific fields. |
|
||||
| `NumberOfDecimalsForCostValues` | Holds the number of decimals the cost / price values are converted with. |
|
||||
| `CustomIdleFeeAfterStop` | Set to `true` to extend the transaction until `ConnectorUnplugged` is sent (readonly). The chargepoint implementation should send this DataTransfer message, this is not part of libocpp (yet, 2024-08) |
|
||||
| `CustomMultiLanguageMessages` | Set to `true` to enable multi language support (readonly). |
|
||||
| `Language` | Default language code for the stations UI (IETF RFC5646) (readwrite). |
|
||||
| `SupportedLanguages` | Comma separated list of supported languages, specified as IETF RFC5646 (readonly). |
|
||||
| `DefaultPriceText` | Holds an array (`priceTexts`) of default price texts in several languages. Each item has the `priceText` (string) in the given `language` (string) and a `priceTextOffline` (string) in the given language. The CSMS sends the DefaultPriceText per language: `"DefaultPriceText,\<language code\>"`, but libocpp will convert it to the above described json object. (readwrite) |
|
||||
| `TimeOffset` | As OCPP 1.6 does not have built-in support for timezones, you can set a timezone when displaying time related pricing information. This timezone is also used for the `atTime` trigger. (readwrite) |
|
||||
| `NextTimeOffsetTransitionDateTime` | When to change to summer or winter time, to the offset `TimeOffsetNextTransition` |
|
||||
| `TimeOffsetNextTransition` | What the new offset should be at the given `NextTimeOffsetTransationDateTime` (readwrite) |
|
||||
|
||||
### Callbacks
|
||||
|
||||
For California Pricing to work, the following callbacks must be enabled:
|
||||
|
||||
- `session_cost_callback`, used for running cost and final cost
|
||||
- `tariff_message_callback`, used to show a user specific price
|
||||
|
||||
## OCPP 2.0.1
|
||||
|
||||
OCPP 2.0.1 uses different mechanisms to send pricing information. The messages are converted to internally used structs as described above. For California Pricing Requirements to work, TariffAndCost must be implemented as well.
|
||||
|
||||
### Device Model Variables
|
||||
|
||||
| Variable name | Instance | Component | Description |
|
||||
| ------------- | -------- | --------- | ----------- |
|
||||
| `CustomImplementationEnabled` | `org.openchargealliance.costmsg` | `CustomizationCtrlr` | Set to 'true' to support California Pricing (actually to indicate `customData` fields for California Pricing are supported). |
|
||||
| `Enabled` | `Tariff` | `TariffCostCtrlr` | Enable showing tariffs. |
|
||||
| `Enabled` | `Cost` | `TariffCostCtrlr` | Enable showing of cost. |
|
||||
| `TariffFallbackMessage` | | `TariffCostCtrlr` | Fallback message to show to EV Driver when there is no driver specific tariff information. Not used by libocpp. |
|
||||
| `TotalCostFallbackMessage` | | `TariffCostCtrlr` | Fallback message to sho to EV Driver when CS can not retrieve the cost for a transaction at the end of the transaction. Not used by libocpp. |
|
||||
| `Currency` | | `TariffCostCtrlr` | Currency used for tariff and cost information. |
|
||||
| `NumberOfDecimalsForCostValues` | | `TariffCostCtrlr` | Holds the number of decimals the cost / price values are converted with. |
|
||||
| `TariffFallbackMessage` | `Offline` | `TariffCostCtrlr` | Fallback message to be shown to an EV Driver when CS is offline. Not used by libocpp. |
|
||||
| `OfflineChargingPrice` | `kWhPrice` | `TariffCostCtrlr` | The energy (kWh) price for transactions started while offline. Not used by libocpp. |
|
||||
| `OfflineChargingPrice` | `hourPrice` | `TariffCostCtrlr` | The time (hour) price for transactions started while offline. Not used by libocpp. |
|
||||
| `QRCodeDisplayCapable` | | `DisplayMessageCtrlr` | Set to 'true' if station can display QR codes |
|
||||
| `CustomImplementationEnabled` | `org.openchargealliance.multilanguage` | `CustomizationCtrlr` | Enable multilanguage |
|
||||
| `TariffFallbackMessage` | `<language code>` | `TariffCostCtrlr` | TariffFallbackMessage in a specific language. There must be a variable with the language as instance for every supported language. |
|
||||
| `OfflineTariffFallbackMessage` | `<language code>` | `TariffCostCtrlr` | TariffFallbackMessage when charging station is offline, in a specific language. There must be a variable with the language as instance for every supported language. |
|
||||
| `TotalCostFallbackMessage` | `<language code>` | `TariffCostCtrlr` | Multi language TotalCostFallbackMessage. There must be a variable with the language as instance for every supported language. |
|
||||
| `Language` | | `DisplayMessageCtrlr` | Default language code (RFC 5646). The `valuesList` holds the supported languages of the charging station. The value must be one of `valuesList`. |
|
||||
|
||||
> **_NOTE:_** Tariff and cost can be enabled separately. To be able to use all functionality, it is recommended to
|
||||
enable both. If cost is enabled and tariff is not enabled, the total cost message will not contain the personal message (`set_running_cost_callback`).
|
||||
If tariff is enabled and cost is not enabled, the total cost message will only be a TariffMessage
|
||||
(`tariff_message_callback`) containing the personal message(s).
|
||||
|
||||
### Callbacks
|
||||
|
||||
For California Pricing to work, the following callbacks must be enabled:
|
||||
|
||||
- `set_running_cost_callback`
|
||||
- `tariff_message_callback`
|
||||
|
||||
For the tariff information (the personal messages), the `tariff_message_callback` is used.
|
||||
|
||||
Driver specific tariffs / pricing information can be returned by the CSMS in the `AuthorizeResponse` message. In
|
||||
libocpp, the whole message is just forwared (pricing information is not extracted from it), because the pricing
|
||||
information is coupled to the authorize response. So when Tariff and Cost are enabled, the `idTokenInfo` field must be read for pricing information.
|
||||
|
||||
Cost information is also sent by the CSMS in the TransactionEventResponse. In that case, the pricing / cost information
|
||||
is extracted from the message and a RunningCost message is sent containing the current cost and extra messages
|
||||
(optional). If only Tariff is enabled and there is a personal message in the TransationEventResponse, a TariffMessage is sent.
|
||||
@@ -0,0 +1,13 @@
|
||||
# Exception Handling of Database Operations
|
||||
|
||||
OCPP versions 1.6 and 2.0.1 contain several requirements for the persistent storage and retrieval of data. This library leverages SQLite to fulfill these persistent storage requirements. The DatabaseHandler classes manage all related database operations, including CREATE, SELECT, INSERT, UPDATE, and DELETE commands.
|
||||
|
||||
## Exception Design Considerations
|
||||
|
||||
The primary design decision in the development of DatabaseHandler classes is to allow exceptions to propagate to higher level logic rather than catching them internally. By propagating exceptions of database operations, it is ensured the consumer application is fully aware of any issues that occur during database operations. This transparency allows higher-level logic to make informed decisions based on the specific errors encountered. Different higher level logic may require different strategies for error handling based on their specific requirements. Propagating exceptions gives developers the flexibility to implement custom handling procedures that best fit the application’s needs.
|
||||
|
||||
SQLite does not natively support exceptions as it is written in C. The DatabaseHandler classes check the return codes of SQLite function calls, and if an operation fails (i.e., the return code is not SQLITE_OK, SQLITE_DONE, SQLITE_ROW, etc.), an exception is thrown. This way, all database-related errors are converted into C++ exceptions, which are then propagated up to the caller.
|
||||
|
||||
## Implementation Strategy
|
||||
|
||||
The public functions of the DatabaseHandler classes are designed to throw exceptions when SQL operations fail to execute as expected Error Handling. Consumer code should implement try-catch blocks around calls to DatabaseHandler functions. This handling should be tailored to the requirements of the OCPP specification and improve the stability of the application, whether it involves the logging errors, retrying operations or other logic.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Database migrations
|
||||
|
||||
The migrations support has been moved to [everest-sqlite](https://github.com/EVerest/EVerest/tree/main/lib/everest/sqlite). Please see its [documentation for the migration support](https://github.com/EVerest/EVerest/tree/main/lib/everest/sqlite/docs/migrations.md).
|
||||
@@ -0,0 +1,72 @@
|
||||
# Getting Started
|
||||
|
||||
## Requirements
|
||||
|
||||
For Debian GNU/Linux 11 you will need the following dependencies:
|
||||
|
||||
```bash
|
||||
sudo apt install build-essential cmake python3-pip libboost-all-dev libsqlite3-dev libssl-dev
|
||||
```
|
||||
|
||||
OpenSSL version 3.0 or above is required.
|
||||
|
||||
Clone this repository.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/EVerest/libocpp
|
||||
```
|
||||
|
||||
In the libocpp folder create a folder named build and cd into it.
|
||||
Execute cmake and then make:
|
||||
|
||||
```bash
|
||||
mkdir build && cd build
|
||||
cmake ..
|
||||
make -j$(nproc)
|
||||
```
|
||||
|
||||
## Unit testing
|
||||
|
||||
GTest is required for building the test cases target.
|
||||
To build the target and run the tests you can reference the script `.ci/build-kit/install_and_test.sh`.
|
||||
The script allows the GitHub Actions runner to execute.
|
||||
|
||||
Local testing:
|
||||
|
||||
```bash
|
||||
mkdir build
|
||||
cmake -B build -DBUILD_TESTING=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX="./dist"
|
||||
cd build
|
||||
make -j$(nproc) install
|
||||
```
|
||||
|
||||
Run any required tests from build/tests.
|
||||
|
||||
## Clarifications for directory structures, namespaces and OCPP versions
|
||||
|
||||
This repository contains multiple subdirectories and namespaces named v16, v2 and v21.
|
||||
|
||||
* The v16 directories contain files for configuring and implementing OCPP 1.6.
|
||||
* The v2 directories include files for both OCPP2.0.1 and OCPP2.1, as OCPP 2.1
|
||||
is fully backward compatible with OCPP 2.0.1.
|
||||
* The v21 directories include files only for OCPP2.1.
|
||||
|
||||
## Get Started with OCPP1.6
|
||||
|
||||
Please see the [Getting Started documentation for OCPP1.6](../v16/getting_started.md).
|
||||
|
||||
## Get Started with OCPP2.0.1
|
||||
|
||||
Please see the [Getting Started documentation for OCPP2.0.1](../v2/getting_started.md).
|
||||
|
||||
## Building the doxygen documentation
|
||||
|
||||
```bash
|
||||
cmake -S . -B build
|
||||
cmake --build build --target doxygen-ocpp
|
||||
```
|
||||
|
||||
You will find the generated doxygen documentation at:
|
||||
`build/dist/docs/html/index.html`
|
||||
|
||||
The main reference for the integration of libocpp for OCPP1.6 is the ocpp::v16::ChargePoint class defined in `v16/charge_point.hpp` , for OCPP2.0.1 and OCPP2.1 that is the ocpp::v2::ChargePoint class defined in `v2/charge_point.hpp` .
|
||||
@@ -0,0 +1,244 @@
|
||||
# Notes on Smart Charging Profiles and approach
|
||||
|
||||
There are some complexities calculating composite schedules and this note explains the approach.
|
||||
|
||||
The new approach is in `profile.cpp` and `profile.hpp` and then integrated into `smart_charging.cpp` maintaining the same API.
|
||||
|
||||
```cpp
|
||||
std::vector<ChargingProfile> get_valid_profiles(
|
||||
const DateTime& start_time, const DateTime& end_time,
|
||||
int connector_id);
|
||||
|
||||
EnhancedChargingSchedule calculate_enhanced_composite_schedule(
|
||||
const std::vector<ChargingProfile>& valid_profiles,
|
||||
const DateTime& start_time,
|
||||
const DateTime& end_time,
|
||||
const int connector_id,
|
||||
std::optional<ChargingRateUnit> charging_rate_unit);
|
||||
```
|
||||
|
||||
## get_valid_profiles()
|
||||
|
||||
Retrieves all profiles that should be considered for calculating the composite schedule.
|
||||
|
||||
- checks that profiles are associated with the correct connector
|
||||
- checks that profiles match the transaction ID (when there is an active transaction)
|
||||
- start_time and end_time are not used in the new implementation
|
||||
|
||||
`start_time` and `end_time` could be used to remove profiles that would never be valid
|
||||
within that period. However only the `validFrom` and `validTo` settings should be considered.
|
||||
|
||||
`calculate_enhanced_composite_schedule` checks the `start_time` and `end_time` so it is not essential to remove profiles at this point.
|
||||
|
||||
## calculate_enhanced_composite_schedule()
|
||||
|
||||
Assumes that profiles for other connectors and transactions have been removed.
|
||||
|
||||
Processes profiles:
|
||||
|
||||
1. obtain session/transaction start time
|
||||
2. split the profiles into ChargePointMax/TxDefault/Tx sets
|
||||
3. for each set calculate the composite schedule using the preferred units (Amps/Watts)
|
||||
the resulting composite schedule covers the time period `start_time` to `end_time` only. Note that stack level is used where the higher level has priority
|
||||
4. combines the three composite schedules into the single result
|
||||
|
||||
When combining; TxDefault is the starting limit, overridden by Tx and potentially lowered by ChargePointMax. The result will never be higher than a ChargePointMax limit (where there is one).
|
||||
|
||||
## Time handling
|
||||
|
||||
The approach removes all calls for obtaining the current date and time.
|
||||
|
||||
- where there is a transaction then the start time of the transaction is obtained
|
||||
- relative schedules use the transaction start time where known or the start time
|
||||
|
||||
For generating a composite schedule relative schedules are included based on the transaction start time. Where there isn't a transaction in progress the `start_time` is used - i.e. the assumption is that a transaction has just started.
|
||||
|
||||
The removal of any relationship to the current time simplifies writing test cases and debugging test failures.
|
||||
|
||||
## Default limit
|
||||
|
||||
The OCPP 1.6 specification doesn't support gaps in charging schedules. This presents a problem while creating a composite schedule when there is a period of time when no profile is active.
|
||||
|
||||
- profile 1: stack level 10, Monday 10:00 for 2 hours
|
||||
- profile 2: stack level 11, Monday 14:00 for 2 hours
|
||||
|
||||
At Monday 08:00 requesting a composite schedule for the next 9 hours needs to indicate
|
||||
|
||||
08:00|09:00|10:00|11:00|12:00|13:00|14:00|15:00|16:00|17:00
|
||||
-----|-----|-----|-----|-----|-----|-----|-----|-----|-----
|
||||
unknown|unknown|P1|P1|unknown|unknown|P2|P2|unknown|unknown|unknown
|
||||
|
||||
Where the limit is not known then a default limit of `48.0 Amps` is used when calculating the final composite schedule.
|
||||
|
||||
A different default can be specified by installing a lower stack level TxDefault profile e.g.
|
||||
|
||||
```json
|
||||
{
|
||||
"chargingProfileId": 1,
|
||||
"chargingProfileKind": "Relative",
|
||||
"chargingProfilePurpose": "TxDefaultProfile",
|
||||
"stackLevel": 1
|
||||
"chargingSchedule": {
|
||||
"chargingRateUnit": "A",
|
||||
"chargingSchedulePeriod": [
|
||||
{
|
||||
"limit": 32.0,
|
||||
"startPeriod": 0
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## No valid profiles
|
||||
|
||||
Since a default limit is applied a composite schedule will always start at the `start_time` and have a fixed duration even when there are no valid profiles for that time period.
|
||||
|
||||
e.g. for 2024-01-01 starting at 08:00 for 10 minutes
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "Accepted",
|
||||
"scheduleStart": "2024-01-01T08:00:00Z",
|
||||
"chargingSchedule": {
|
||||
"duration:": 3600,
|
||||
"startSchedule": "2024-01-01T08:00:00Z",
|
||||
"chargingRateUnit": "A",
|
||||
"chargingSchedulePeriod": {
|
||||
"startPeriod": 0,
|
||||
"limit": 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When building the OCPP response "startSchedule" could be excluded however the composite schedule refers to a specific point in time, so it should be provided.
|
||||
|
||||
According to the OCPP specification section `7.13 ChargingSchedule` chargingSchedulePeriod is a required field and must have at least one entry. Hence the following is not a valid response:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "Accepted",
|
||||
"scheduleStart": "2024-01-01T08:00:00Z",
|
||||
"chargingSchedule": {
|
||||
"duration:": 3600,
|
||||
"startSchedule": "2024-01-01T08:00:00Z",
|
||||
"chargingRateUnit": "A",
|
||||
"chargingSchedulePeriod": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Profile validity
|
||||
|
||||
The following items need to be considered when looking at a schedule:
|
||||
|
||||
- validFrom
|
||||
- validTo
|
||||
- transaction start time
|
||||
- startSchedule
|
||||
- duration
|
||||
- startPeriod
|
||||
|
||||
The following sections explore some interesting edge cases.
|
||||
|
||||
### validFrom & validTo and transaction start time
|
||||
|
||||
For the following schedule
|
||||
|
||||
```json
|
||||
{
|
||||
"chargingProfileId": 1,
|
||||
"chargingProfileKind": "Relative",
|
||||
"chargingProfilePurpose": "TxDefaultProfile",
|
||||
"stackLevel": 1,
|
||||
"chargingSchedule": {
|
||||
"chargingRateUnit": "A",
|
||||
"chargingSchedulePeriod": [
|
||||
{
|
||||
"limit": 32.0,
|
||||
"startPeriod": 0
|
||||
},
|
||||
{
|
||||
"limit": 6.0,
|
||||
"startPeriod": 3600
|
||||
}
|
||||
],
|
||||
},
|
||||
"validFrom": "2024-01-01T12:00:00Z",
|
||||
"validTo": "2024-01-01T20:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
The PEV plugs in at "2024-01-01T10:00:00Z" the result is no charging until the profile is valid at 12:00 and then the limit is 6.0A since only the first hour of a transaction is at the higher limit of 32.0A
|
||||
|
||||
The PEV plugs in at "2024-01-01T19:50:00Z" the result is charging for 10 minutes at 32. A and then no charge offered.
|
||||
|
||||
### startSchedule and daily recurring
|
||||
|
||||
For the following schedule
|
||||
|
||||
```json
|
||||
{
|
||||
"chargingProfileId": 1,
|
||||
"chargingProfileKind": "Recurring",
|
||||
"chargingProfilePurpose": "TxDefaultProfile",
|
||||
"recurrencyKind": "Daily",
|
||||
"stackLevel": 1,
|
||||
"chargingSchedule": {
|
||||
"startSchedule": "2024-01-01T12:00:00Z",
|
||||
"chargingRateUnit": "A",
|
||||
"chargingSchedulePeriod": [
|
||||
{
|
||||
"limit": 32.0,
|
||||
"startPeriod": 0
|
||||
},
|
||||
{
|
||||
"limit": 6.0,
|
||||
"startPeriod": 3600
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The PEV plugs in at "2024-01-10T11:50:00Z" the result is charging at 6.0A for 10 minutes (based on the previous day) and then at 32.0A from 12:00 when the schedule repeats.
|
||||
|
||||
`validFrom` and `validTo` add additional complications.
|
||||
|
||||
### startSchedule and daily recurring with duration
|
||||
|
||||
For the following schedule
|
||||
|
||||
```json
|
||||
{
|
||||
"chargingProfileId": 1,
|
||||
"chargingProfileKind": "Recurring",
|
||||
"chargingProfilePurpose": "TxDefaultProfile",
|
||||
"recurrencyKind": "Daily",
|
||||
"stackLevel": 1,
|
||||
"chargingSchedule": {
|
||||
"startSchedule": "2024-01-01T12:00:00Z",
|
||||
"duration": 18000,
|
||||
"chargingRateUnit": "A",
|
||||
"chargingSchedulePeriod": [
|
||||
{
|
||||
"limit": 32.0,
|
||||
"startPeriod": 0
|
||||
},
|
||||
{
|
||||
"limit": 6.0,
|
||||
"startPeriod": 3600
|
||||
}
|
||||
],
|
||||
},
|
||||
"validFrom": "2024-02-01T12:00:00Z",
|
||||
"validTo": "2024-03-01T09:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
The PEV plugs in at "2024-02-10T11:50:00Z" the result is based on the default limit since the profile is only valid for 5 hours. i.e. no charging for 10 minutes (based on the previous day) and then at 32.0A from 12:00 when the schedule repeats.
|
||||
|
||||
The PEV plugs in at "2024-02-01T11:50:00Z" the result is based on the default limit since the profile isn't valid yet. i.e. no charging for 10 minutes and then at 32.0A from 12:00 when the schedule starts.
|
||||
|
||||
The PEV plugs in at "2024-03-01T08:00:00Z" the result is charging at 6.0A (based on the previous day) and then no charging at 09:00 when the profile is no longer valid.
|
||||
@@ -0,0 +1,60 @@
|
||||
# Message Dispatching Class Diagram
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class MessageDispatcherInterface {
|
||||
+dispatch_call(const json& call, bool triggered = false)
|
||||
+dispatch_call_async(const json& call, bool triggered = false): std::future~EnhancedMessage~T~~
|
||||
+dispatch_call_result(const json& call_result)
|
||||
+dispatch_call_error(const json& call_error)
|
||||
}
|
||||
|
||||
class v16_MessageDispatcher {
|
||||
- MessageQueue& message_queue
|
||||
- ChargePointConfiguration& configuration
|
||||
- RegistrationStatus& registration_status
|
||||
}
|
||||
|
||||
class v2_MessageDispatcher {
|
||||
- MessageQueue& message_queue
|
||||
- DeviceModelAbstract& device_model
|
||||
- ConnectivityManager& connectivity_manager
|
||||
- RegistrationStatusEnum& registration_status
|
||||
}
|
||||
|
||||
class v2_MessageHandlerInterface {
|
||||
+handle_message(EnhancedMessage~v2_MessageType~ message)
|
||||
}
|
||||
|
||||
class v16_MessageHandlerInterface {
|
||||
+handle_message(EnhancedMessage~v16_MessageType~ message)
|
||||
}
|
||||
|
||||
class v2_DataTransferInterface {
|
||||
+data_transfer_req(request: DataTransferRequest): std::optional~DataTransferResponse~
|
||||
+handle_data_transfer_req(call: Call~DataTransferRequest~)
|
||||
}
|
||||
|
||||
class v2_DataTransfer {
|
||||
-MessageDispatcherInterface &message_dispatcher
|
||||
-std::optional~function~ data_transfer_callback
|
||||
}
|
||||
|
||||
class v2_ChargePoint {
|
||||
std::unique_ptr~MessageDispatcherInterface~ message_dispatcher
|
||||
std::unique_ptr~v2_DataTransferInterface~ data_transfer
|
||||
}
|
||||
|
||||
class v16_ChargePoint {
|
||||
std::unique_ptr~MessageDispatcherInterface~ message_dispatcher
|
||||
}
|
||||
|
||||
MessageDispatcherInterface <|-- v16_MessageDispatcher
|
||||
MessageDispatcherInterface <|-- v2_MessageDispatcher
|
||||
v2_DataTransferInterface <|-- v2_DataTransfer
|
||||
v2_MessageHandlerInterface <|-- v2_DataTransferInterface
|
||||
MessageDispatcherInterface *-- v2_DataTransfer
|
||||
MessageDispatcherInterface *-- v2_ChargePoint
|
||||
v2_DataTransferInterface *-- v2_ChargePoint
|
||||
MessageDispatcherInterface *-- v16_ChargePoint
|
||||
```
|
||||
@@ -0,0 +1,313 @@
|
||||
# Network connection profile interface
|
||||
|
||||
libocpp automatically tries to connect using the given network connection profiles.
|
||||
However, if you want more control, you can use the callbacks provided for the network connection.
|
||||
|
||||
libocpp will automatically connect to the network profile with the highest priority.
|
||||
If this fails, it will network profile with the second highest priority, and so on.
|
||||
|
||||
## Set up interface (optional)
|
||||
|
||||
A callback can be implemented to set up the interface. For example, if the interface is a modem, it must first be
|
||||
be activated before it is possible to connect to this interface. To do this, you can implement the callback
|
||||
`std::future<ConfigNetworkResult>(configure_network_connection_profile_callback(configuration_slot, NetworkConnectionProfile))`
|
||||
|
||||
In the implementation of this callback, you have to create a promise and return the future to the promise:
|
||||
```cpp
|
||||
std::promise<ocpp::ConfigNetworkResult> promise();
|
||||
std::future<ocpp::ConfigNetworkResult> future = promise.get_future();
|
||||
return future;
|
||||
```
|
||||
|
||||
If the network was setup successfully, you can set the values in the promise with
|
||||
```cpp
|
||||
promise.set_value(configNetworkResult);
|
||||
```
|
||||
This way, libocpp knows that it can connect to the given interface and will try to do so.
|
||||
A timeout can be configured using `NetworkConfigTimeout' to wait longer or shorter than the default 60 seconds.
|
||||
|
||||
### Bind to a specific interface
|
||||
|
||||
In some cases there are multiple network interfaces available and you may want to connect to a specific one.
|
||||
In `ConfigNetworkResult` you can specify which interface you want the websocket to bind to.
|
||||
Sometimes an interface has more than one IP address (in the case of a local/auto link for example).
|
||||
In this case you want the websocket to bind to a specific IP address. The `interface_address` in ConfigNetworkResult supports both.
|
||||
It will bind to the given network interface (a string containing the name of the interface) or the given ip address (a string containing the ip address in human readable format).
|
||||
|
||||
## Connect to higher network connection profile priority (optional)
|
||||
|
||||
Normally, when libocpp is connected with a network connection profile, it will not disconnect.
|
||||
However, there may be a situation where libocpp is connected to a profile with priority 2 or lower, and you find out at system level that an interface (with a higher priority) has changed and is now up.
|
||||
A call is added so that you can suggest that libocpp switch to this profile: `bool on_try_switch_network_connection_profile(const std::int32_t configuration_slot)`.
|
||||
libocpp will inform the caller by the return value if it tries to switch to this profile.
|
||||
|
||||
## Disconnected / connected callbacks
|
||||
|
||||
libocpp provides two callbacks for when the websocket is connected and disconnected. It will provide the network slot
|
||||
in these callbacks, so you can keep the network connection in use (e.g. not disable the modem), or disable the network connection (example again: disable the modem).
|
||||
|
||||
## External ConnectivityManager (optional)
|
||||
|
||||
The `ConnectivityManager` lives in the `ocpp` namespace and is not tied to a specific OCPP version. This
|
||||
design allows the websocket connection to be established **before** a protocol version is determined. During the
|
||||
websocket handshake the CSMS selects the OCPP protocol version (e.g. OCPP 1.6, OCPP2.0.1 or OCPP2.1). An integrating
|
||||
application can use this information to instantiate the appropriate version-specific `ChargePoint` and inject the
|
||||
already-connected `ConnectivityManager` into it.
|
||||
|
||||
By default, when no external `ConnectivityManager` is provided, libocpp creates and manages one internally.
|
||||
However, you can provide your own instance of `ConnectivityManagerInterface` (or the concrete
|
||||
`ConnectivityManager`) to take full control over websocket connectivity.
|
||||
|
||||
> **Note:** External `ConnectivityManager` injection is currently only supported for `ocpp::v2::ChargePoint`.
|
||||
> Support for `ocpp::v16::ChargePoint` is not yet implemented and will follow in a future release.
|
||||
|
||||
### Typical flow with an external ConnectivityManager
|
||||
|
||||
1. Create a `ConnectivityManager`.
|
||||
2. Create **all** version-specific `ChargePoint` instances (e.g. both `ocpp::v16::ChargePoint` and
|
||||
`ocpp::v2::ChargePoint`), passing the shared `ConnectivityManager` to each.
|
||||
3. Register a `websocket_connected_callback` on the `ConnectivityManager` that inspects the negotiated
|
||||
`OcppProtocolVersion` and calls `start()` on the matching `ChargePoint`. `start()` automatically sets
|
||||
the message callback in the `ConnectivityManager`, so messages are routed to the correct version.
|
||||
4. On subsequent reconnects (where `start()` was already called for that version), forward the event via
|
||||
`on_websocket_connected()` instead.
|
||||
5. Because the CSMS may select a **different** protocol version on every websocket handshake, the integrating
|
||||
application must be prepared to switch between `ChargePoint` instances at any reconnect. When a version
|
||||
switch occurs, `stop()` should be called on the previously active `ChargePoint` and `start()` on the
|
||||
newly selected one.
|
||||
|
||||
### Creating and injecting a ConnectivityManager
|
||||
|
||||
```cpp
|
||||
// 1. Create a version-independent ConnectivityManager
|
||||
auto connectivity_manager = std::make_shared<ocpp::ConnectivityManager>(device_model, evse_security);
|
||||
|
||||
// 2. Create all ChargePoint instances, each receiving the shared ConnectivityManager
|
||||
auto charge_point_v16 = std::make_unique<ocpp::v16::ChargePoint>(
|
||||
/* ... */, connectivity_manager, /* ... */);
|
||||
|
||||
auto charge_point_v2 = std::make_unique<ocpp::v2::ChargePoint>(
|
||||
evse_connector_structure, device_model, database_handler, evse_security,
|
||||
connectivity_manager, message_log_path, callbacks);
|
||||
```
|
||||
|
||||
### Wiring up websocket event callbacks
|
||||
|
||||
The websocket connected callback must handle both the initial `start()` call and subsequent reconnects.
|
||||
It also needs to handle potential version switches:
|
||||
|
||||
```cpp
|
||||
// Track which ChargePoint is currently active and started
|
||||
ocpp::OcppProtocolVersion active_version = ocpp::OcppProtocolVersion::Unknown;
|
||||
|
||||
connectivity_manager->set_websocket_connected_callback(
|
||||
[&](int configuration_slot,
|
||||
const ocpp::v2::NetworkConnectionProfile& profile,
|
||||
const ocpp::OcppProtocolVersion version) {
|
||||
|
||||
if (version != active_version) {
|
||||
// Version changed — stop the previously active ChargePoint (if any)
|
||||
if (active_version == ocpp::OcppProtocolVersion::v16) {
|
||||
charge_point_v16->stop();
|
||||
} else if (active_version != ocpp::OcppProtocolVersion::Unknown) {
|
||||
charge_point_v2->stop();
|
||||
}
|
||||
|
||||
// Start the ChargePoint for the newly negotiated version.
|
||||
// start() sets the message callback in the ConnectivityManager
|
||||
// so that incoming messages are routed to the correct ChargePoint.
|
||||
if (version == ocpp::OcppProtocolVersion::v16) {
|
||||
charge_point_v16->start();
|
||||
} else {
|
||||
charge_point_v2->start();
|
||||
}
|
||||
active_version = version;
|
||||
} else {
|
||||
// Same version as before — just notify the active ChargePoint
|
||||
if (version == ocpp::OcppProtocolVersion::v16) {
|
||||
charge_point_v16->on_websocket_connected(configuration_slot, profile, version);
|
||||
} else {
|
||||
charge_point_v2->on_websocket_connected(configuration_slot, profile, version);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
connectivity_manager->set_websocket_disconnected_callback(
|
||||
[&](int configuration_slot,
|
||||
const ocpp::v2::NetworkConnectionProfile& profile,
|
||||
auto /*version*/) {
|
||||
if (active_version == ocpp::OcppProtocolVersion::v16) {
|
||||
charge_point_v16->on_websocket_disconnected(configuration_slot, profile);
|
||||
} else {
|
||||
charge_point_v2->on_websocket_disconnected(configuration_slot, profile);
|
||||
}
|
||||
});
|
||||
|
||||
connectivity_manager->set_websocket_connection_failed_callback(
|
||||
[&](ocpp::ConnectionFailedReason reason) {
|
||||
if (active_version == ocpp::OcppProtocolVersion::v16) {
|
||||
charge_point_v16->on_websocket_connection_failed(reason);
|
||||
} else {
|
||||
charge_point_v2->on_websocket_connection_failed(reason);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Optionally, if your system requires interface setup (e.g. modem activation), set the network configuration
|
||||
callback:
|
||||
|
||||
```cpp
|
||||
connectivity_manager->set_configure_network_connection_profile_callback(
|
||||
[](const int32_t configuration_slot,
|
||||
const ocpp::v2::NetworkConnectionProfile& profile) {
|
||||
std::promise<ocpp::ConfigNetworkResult> promise;
|
||||
// ... set up the network interface ...
|
||||
ocpp::ConfigNetworkResult result;
|
||||
result.success = true;
|
||||
result.interface_address = "eth0"; // optional: bind to specific interface or IP
|
||||
promise.set_value(result);
|
||||
return promise.get_future();
|
||||
});
|
||||
```
|
||||
|
||||
**Note:** `start()` automatically calls `set_message_callback` on the `ConnectivityManager`, ensuring that
|
||||
incoming OCPP messages are routed to the correct `ChargePoint` instance. The `set_logging` method is called
|
||||
automatically by `ChargePoint::initialize()`. You do not need to call either of these yourself.
|
||||
|
||||
### When to use an external ConnectivityManager
|
||||
|
||||
- When a single application needs to support multiple OCPP versions and switch between them at runtime
|
||||
based on the CSMS's protocol selection
|
||||
- When the OCPP protocol version is not known ahead of time and should be determined by the websocket
|
||||
handshake before starting a version-specific `ChargePoint`
|
||||
- When you want to implement a custom connectivity strategy (e.g. custom reconnection logic by implementing
|
||||
`ConnectivityManagerInterface`)
|
||||
|
||||
### Version switching responsibility
|
||||
|
||||
When multi-version support is implemented (see note above), the **application** will be responsible for
|
||||
implementing the version-switching logic (i.e. deciding which `ChargePoint` to `start()`/`stop()` based on
|
||||
the negotiated protocol version). libocpp does not currently provide a built-in orchestrator for this, because
|
||||
the v16 and v2 `ChargePoint` implementations do not share a common interface and applications may need to manage
|
||||
additional version-specific state during a switch (e.g. active transactions, UI state). Such an orchestrator
|
||||
may be provided in the future.
|
||||
|
||||
### Default (internal) behavior
|
||||
|
||||
If you do **not** provide a `ConnectivityManager`, libocpp creates one internally and automatically registers the
|
||||
websocket event callbacks. In this case, the `on_websocket_connected`, `on_websocket_disconnected`, and
|
||||
`on_websocket_connection_failed` methods on `ChargePoint` are called automatically and you do not need to call them.
|
||||
This is sufficient when the OCPP protocol version is known at compile time or configuration time.
|
||||
|
||||
## Sequence diagram
|
||||
|
||||
'core' can be read as any application that implements libocpp
|
||||
|
||||
For step 9, ping is one way to check if a CSMS is up, but you of course can implement a way to check this yourself.
|
||||
|
||||
### Internal ConnectivityManager (default)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant csms
|
||||
participant libocpp
|
||||
participant core
|
||||
autonumber
|
||||
|
||||
note over csms,libocpp: libocpp wants to connect to network connection profile
|
||||
|
||||
libocpp ->>+ core: std::future<ConfigNetworkResult>(configure_network_connection_profile_callback(<br>configuration_slot, NetworkConnectionProfile))
|
||||
note over core: ... possible delay ...
|
||||
core ->> core: Setup network, e.g. setup modem
|
||||
core ->>- libocpp: promise.set_value(status,<br>ip_address, etc)
|
||||
|
||||
alt within timeout
|
||||
|
||||
%% core ->> libocpp: on_network_update (ip address)
|
||||
libocpp ->> csms: connect websocket (ip address)
|
||||
csms ->> libocpp: ACK
|
||||
libocpp ->> core: websocket_connected_callback(configuration_slot, NetworkConnectionProfile)
|
||||
core ->> core: disable unneeded interfaces, <br>e.g. disable modem
|
||||
else timeout reached, next network connection profile selected
|
||||
libocpp -->> core: std::future<ConfigNetworkResult>(configure_network_connection_profile_callback(<br>configuration_slot, NetworkConnectionProfile)) (see 1)
|
||||
end
|
||||
|
||||
|
||||
note over libocpp: CSMS is connected via connection profile prio 2 (for example modem) but prio 1 (for example eth0) comes up
|
||||
|
||||
loop until prio 1 csms is found
|
||||
core ->> csms: ping
|
||||
end
|
||||
|
||||
core ->> libocpp: on_try_switch_networkconnectionprofile(configuration_slot)
|
||||
libocpp -->> core: std::future<ConfigNetworkResult>(configure_network_connection_profile_callback(<br>configuration_slot, NetworkConnectionProfile)) (see 1)
|
||||
|
||||
|
||||
note over csms,libocpp: Network is disconnected (for example networkcable removed)
|
||||
|
||||
core ->> libocpp: disconnect csms (on_network_disconnected(configuration_slot, OCPPInterfaceEnum)
|
||||
libocpp ->> core: websocket_disconnected_callback(configuration_slot, NetworkConnectionProfile)
|
||||
libocpp -->> core: std::future<ConfigNetworkResult>(configure_network_connection_profile_callback(<br>configuration_slot, NetworkConnectionProfile)) (see 1)
|
||||
|
||||
|
||||
note over csms,libocpp: Network is disconnected (websocket timeout)
|
||||
|
||||
libocpp ->> libocpp: disconnect csms
|
||||
libocpp ->> core: websocket_disconnected_callback(configuration_slot, NetworkConnectionProfile)
|
||||
libocpp -->> core: std::future<ConfigNetworkResult>(configure_network_connection_profile_callback(<br>configuration_slot, NetworkConnectionProfile)) (see 1)
|
||||
```
|
||||
|
||||
### External ConnectivityManager
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant csms
|
||||
participant core
|
||||
participant connectivity_manager as ConnectivityManager<br>(external)
|
||||
participant cp_v16 as ChargePoint v16
|
||||
participant cp_v2 as ChargePoint v2
|
||||
autonumber
|
||||
|
||||
note over core: 1. Initialization — create all components upfront
|
||||
core ->> connectivity_manager: create ConnectivityManager(device_model, evse_security)
|
||||
core ->> cp_v16: create ChargePoint(..., connectivity_manager, ...)
|
||||
core ->> cp_v2: create ChargePoint(..., connectivity_manager, ...)
|
||||
core ->> connectivity_manager: set_websocket_connected_callback(version_dispatch)
|
||||
core ->> connectivity_manager: set_websocket_disconnected_callback(version_dispatch)
|
||||
core ->> connectivity_manager: set_websocket_connection_failed_callback(version_dispatch)
|
||||
|
||||
note over core: 2. First connection — version unknown
|
||||
core ->> connectivity_manager: connect()
|
||||
connectivity_manager ->> csms: websocket handshake (offering supported OCPP versions)
|
||||
csms ->> connectivity_manager: ACK (selected OCPP version, e.g. v2)
|
||||
|
||||
note over core: 3. Connected callback triggers start() for negotiated version
|
||||
connectivity_manager ->> core: websocket_connected_callback(slot, profile, v2)
|
||||
core ->> core: active_version = Unknown → v2 (first time or version changed)
|
||||
core ->> cp_v2: start()
|
||||
note over cp_v2: start() sets message_callback<br>in ConnectivityManager
|
||||
|
||||
note over core: 4. Reconnect with same version
|
||||
connectivity_manager ->> csms: websocket handshake
|
||||
csms ->> connectivity_manager: ACK (v2 again)
|
||||
connectivity_manager ->> core: websocket_connected_callback(slot, profile, v2)
|
||||
core ->> core: active_version == v2, no change
|
||||
core ->> cp_v2: on_websocket_connected(slot, profile, v2)
|
||||
|
||||
note over core: 5. Reconnect with different version (CSMS switches to v16)
|
||||
connectivity_manager ->> csms: websocket handshake
|
||||
csms ->> connectivity_manager: ACK (v16)
|
||||
connectivity_manager ->> core: websocket_connected_callback(slot, profile, v16)
|
||||
core ->> core: active_version v2 → v16 (version changed)
|
||||
core ->> cp_v2: stop()
|
||||
core ->> cp_v16: start()
|
||||
note over cp_v16: start() sets message_callback<br>in ConnectivityManager
|
||||
|
||||
note over core: 6. Disconnection — forwarded to active ChargePoint
|
||||
connectivity_manager ->> core: websocket_disconnected_callback(slot, profile)
|
||||
core ->> cp_v16: on_websocket_disconnected(slot, profile)
|
||||
|
||||
note over core: 7. Network disconnected externally
|
||||
core ->> cp_v16: on_network_disconnected(ocpp_interface)
|
||||
cp_v16 ->> connectivity_manager: on_network_disconnected(ocpp_interface)
|
||||
```
|
||||
298
tools/EVerest-main/lib/everest/ocpp/doc/v16/getting_started.md
Normal file
298
tools/EVerest-main/lib/everest/ocpp/doc/v16/getting_started.md
Normal file
@@ -0,0 +1,298 @@
|
||||
# Getting Started with OCPP1.6
|
||||
|
||||
## Integrate this library with your Charging Station Implementation for OCPP1.6
|
||||
|
||||
OCPP is a protocol that affects, controls and monitors many areas of a charging station's operation.
|
||||
|
||||
If you want to integrate this library with your charging station implementation, you have to register a couple of **callbacks** and integrate **event handlers**. This is necessary for the library to interact with your charging station according to the requirements of OCPP.
|
||||
|
||||
Libocpp needs registered **callbacks** in order to execute control commands defined within OCPP (e.g Reset.req or RemoteStartTransaction.req)
|
||||
|
||||
The implementation must call **event handlers** of libocpp so that the library can track the state of the charging station and trigger OCPP messages accordingly (e.g. MeterValues.req , StatusNotification.req)
|
||||
|
||||
Your reference within libocpp to interact is a single instance to the class ocpp::v16::ChargePoint ([ChargePoint](include/ocpp/v16/charge_point.hpp)) defined in `ocpp/v16/charge_point.hpp` for OCPP 1.6.
|
||||
|
||||
### Overview of the required callbacks and events and what libocpp expects to happen
|
||||
|
||||
The following section will give you a high level overview of how to integrate libocpp with your application. Please use the [Doxygen Documentation](#building-the-doxygen-documentation) as an additional source for the ChargePoint API.
|
||||
|
||||
In EVerest the OCPP module leverages several other modules to perform tasks that relate to authorization, reservations, charging session handling and system tasks like rebooting or firmware updates.
|
||||
|
||||
- Auth orchestrates authorization, utilizing different token providers like RFID reads and token validators. Libocpp mainly acts as a token validator, but in the case of RemoteStartTransactions it acts as a token provider as well
|
||||
- EvseManager manages the charging session and charging state machine by communicating with a "board support package", a driver for the charging hardware that abstracts away the control pilot, relay control, power meters, etc. The EvseManager also handles reservations.
|
||||
- System handles firmware updates, log uploads and resets
|
||||
|
||||
The following sections explain the steps you can follow to implement their functionality on your own and integrate the libocpp directly into your charging station software without relying on EVerest. However, in most cases it's much easier to write an EVerest driver using the *EVerest/interfaces/board_support_AC.yaml* interface.
|
||||
|
||||
#### ChargePoint() constructor
|
||||
|
||||
The main entrypoint for libocpp for OCPP1.6 is the ocpp::v16::ChargePoint constructor.
|
||||
This is defined in `v16/charge_point.hpp` and takes the following parameters:
|
||||
|
||||
- config: a std::string that contains the libocpp 1.6 config. There are example configs that work with a [SteVe](https://github.com/steve-community/steve) installation for example `config/v16/config-docker.json`.
|
||||
|
||||
- share_path: a std::filesystem path containing the path to the OCPP modules folder, for example pointing to */usr/share/everest/modules/OCPP*. This path contains the following files and directories and is installed by the libocpp install target:
|
||||
|
||||
```bash
|
||||
.
|
||||
├── config-docker.json
|
||||
├── config-docker-tls.json
|
||||
├── config.json
|
||||
├── init.sql
|
||||
├── logging.ini
|
||||
└── profile_schemas
|
||||
├── Config.json
|
||||
├── Core.json
|
||||
├── FirmwareManagement.json
|
||||
├── Internal.json
|
||||
├── LocalAuthListManagement.json
|
||||
├── PnC.json
|
||||
├── Reservation.json
|
||||
├── Security.json
|
||||
├── SmartCharging.json
|
||||
└── Custom.json
|
||||
```
|
||||
|
||||
Here you can find:
|
||||
- the aforementioned config files
|
||||
|
||||
- a *logging.ini* that is needed to initialize logging with Everest::Logging::init(path_to_logging_ini, "name_of_binary")
|
||||
|
||||
- a *init.sql* file which contains the database schema used by libocpp for its sqlite database
|
||||
|
||||
- and a *profile_schemas* directory. This contains json schema files that are used to validate the libocpp config. The schemas are split up according to the OCPP1.6 feature profiles like Core, FirmwareManagement and so on. Additionally there is a schema for "Internal" configuration options (for example the ChargePointId, or CentralSystemURI). A "PnC" schema for the ISO 15118 Plug & Charge with OCPP 1.6 Application note, a "Security" schema for the OCPP 1.6 Security Whitepaper (3rd edition) and an exemplary "Custom" schema are provided as well. The Custom.json could be modified to be able to add custom configuration keys. Finally there's a Config.json schema that ties everything together
|
||||
|
||||
- user_config_path: this points to a "user config", which we call a configuration file that's merged with the config that's provided in the "config" parameter. Here you can add, remove and overwrite settings without modifying the config passed in the first parameter directly. This is also used by libocpp to persistently modify config entries that are changed by the CSMS that should persist across restarts.
|
||||
|
||||
- database_path: this points to the location of the sqlite database that libocpp uses to keep track of connector availability, the authorization cache and auth list, charging profiles and transaction data
|
||||
|
||||
- sql_init_path: this points to the aforementioned init.sql file which contains the database schema used by libocpp for its sqlite database
|
||||
|
||||
- message_log_path: this points to the directory in which libocpp can put OCPP communication logfiles for debugging purposes. This behavior can be controlled by the "LogMessages" (set to true by default) and "LogMessagesFormat" (set to ["log", "html", "session_logging"] by default, "console" and "console_detailed" are also available) configuration keys in the "Internal" section of the config file. Please note that this is intended for debugging purposes only as it logs all communication, including authentication messages.
|
||||
|
||||
- evse_security: this is a pointer to an implementation of the `common/evse_security.hpp` interface. This allows you to include your custom implementation of the security related operations according to this interface. If you set this value to nullptr, the internal implementation of the security related operations of libocpp will be used. In this case you need to specify the parameter security_configuration
|
||||
|
||||
- security_configuration: this parameter should only be set in case the evse_security parameter is nullptr. It specifies the file paths that are required to set up the internal evse_security implementation. Note that you need to specify bundle files for the CA certificates and directories for the certificates and keys
|
||||
|
||||
The directory layout expected is as follows
|
||||
|
||||
```bash
|
||||
.
|
||||
├── ca
|
||||
│ ├── csms
|
||||
│ │ └── CSMS_ROOT_CA.pem
|
||||
│ ├── cso
|
||||
│ │ ├── CPO_CERT_CHAIN.pem
|
||||
│ │ ├── CPO_SUB_CA1_LEAF.der
|
||||
│ │ ├── CPO_SUB_CA1.pem
|
||||
│ │ ├── CPO_SUB_CA2_LEAF.der
|
||||
│ │ └── CPO_SUB_CA2.pem
|
||||
│ ├── mf
|
||||
│ │ └── MF_ROOT_CA.pem
|
||||
│ ├── mo
|
||||
│ │ ├── INTERMEDIATE_MO_CA_CERTS.pem
|
||||
│ │ ├── MO_ROOT_CA.der
|
||||
│ │ ├── MO_ROOT_CA.pem
|
||||
│ │ ├── MO_SUB_CA1.der
|
||||
│ │ ├── MO_SUB_CA1.pem
|
||||
│ │ ├── MO_SUB_CA2.der
|
||||
│ │ └── MO_SUB_CA2.pem
|
||||
│ └── v2g
|
||||
│ ├── V2G_ROOT_CA.der
|
||||
│ └── V2G_ROOT_CA.pem
|
||||
├── client
|
||||
│ ├── csms
|
||||
│ │ ├── CPO_CERT_CHAIN.pem
|
||||
│ │ ├── CPO_SUB_CA1.key
|
||||
│ │ ├── CPO_SUB_CA2.key
|
||||
│ │ ├── SECC_LEAF.der
|
||||
│ │ ├── SECC_LEAF.key
|
||||
│ │ └── SECC_LEAF.pem
|
||||
│ ├── cso
|
||||
│ │ ├── CPO_CERT_CHAIN.pem
|
||||
│ │ ├── CPO_SUB_CA1.key
|
||||
│ │ ├── CPO_SUB_CA2.key
|
||||
│ │ ├── SECC_LEAF.der
|
||||
│ │ ├── SECC_LEAF.key
|
||||
│ │ └── SECC_LEAF.pem
|
||||
│ └── v2g
|
||||
│ └── V2G_ROOT_CA.key
|
||||
```
|
||||
|
||||
#### registering callbacks
|
||||
|
||||
You can (and in many cases MUST) register a number of callbacks so libocpp can interact with the charger. In EVerest most of this functionality is orchestrated by the "EvseManager" module, but you can also register your own callbacks interacting directly with your chargers software. Following is a list of callbacks that you must register and a few words about their purpose.
|
||||
|
||||
TODO: in a future version of libocpp the callbacks will be organised in a struct with optional members emphasizing the required and optional callbacks.
|
||||
|
||||
Some general notes: the "connector" parameter of some of the callbacks refers to the connector number as understood in the OCPP 1.6 specification, "0" means the whole charging station, the connectors with EVSEs used for charging cars start at "1".
|
||||
|
||||
- register_pause_charging_callback
|
||||
|
||||
this callback is used by libocpp to request pausing of charging, the "connector" parameter tells you which connector/EVSE has to pause charging
|
||||
|
||||
- register_resume_charging_callback
|
||||
|
||||
this callback is used by libocpp the request resuming of charging, the "connector" parameter tells you which connector/EVSE can resume charging
|
||||
|
||||
- register_stop_transaction_callback
|
||||
|
||||
in EVerest this calls the EvseManagers stop_transaction command which "Stops transactions and cancels charging externally, charging can only be resumed by replugging car. EVSE will also stop transaction automatically e.g. on disconnect, so this only needs to be called if the transaction should end before."
|
||||
this will then signal the following events:
|
||||
- ChargingFinished
|
||||
- TransactionFinished
|
||||
|
||||
- register_unlock_connector_callback
|
||||
|
||||
can be used by libocpp to force unlock a connector
|
||||
|
||||
- register_reserve_now_callback
|
||||
|
||||
libocpp can use this to reserve a connector, reservation handling is outsourced to a reservation manager in EVerest that implements the reservation interface (EVerest/interfaces/reservation.yaml)
|
||||
|
||||
- register_upload_diagnostics_callback
|
||||
|
||||
uses a function (in EVerest provided by the System module) to upload the requested diagnostics file
|
||||
|
||||
- register_upload_logs_callback
|
||||
|
||||
uses a function (in EVerest provided by the System module) to upload the requested log file
|
||||
|
||||
- register_update_firmware_callback
|
||||
|
||||
uses a function (in EVerest provided by the System module) to perform a firmware update
|
||||
|
||||
- register_signed_update_firmware_callback
|
||||
|
||||
uses a function (in EVerest provided by the System module) to perform a signed firmware update
|
||||
|
||||
- register_provide_token_callback
|
||||
|
||||
this callback is used in a remote start transaction to provide a token (prevalidated or not) to the authorization system
|
||||
|
||||
- register_set_connection_timeout_callback
|
||||
|
||||
used by libocpp to set the authorization or plug in connection timeout in the authorization system based on the "ConnectionTimeout" configuration key
|
||||
|
||||
- register_disable_evse_callback
|
||||
|
||||
used to disable the EVSE (ChangeAvailability.req)
|
||||
|
||||
- register_enable_evse_callback
|
||||
|
||||
used to enable the EVSE (ChangeAvailability.req)
|
||||
|
||||
- register_cancel_reservation_callback
|
||||
|
||||
used to cancel a reservation in the reservation manager (CancelReservation.req)
|
||||
|
||||
- register_signal_set_charging_profiles_callback
|
||||
|
||||
used to signal that new charging schedule(s) have been set, you can then use
|
||||
get_all_composite_charging_schedules(duration_s) to get the new valid charging schedules
|
||||
|
||||
- register_is_reset_allowed_callback
|
||||
|
||||
used to inquire (in EVerest from the System module) if a reset is allowed
|
||||
|
||||
- register_reset_callback
|
||||
|
||||
used to perform a reset of the requested type, it is up to the user to properly stop the charge point when this callback is used
|
||||
|
||||
- register_connection_state_changed_callback
|
||||
|
||||
used to inform about the connection state to the CSMS (connected = true, disconnected = false)
|
||||
|
||||
- register_configuration_key_changed_callback
|
||||
used to react on a changed configuration key. This callback is called when the specified configuration key has been changed by the CSMS
|
||||
|
||||
#### Functions that need to be triggered from the outside after new information is availble (on_... functions in the charge point API)
|
||||
|
||||
- on_log_status_notification(std::int32_t request_id, std::string log_status)
|
||||
|
||||
can be used to notify libocpp of a log status notification
|
||||
|
||||
- on_firmware_update_status_notification(std::int32_t request_id, std::string firmware_update_status)
|
||||
|
||||
can be used to notify libocpp of a firmware update status notification
|
||||
|
||||
- on_meter_values(std::int32_t connector, const Powermeter& powermeter)
|
||||
|
||||
provides a Powermeter struct to libocpp (for sending meter values during charging sessions or periodically)
|
||||
|
||||
- on_max_current_offered(std::int32_t connector, std::int32_t max_current)
|
||||
|
||||
the maximum current offered to the EV on this connector (in ampere)
|
||||
|
||||
#### The following functions are triggered depending on different so called "Session Events" from the EvseManager
|
||||
|
||||
each of these functions will have a small note what the Session Event was and what it triggers in libocpp
|
||||
|
||||
- on_enabled(std::int32_t connector)
|
||||
|
||||
Notifies libocpp that the connector is functional and operational
|
||||
|
||||
- on_disabled(std::int32_t connector)
|
||||
|
||||
Notifies libocpp that the connector is disabled
|
||||
|
||||
- on_transaction_started
|
||||
|
||||
Notifies libocpp that a transaction at the given connector has started, this means that authorization is available and the car is plugged in.
|
||||
|
||||
Some of its parameters:
|
||||
|
||||
session_id is an internal session_id originating in the EvseManager to keep track of the transaction, this is NOT to be mistaken for the transactionId from the StartTransactionResponse in OCPP!
|
||||
|
||||
id_token is the token with which the transaction was authenticated
|
||||
|
||||
meter_start contains the meter value in Wh for the connector at start of the transaction
|
||||
|
||||
timestamp at the start of the transaction
|
||||
|
||||
- on_transaction_stopped
|
||||
|
||||
Notifies libocpp that the transaction on the given connector with the given reason has been stopped.
|
||||
|
||||
Some of its parameters:
|
||||
|
||||
timestamp at the end of the transaction
|
||||
|
||||
energy_wh_import contains the meter value in Wh for the connector at end of the transaction
|
||||
|
||||
- on_suspend_charging_ev
|
||||
|
||||
Notifies libocpp that the EV has paused charging
|
||||
|
||||
- on_suspend_charging_evse
|
||||
|
||||
Notifies libocpp that the EVSE has paused charging
|
||||
|
||||
- on_resume_charging
|
||||
|
||||
Notifies libocpp that charging has resumed
|
||||
|
||||
- on_session_started
|
||||
|
||||
this is mostly used for logging and changing the connector state
|
||||
|
||||
- on_session_stopped
|
||||
|
||||
this is mostly used for logging and changing the connector state
|
||||
|
||||
- on_error
|
||||
|
||||
Notify libocpp of an error
|
||||
|
||||
- on_reservation_start
|
||||
|
||||
Notifies libocpp that a reservation has started
|
||||
|
||||
- on_reservation_end
|
||||
|
||||
Notifies libocpp that a reservation has ended
|
||||
|
||||
#### Authorization
|
||||
|
||||
In EVerest authorization is handled by the Auth module and various auth token providers and validators. The OCPP module acts as both a token provider (for pre validated tokens in RemoteStartTransactions) and a token validator (using the authorize requests, or plug & charge)
|
||||
To use libocpp as a auth token validator (e.g. before starting a transaction) you can call the "authorize_id_token" function of the ChargePoint object.
|
||||
@@ -0,0 +1,348 @@
|
||||
<mxfile host="65bd71144e">
|
||||
<diagram id="enTwcsW3kJKdFbbsoOIB" name="Page-1">
|
||||
<mxGraphModel dx="2808" dy="1103" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0"/>
|
||||
<mxCell id="1" parent="0"/>
|
||||
<mxCell id="120" style="edgeStyle=none;html=1;entryX=0.25;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" target="107">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="680" y="1260" as="sourcePoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="760" y="1260"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="121" value="FaultDetected" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="120">
|
||||
<mxGeometry x="0.5379" relative="1" as="geometry">
|
||||
<mxPoint x="-54" y="-10" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="187" style="edgeStyle=none;html=1;entryX=0.25;entryY=0;entryDx=0;entryDy=0;strokeColor=#000000;" edge="1" parent="1" target="100">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="40" y="380" as="sourcePoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="160" y="380"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="188" value="UsageInitiated" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="187">
|
||||
<mxGeometry x="-0.669" y="2" relative="1" as="geometry">
|
||||
<mxPoint x="14" y="-8" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="200" style="edgeStyle=none;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;strokeColor=#000000;" edge="1" parent="1" source="7" target="199">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="360" y="300"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="7" value="Available" style="rounded=1;whiteSpace=wrap;html=1;arcSize=40;fontColor=#000000;fillColor=#ffffc0;strokeColor=#ff0000;" parent="1" vertex="1">
|
||||
<mxGeometry x="120" y="280" width="160" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="9" value="" style="ellipse;html=1;shape=startState;fillColor=#000000;strokeColor=#ff0000;" parent="1" vertex="1">
|
||||
<mxGeometry x="70" y="285" width="30" height="30" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="10" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;verticalAlign=bottom;endArrow=open;endSize=8;strokeColor=#ff0000;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=0.867;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" parent="1" source="9" target="7" edge="1">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="210" y="330" as="targetPoint"/>
|
||||
<mxPoint x="110" y="390" as="sourcePoint"/>
|
||||
<Array as="points"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="133" style="edgeStyle=none;html=1;entryX=0.25;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" target="7">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="40" y="260" as="sourcePoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="160" y="260"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="134" value="BecomeAvailable" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="133">
|
||||
<mxGeometry x="-0.308" y="-9" relative="1" as="geometry">
|
||||
<mxPoint x="5" y="-19" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="189" style="edgeStyle=none;html=1;entryX=0.25;entryY=0;entryDx=0;entryDy=0;strokeColor=#000000;" edge="1" parent="1" target="104">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="40" y="860" as="sourcePoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="160" y="860"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="190" value="TransactionStoppedAndUserActionRequired" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="189">
|
||||
<mxGeometry x="-0.3632" relative="1" as="geometry">
|
||||
<mxPoint x="-35" y="-10" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="204" style="edgeStyle=none;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;strokeColor=#000000;spacingLeft=8;" edge="1" parent="1" source="101" target="203">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="360" y="540"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="101" value="Charging" style="rounded=1;whiteSpace=wrap;html=1;arcSize=40;fontColor=#000000;fillColor=#ffffc0;strokeColor=#ff0000;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="520" width="160" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="193" style="edgeStyle=none;html=1;entryX=0.25;entryY=0;entryDx=0;entryDy=0;strokeColor=#000000;" edge="1" parent="1" target="102">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="40" y="620" as="sourcePoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="160" y="620"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="194" value="PauseChargingEV" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="193">
|
||||
<mxGeometry x="-0.176" y="5" relative="1" as="geometry">
|
||||
<mxPoint x="2" y="-5" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="214" style="edgeStyle=none;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;strokeColor=#000000;" edge="1" parent="1" source="105" target="213">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="360" y="1020"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="105" value="Reserved" style="rounded=1;whiteSpace=wrap;html=1;arcSize=40;fontColor=#000000;fillColor=#ffffc0;strokeColor=#ff0000;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="1000" width="160" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="216" style="edgeStyle=none;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;strokeColor=#000000;" edge="1" parent="1" source="106" target="215">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="360" y="1140"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="106" value="Unavailable" style="rounded=1;whiteSpace=wrap;html=1;arcSize=40;fontColor=#000000;fillColor=#ffffc0;strokeColor=#ff0000;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="1120" width="160" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="217" style="edgeStyle=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;strokeColor=#000000;" edge="1" parent="1" source="107">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="680" y="300" as="targetPoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="800" y="300"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="232" value="I1_ReturnToAvailable" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="217">
|
||||
<mxGeometry x="0.9517" y="-6" relative="1" as="geometry">
|
||||
<mxPoint x="33" y="-4" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="218" style="edgeStyle=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;strokeColor=#000000;" edge="1" parent="1" source="107">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="680" y="1140" as="targetPoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="800" y="1140"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="220" value="I8_ReturnToUnavailable" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="218">
|
||||
<mxGeometry x="0.7671" y="-2" relative="1" as="geometry">
|
||||
<mxPoint x="30" y="-8" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="219" style="edgeStyle=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;strokeColor=#000000;" edge="1" parent="1" source="107">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="680" y="1020" as="targetPoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="800" y="1020"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="221" value="I7_ReturnToReserved" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="219">
|
||||
<mxGeometry x="0.8661" y="-4" relative="1" as="geometry">
|
||||
<mxPoint x="35" y="-6" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="222" style="edgeStyle=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;strokeColor=#000000;" edge="1" parent="1" source="107">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="680" y="900" as="targetPoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="800" y="900"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="223" value="I6_ReturnToFinishing" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="222">
|
||||
<mxGeometry x="0.8928" y="-3" relative="1" as="geometry">
|
||||
<mxPoint x="33" y="-7" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="224" style="edgeStyle=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;strokeColor=#000000;" edge="1" parent="1" source="107">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="680" y="780" as="targetPoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="800" y="780"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="225" value="I5_ReturnToSuspendedEVSE" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="224">
|
||||
<mxGeometry x="0.9051" y="-4" relative="1" as="geometry">
|
||||
<mxPoint x="10" y="-6" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="226" style="edgeStyle=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;strokeColor=#000000;" edge="1" parent="1" source="107">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="680" y="660" as="targetPoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="800" y="660"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="227" value="I4_ReturnToSuspendedEV" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="226">
|
||||
<mxGeometry x="0.935" y="-1" relative="1" as="geometry">
|
||||
<mxPoint x="26" y="-9" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="228" style="edgeStyle=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;strokeColor=#000000;" edge="1" parent="1" source="107">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="680" y="540" as="targetPoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="800" y="540"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="229" value="I3_ReturnToCharging" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="228">
|
||||
<mxGeometry x="0.9352" y="-7" relative="1" as="geometry">
|
||||
<mxPoint x="32" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="230" style="edgeStyle=none;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;strokeColor=#000000;" edge="1" parent="1" source="107">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="680" y="420" as="targetPoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="800" y="420"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="231" value="I2_ReturnToPreparing" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="230">
|
||||
<mxGeometry x="0.9511" y="-2" relative="1" as="geometry">
|
||||
<mxPoint x="36" y="-8" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="107" value="Faulted" style="rounded=1;whiteSpace=wrap;html=1;arcSize=40;fontColor=#000000;fillColor=#ffffc0;strokeColor=#ff0000;" vertex="1" parent="1">
|
||||
<mxGeometry x="720" y="1280" width="160" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="185" style="edgeStyle=none;html=1;entryX=0.25;entryY=0;entryDx=0;entryDy=0;strokeColor=#000000;" edge="1" parent="1" target="101">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="40" y="500" as="sourcePoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="160" y="500"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="186" value="StartCharging" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="185">
|
||||
<mxGeometry x="-0.5184" y="-1" relative="1" as="geometry">
|
||||
<mxPoint x="21" y="-11" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="202" style="edgeStyle=none;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;strokeColor=#000000;" edge="1" parent="1" source="100" target="201">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="360" y="420"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="100" value="Preparing" style="rounded=1;whiteSpace=wrap;html=1;arcSize=40;fontColor=#000000;fillColor=#ffffc0;strokeColor=#ff0000;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="400" width="160" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="195" style="edgeStyle=none;html=1;entryX=0.25;entryY=0;entryDx=0;entryDy=0;strokeColor=#000000;" edge="1" parent="1" target="103">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="40" y="740" as="sourcePoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="160" y="740"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="196" value="PauseChargingEVSE" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="195">
|
||||
<mxGeometry x="-0.0756" relative="1" as="geometry">
|
||||
<mxPoint x="-15" y="-10" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="206" style="edgeStyle=none;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;strokeColor=#000000;" edge="1" parent="1" source="102" target="205">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="360" y="660"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="102" value="SuspendedEV" style="rounded=1;whiteSpace=wrap;html=1;arcSize=40;fontColor=#000000;fillColor=#ffffc0;strokeColor=#ff0000;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="640" width="160" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="191" style="edgeStyle=none;html=1;entryX=0.25;entryY=0;entryDx=0;entryDy=0;strokeColor=#000000;" edge="1" parent="1" target="105">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="40" y="980" as="sourcePoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="160" y="980"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="192" value="ReserveConnector" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="191">
|
||||
<mxGeometry x="-0.2041" y="2" relative="1" as="geometry">
|
||||
<mxPoint x="4" y="-8" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="212" style="edgeStyle=none;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;strokeColor=#000000;" edge="1" parent="1" source="104" target="211">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="360" y="900"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="104" value="Finishing" style="rounded=1;whiteSpace=wrap;html=1;arcSize=40;fontColor=#000000;fillColor=#ffffc0;strokeColor=#ff0000;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="880" width="160" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="197" style="edgeStyle=none;html=1;entryX=0.25;entryY=0;entryDx=0;entryDy=0;strokeColor=#000000;" edge="1" parent="1" target="106">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="40" y="1100" as="sourcePoint"/>
|
||||
<Array as="points">
|
||||
<mxPoint x="160" y="1100"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="198" value="ChangeAvailabilityToUnavailable" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="197">
|
||||
<mxGeometry x="-0.1438" y="7" relative="1" as="geometry">
|
||||
<mxPoint x="-30" y="-3" as="offset"/>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="208" style="edgeStyle=none;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.75;entryDx=0;entryDy=0;strokeColor=#000000;" edge="1" parent="1" source="103" target="207">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="360" y="780"/>
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="103" value="SuspendedEVSE" style="rounded=1;whiteSpace=wrap;html=1;arcSize=40;fontColor=#000000;fillColor=#ffffc0;strokeColor=#ff0000;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="760" width="160" height="40" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="199" value="UsageInitiated<br>StartCharging<br>PauseChargingEV<br>PauseChargingEVSE<br>ReserveConnector<br>ChangeAvailabilityToUnavailable" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;spacingLeft=8;" vertex="1" parent="1">
|
||||
<mxGeometry x="360" y="280" width="240" height="120" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="201" value="BecomeAvailable<br>StartCharging<br>PauseChargingEV<br>PauseChargingEVSE<br>TransactionStoppedAndUserActionRequired" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;spacingLeft=8;" vertex="1" parent="1">
|
||||
<mxGeometry x="360" y="400" width="240" height="120" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="203" value="BecomeAvailable<br>PauseChargingEV<br>PauseChargingEVSE<br>TransactionStoppedAndUserActionRequired<br>ChangeAvailabilityToUnavailable" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;spacingLeft=8;" vertex="1" parent="1">
|
||||
<mxGeometry x="360" y="520" width="240" height="120" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="205" value="BecomeAvailable<br>StartCharging<br>PauseChargingEVSE<br>TransactionStoppedAndUserActionRequired<br>ChangeAvailabilityToUnavailable" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;spacingLeft=8;" vertex="1" parent="1">
|
||||
<mxGeometry x="360" y="640" width="240" height="120" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="207" value="BecomeAvailable<br>StartCharging<br>PauseChargingEV<br>TransactionStoppedAndUserActionRequired<br>ChangeAvailabilityToUnavailable" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;spacingLeft=8;" vertex="1" parent="1">
|
||||
<mxGeometry x="360" y="760" width="260" height="120" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="211" value="BecomeAvailable<br>UsageInitiated<br>ChangeAvailabilityToUnavailable" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;spacingLeft=8;" vertex="1" parent="1">
|
||||
<mxGeometry x="360" y="880" width="260" height="120" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="213" value="BecomeAvailable<br>UsageInitiated<br>ChangeAvailabilityToUnavailable" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;spacingLeft=8;" vertex="1" parent="1">
|
||||
<mxGeometry x="360" y="1000" width="260" height="120" as="geometry"/>
|
||||
</mxCell>
|
||||
<mxCell id="215" value="BecomeAvailable<br>UsageInitiated<br>StartCharging<br>PauseChargingEV<br>PauseChargingEVSE" style="text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;whiteSpace=wrap;rounded=0;spacingLeft=8;" vertex="1" parent="1">
|
||||
<mxGeometry x="360" y="1120" width="240" height="120" as="geometry"/>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -0,0 +1,13 @@
|
||||
# Getting Started with OCPP2.0.1
|
||||
|
||||
## Integrate this library with your Charging Station Implementation for OCPP2.0.1
|
||||
|
||||
OCPP is a protocol that affects, controls and monitors many areas of a charging station's operation.
|
||||
|
||||
If you want to integrate this library with your charging station implementation, you have to register a couple of **callbacks** and integrate **event handlers**. This is necessary for the library to interact with your charging station according to the requirements of OCPP.
|
||||
|
||||
Libocpp needs registered **callbacks** in order to execute control commands defined within OCPP (e.g ResetRequest or RemoteStartTransactionRequest)
|
||||
|
||||
The implementation must call **event handlers** of libocpp so that the library can track the state of the charging station and trigger OCPP messages accordingly (e.g. MeterValuesRequest , StatusNotificationRequest)
|
||||
|
||||
Your reference within libocpp to interact is a single instance to the class ocpp::v2::ChargePoint defined in `v2/charge_point.hpp` for OCPP 2.0.1.
|
||||
@@ -0,0 +1,57 @@
|
||||
# OCPP 2.0.1 and 2.1: Device model initialization and inserting of config values
|
||||
|
||||
If there is no custom database used for the device model, and 'initialize_device_model' is set to true in the
|
||||
constructor of ChargePoint, the device model will be created or updated when ChargePoint is created. This document will
|
||||
give more information about the files you need and what will be updated when the 'initialize_device_model' is set
|
||||
to true.
|
||||
|
||||
|
||||
## Database, component config and config file paths
|
||||
|
||||
Along with the 'initialize_device_model' flag, a few paths must be given to the constructor:
|
||||
- The path of the device model migration files (normally `resources/v2/device_model_migration_files`).
|
||||
- The path of the device model database.
|
||||
- The path of the directory with the device model config. There should be two directories in it: 'standardized' and
|
||||
'custom', both containing device model config.
|
||||
|
||||
|
||||
## Component config
|
||||
|
||||
When the database is created for the first time, it will insert all components, variables, characteristics and
|
||||
attributes from the component config.
|
||||
|
||||
|
||||
## Update config values
|
||||
|
||||
Each time the ChargePoint class is instantiated, the component config is read and the values will be set to the database
|
||||
accordingly. Only the initial values will be set to the values in the component config. So if for example the CSMS
|
||||
changed a value, it will not be updated to the value from the component config file.
|
||||
|
||||
|
||||
## Update component config
|
||||
|
||||
To update a component, just place the correct json component config in the `component_config/custom` or
|
||||
`component_config/standardized` folder. When restarting the software, it will:
|
||||
- Check if there are Components in the database that are not in the component config's. Those will be removed.
|
||||
- Check if there are Components in the component config's that are not in the database. Those will be added.
|
||||
- Check if anything has changed inside the Component (`Variable`, `Characteristics` or `Attributes`).
|
||||
Those will be removed, changed or added to the database as well.
|
||||
|
||||
Note: When the `evse_id` or `connector_id` of a component is changed, this is seen as the removal of a Component and
|
||||
addition of a new one.
|
||||
|
||||
Note: OCPP requires EVSE and Connector numbering starting from 1 counting upwards.
|
||||
|
||||
Note: There should be no duplicate components or variables in the component config files.
|
||||
|
||||
## Required variables
|
||||
|
||||
There are some required Variables, which can be found in the OCPP spec.
|
||||
Some `Variables` are only required if the `Component` is `Available`, for example `Reservation` and `Smart Charging`.
|
||||
There are some Components that are always required because that is how libocpp works: `AlignedDataCtrlr` and
|
||||
`SampledDataCtrlr`.
|
||||
When libocpp is started and initialized, all required Variables will be checked and an DeviceModelError is thrown if
|
||||
one of the required Variables is not there.
|
||||
|
||||
This also implies, that if you write code that needs a required `Variable`, when trying to get that variable with
|
||||
`DeviceModel::get_value(...)`, you should first check if the Component that Variable belongs to is `Available`.
|
||||
@@ -0,0 +1,76 @@
|
||||
# OCPP 2.0.1: Monitors
|
||||
|
||||
Monitors are a mechanism for reporting based on certain criteria the internal state of the variables present on the charger. The monitors can be configured in different ways, with custom monitors being sent from the CSMS and HardWired and Preconfigured monitors set up in the config of the database.
|
||||
|
||||
## Basic Configuration
|
||||
|
||||
The monitors are evaluated from time to time in the case of periodic monitors and after a variable has been modified in the case of monitors that are triggered. Periodic monitors will be handled from time to time, the default being 1 second.
|
||||
|
||||
### Variables
|
||||
|
||||
- Enabling monitors: set the `MonitoringCtrlrEnabled` variable to true
|
||||
- Periodic monitor process time: set the `MonitorsProcessingInterval` to the desired interval (default 1 second)
|
||||
- To activate monitor processing: set the `ActiveMonitoringBase` variable to `All`
|
||||
- To filter the verbosity level: set the `ActiveMonitoringLevel` variable to a value of 0-9 with 9 being the most verbose
|
||||
- To filter the verbosity level when the charging station is offline: set the `OfflineQueuingSeverity` value to 0-9, with 9 keeping all monitor generated event while being offline
|
||||
|
||||
Note: There is a small overhead for the monitoring process interval. The periodic monitors that are triggered will require a database value query. However, based on the count and config of monitors it is unlikely that many of them will trigger at the same time, therefore, the database queries will be limited.
|
||||
|
||||
## Hardwired/Preconfigured Monitors
|
||||
|
||||
In order to set up pre-existing monitors that are not set up by the CSMS, for the variables that allow monitoring the configuration json file can be extended in the following way:
|
||||
|
||||
```json
|
||||
"EVSEPower": {
|
||||
"variable_name": "Power",
|
||||
"characteristics": {
|
||||
"unit": "W",
|
||||
"maxLimit": 22000,
|
||||
"supportsMonitoring": true,
|
||||
"dataType": "decimal"
|
||||
},
|
||||
"attributes": [
|
||||
{
|
||||
"type": "Actual",
|
||||
"mutability": "ReadOnly"
|
||||
},
|
||||
{
|
||||
"type": "MaxSet",
|
||||
"mutability": "ReadOnly"
|
||||
}
|
||||
],
|
||||
"monitors": [
|
||||
{
|
||||
"value": 21950,
|
||||
"severity": 1,
|
||||
"transaction": false,
|
||||
"type": "UpperThreshold",
|
||||
"config_type": "HardWiredMonitor"
|
||||
},
|
||||
{
|
||||
"value": 100,
|
||||
"severity": 1,
|
||||
"transaction": false,
|
||||
"type": "LowerThreshold",
|
||||
"config_type": "HardWiredMonitor"
|
||||
},
|
||||
{
|
||||
"value": 100,
|
||||
"severity": 1,
|
||||
"transaction": false,
|
||||
"type": "Delta",
|
||||
"reference_value": "10700",
|
||||
"config_type": "PreconfiguredMonitor"
|
||||
}
|
||||
],
|
||||
"description": "",
|
||||
"type": "number",
|
||||
"default": "0"
|
||||
}
|
||||
```
|
||||
|
||||
In the example for the 'EVSEPower' variable that supports monitoring there were attached three hardwired and preconfigured monitors. The monitors will report (based on the setup, see the `Basic Configuration` section) to the CSMS when the power will exceed '21950' W, when the power will fall below '100' W and when there will be a delta difference of more than `100` W from the `reference_value` in the case of the delta monitor. When the delta is exceeded the `reference_value` will be updated internally, and a new delta will be calculated based on that.
|
||||
|
||||
For more information related to the monitor functionality, please refer to the OCPP201 specification.
|
||||
|
||||
Note: for a delta monitor, an initial `reference_value` must be provided or the library will fail to initialize.
|
||||
@@ -0,0 +1,37 @@
|
||||
@startuml Periodic Monitors
|
||||
|
||||
start
|
||||
:DB Monitor Config Setup;
|
||||
:DB Monitor Initialization;
|
||||
if (Monitoring Enabled?) then (yes)
|
||||
:Query Config\nProcess Time;
|
||||
:Enable Monitor\nProcessor;
|
||||
repeat
|
||||
:Verify Periodic\nMonitors;
|
||||
if(Periodic Monitor\nTime Elapsed?) then (yes)
|
||||
if(Online?) then (yes)
|
||||
if(Active Monitoring Severity\n> Monitor Severity?) then (yes)
|
||||
:Queue \nPeriodic Monitor;
|
||||
else (no)
|
||||
:Discard Monitor;
|
||||
endif
|
||||
else (no)
|
||||
if(Offline Monitoring Severity\n> Monitor Severity?) then (yes)
|
||||
:Queue \nPeriodic Monitor;
|
||||
else (no)
|
||||
:Discard Monitor;
|
||||
endif
|
||||
endif
|
||||
else(no)
|
||||
:Update Monitor\nElapsed Time;
|
||||
endif
|
||||
:Verify Triggered\nMonitors\n(see trigger flowchart);
|
||||
:Queue Triggered\nMonitors;
|
||||
:Send Queued\nMonitors to CSMS;
|
||||
repeat while (Each N Seconds)
|
||||
else (no)
|
||||
:Do Nothing;
|
||||
endif
|
||||
stop
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,35 @@
|
||||
@startuml Periodic Monitors
|
||||
|
||||
start
|
||||
:DB Monitor Config Setup;
|
||||
:DB Monitor Initialization;
|
||||
if (Monitoring Enabled?) then (yes)
|
||||
:Inject Device\nModel Listener;
|
||||
:Enable Monitor\nProcessor;
|
||||
repeat
|
||||
:Verify Variable\nMonitors;
|
||||
if(Variable Monitor\nTriggered?) then (yes)
|
||||
if(Online?) then (yes)
|
||||
if(Active Monitoring Severity\n> Monitor Severity?) then (yes)
|
||||
:Queue Monitor;
|
||||
else (no)
|
||||
:Discard Monitor;
|
||||
endif
|
||||
else (no)
|
||||
if(Offline Monitoring Severity\n> Monitor Severity?) then (yes)
|
||||
:Queue Monitor;
|
||||
else (no)
|
||||
:Discard Monitor;
|
||||
endif
|
||||
endif
|
||||
else(no)
|
||||
:Do Nothing;
|
||||
endif
|
||||
:Process Queued\nMonitors;
|
||||
repeat while (DB Variable Updated)
|
||||
else (no)
|
||||
:Do Nothing;
|
||||
endif
|
||||
stop
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,181 @@
|
||||
# Smart Charging Use Cases
|
||||
|
||||
The use cases within the Smart Charging functional block are subdivided into the following three categories of use cases:
|
||||
|
||||
1. General Smart Charging (Use Cases K01–K10)
|
||||
2. External Charging Limit-based Smart Charging (K11–K14)
|
||||
3. ISO 15118-based Smart Charging (K15–K17)
|
||||
|
||||
Support for General and External Charging Limit-based Smart Charging is largely complete, with ISO 15118-based Smart Charging under active development. For an up-to-date overview of exactly which features are currently supported as well as design decisions that have been made to address optional or ambiguous functional requirements, please refer to the [OCPP 2.0.1 Status document](ocpp_2x_status.md).
|
||||
|
||||
## K01 SetChargingProfile
|
||||
|
||||
Allows the CSMS to influence the charging power or current drawn from a specific EVSE or the
|
||||
entire Charging Station over a period of time.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
CSMS->>+ChargePoint : SetChargingProfileRequest(call)
|
||||
|
||||
ChargePoint->>+DeviceModel : SmartChargingCtrlrAvailable?
|
||||
DeviceModel-->>-ChargePoint : Component
|
||||
|
||||
rect rgb(128,202,255)
|
||||
break SmartChargingCtrlrAvailable = false
|
||||
ChargePoint-->>CSMS : Smart Charging NotSupported CallError
|
||||
end
|
||||
end
|
||||
|
||||
ChargePoint->>+SmartCharging : validate_and_add_profile(call.msg.Profile, call.msg.EVSE ID)
|
||||
|
||||
SmartCharging->>SmartCharging : validate_profile(Profile, EVSE ID)
|
||||
|
||||
rect rgb(128,202,255)
|
||||
break Invalid Profile
|
||||
SmartCharging-->>ChargePoint : SetChargingProfileResponse: Rejected
|
||||
ChargePoint-->>CSMS : SetChargingProfileResponse: Rejected
|
||||
end
|
||||
end
|
||||
|
||||
SmartCharging->>+SmartCharging : add_profile(Profile, EVSE ID)
|
||||
SmartCharging->>-EVerest : signal_set_charging_profiles_callback
|
||||
|
||||
SmartCharging-->>-ChargePoint : SetChargingProfileResponse: Accepted
|
||||
|
||||
ChargePoint-->>-CSMS : SetChargingProfileResponse: Accepted
|
||||
```
|
||||
|
||||
Profile validation returns the following errors to the caller when a Profile
|
||||
is `Rejected`:
|
||||
|
||||
| Errors | Description |
|
||||
| :------------------------------------------------------------ | :-------------------------------------------------------------- |
|
||||
| `ChargingProfileFirstStartScheduleIsNotZero` | The `startPeriod` of the first `chargingSchedulePeriod` needs to be 0.<br>[K01.FR.31] |
|
||||
| `ChargingProfileNoChargingSchedulePeriods` | Happens when the `ChargingProfile` doesn't have any Charging Schedule Periods. |
|
||||
| `ChargingScheduleChargingRateUnitUnsupported` | Happens when a chargingRateUnit is passed in that is not configured in the `ChargingScheduleChargingRateUnit`. [K01.FR.26] |
|
||||
| `ChargingSchedulePeriodInvalidPhaseToUse` | Happens when an invalid `phaseToUse` is passed in. [K01.FR.19] [K01.FR.48] |
|
||||
| `ChargingSchedulePeriodPhaseToUseACPhaseSwitchingUnsupported` | Happens when `phaseToUse` is passed in and the EVSE does not have `ACPhaseSwitchingSupported` defined and set to true. [K01.FR.20] [K01.FR.48] |
|
||||
| `ChargingSchedulePeriodsOutOfOrder` | `ChargingSchedulePeriod.startPeriod` elements need to be in increasing values. [K01.FR.35] |
|
||||
| `ChargingStationMaxProfileCannotBeRelative` | Happens when a `ChargingStationMaxProfile.chargingProfileKind` is set to `Relative`. [K01.FR.38] |
|
||||
| `ChargingStationMaxProfileEvseIdGreaterThanZero` | Happens when a `ChargingStationMaxProfile` is attempted to be set with an EvseID isn't `0`. [K01.FR.03] |
|
||||
| `ChargingProfileMissingRequiredStartSchedule` | Happens when an `Absolute` or `Recurring` `ChargingProfile` doesn't have a `startSchedule`. [K01.FR.40] |
|
||||
| `ChargingProfileExtraneousStartSchedule` | Happens when a Relative `ChargingProfile` has a `startSchedule`. [K01.FR.41] |
|
||||
| `EvseDoesNotExist` | Happens when the `evseId`of a `SetChargingProfileRequest` does not exist. [K01.FR.28] |
|
||||
| `ExistingChargingStationExternalConstraints` | Happens when a `SetChargingProfileRequest` Profile has a purpose of `ChargingStationExternalConstraints` and one already exists with the same `ChargingProfile.id` exists. [K01.FR.05] |
|
||||
| `InvalidProfileType` | Happens when a `ChargingStationMaxProfile` is attempted to be set with a `ChargingProfile` that isn't a `ChargingStationMaxProfile`. |
|
||||
| `TxProfileEvseHasNoActiveTransaction` | Happens when a `SetChargingProfileRequest` with a `TxProfile` is submitted and there is no transaction active on the specified EVSE. [K01.FR.09] |
|
||||
| `TxProfileEvseIdNotGreaterThanZero` | `TxProfile` needs to have an `evseId` greater than 0. [K01.FR.16] |
|
||||
| `TxProfileMissingTransactionId` | A `transactionId` is required for`SetChargingProfileRequest`s with a `TxProfile` in order to match the profile to a specific transation. [K01.FR.03] |
|
||||
| `TxProfileTransactionNotOnEvse` | Happens when the provided `transactionId` is not known. [K01.FR.33] |
|
||||
| `TxProfileConflictingStackLevel` | Happens when a `TxProfile` has a `stackLevel` and `transactionId` combination already exists in a `TxProfile` with a different id in order to ensure that no two charging profiles with same stack level and purpose can be valid at the same time. [K01.FR.39] |
|
||||
|
||||
## K08 Get Composite Schedule
|
||||
|
||||
The CSMS requests the Charging Station to report the Composite Charging
|
||||
Schedule, as calculated by the Charging Station for a specific point of
|
||||
time, and may change over time due to external causes such as local
|
||||
balancing based on grid connection capacity and EVSE availablity.
|
||||
|
||||
The Composite Schedule is the result of result of merging the time periods
|
||||
set in the `ChargingStationMaxProfile`, `ChargingStationExternalConstraints`,
|
||||
`TxDefaultProfile` and `TxProfile` type profiles.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
|
||||
CSMS->>+ChargePoint: GetCompositeSchedule(call)
|
||||
|
||||
ChargePoint->>+DeviceModel : ChargingScheduleChargingRateUnit?
|
||||
DeviceModel-->>-ChargePoint : Component
|
||||
|
||||
rect rgb(128,202,255)
|
||||
break call.msg.chargingRateUnit is not supported
|
||||
ChargePoint-->>CSMS : ChargingScheduleChargingRateUnitUnsupported CallError
|
||||
end
|
||||
end
|
||||
|
||||
ChargePoint->>+EvseManager : does_evse_exist(call.msg.evseId)
|
||||
EvseManager-->>-ChargePoint : bool
|
||||
rect rgb(128,202,255)
|
||||
break EVSE does not exist
|
||||
ChargePoint-->>CSMS : EvseDoesNotExist CallError
|
||||
end
|
||||
end
|
||||
|
||||
ChargePoint->>+SmartChargingHandler : get_valid_profiles(call.msg.evseId)
|
||||
|
||||
SmartChargingHandler-->>-ChargePoint : vector<ChargingProfile>
|
||||
|
||||
ChargePoint->>+SmartChargingHandler : calculate_composite_schedule<br/>(vector<ChargingProfile, now, msg.duration, evseId, call.msg.chargingRateUnit)
|
||||
|
||||
loop ExternalConstraints, Max, TxDefault, and Tx Profiles
|
||||
SmartChargingHandler->>+Profile: calculate_composite_schedule(profiles)
|
||||
Profile-->>-SmartChargingHandler: composite_schedule
|
||||
end
|
||||
|
||||
note right of SmartChargingHandler: Create consolidated CompositeSchedule<br />from all 4 Profile types
|
||||
|
||||
|
||||
SmartChargingHandler->>+Profile: calculate_composite_schedule(ExternalConstraints, Max, TxDefault, Tx)
|
||||
Profile-->>-SmartChargingHandler: CompositeSchedule
|
||||
|
||||
SmartChargingHandler-->>-ChargePoint: CompositeSchedule
|
||||
|
||||
ChargePoint-->>-CSMS : GetCompositeScheduleResponse(CompositeSchedule)
|
||||
```
|
||||
|
||||
## K09 Get Charging Profiles
|
||||
|
||||
Returns to the CSMS the Charging Schedules/limits installed on a Charging Station based on the
|
||||
passed in criteria.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
|
||||
CSMS->>+ChargePoint: GetChargingProfiles(criteria)
|
||||
|
||||
ChargePoint->>+SmartChargingHandler : get_reported_profiles(criteria)
|
||||
|
||||
loop filter ChargingProfiles
|
||||
SmartChargingHandler->>SmartChargingHandler: filter on ChargingProfile criteria
|
||||
end
|
||||
|
||||
SmartChargingHandler-->>-ChargePoint : Vector<Profiles>
|
||||
|
||||
ChargePoint-->>CSMS : GetChargingProfilesResponse(profiles)
|
||||
|
||||
alt no Profiles
|
||||
rect rgb(128,202,255)
|
||||
ChargePoint-->>CSMS : GetChargingProfilesResponse(NoProfiles)
|
||||
ChargePoint->>CSMS : return
|
||||
end
|
||||
|
||||
else Profiles
|
||||
ChargePoint-->>CSMS : GetChargingProfilesResponse(Accepted)
|
||||
end
|
||||
|
||||
ChargePoint->>ChargePoint : determine profiles_to_report
|
||||
|
||||
ChargePoint-->>-CSMS : ReportChargingProfilesRequest(profiles_to_report)
|
||||
```
|
||||
|
||||
## K10 Clear Charging Profile
|
||||
|
||||
Clears Charging Profiles installed on a Charging Station based on the
|
||||
passed in criteria.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
|
||||
CSMS->>+ChargePoint: ClearChargingProfileRequest(criteria)
|
||||
|
||||
alt no Profiles matching criteria
|
||||
rect rgb(128,202,255)
|
||||
ChargePoint-->>CSMS : ClearChargingProfileResponse(Unknown)
|
||||
|
||||
end
|
||||
|
||||
else found matching Profiles
|
||||
ChargePoint-->>-CSMS : ClearChargingProfileResponse(Accepted)
|
||||
end
|
||||
```
|
||||
2862
tools/EVerest-main/lib/everest/ocpp/doc/v2/ocpp_2x_status.md
Normal file
2862
tools/EVerest-main/lib/everest/ocpp/doc/v2/ocpp_2x_status.md
Normal file
File diff suppressed because it is too large
Load Diff
142
tools/EVerest-main/lib/everest/ocpp/doc/v2/r04_der_supersede.md
Normal file
142
tools/EVerest-main/lib/everest/ocpp/doc/v2/r04_der_supersede.md
Normal file
@@ -0,0 +1,142 @@
|
||||
# R04 DER Control — Supersede Mechanism
|
||||
|
||||
This document explains how `SetDERControl` (OCPP 2.1, use case R04) decides
|
||||
whether a newly received control supersedes existing controls of the same
|
||||
`controlType` + `isDefault`, and when that supersede takes effect.
|
||||
|
||||
## Priority comparison
|
||||
|
||||
OCPP 2.1 R04 uses a **lower priority value wins** convention:
|
||||
|
||||
| `new.priority` vs `existing.priority` | Outcome |
|
||||
|----|----|
|
||||
| `new < existing` | new overrules existing (FR.03 / FR.06 / FR.07) |
|
||||
| `new == existing` | tie does **not** supersede; existing keeps running |
|
||||
| `new > existing` | existing overrules new; new is stored already-superseded (FR.08) |
|
||||
|
||||
The comparison only runs against rows that share the same `controlType` and
|
||||
`isDefault`. Different control types coexist independently.
|
||||
|
||||
## FR.03 — default vs default
|
||||
|
||||
Defaults have no `startTime` / `duration`. They are conceptually always
|
||||
"active" but only one default per `controlType` is in force at any time.
|
||||
|
||||
- New default with **strictly lower** `priority` value than the existing
|
||||
default → existing is flagged `IS_SUPERSEDED = 1` immediately.
|
||||
- New default with equal priority value → both rows persist, neither is
|
||||
flagged. Existing keeps running.
|
||||
- New default with strictly higher priority value → new row is stored with
|
||||
`IS_SUPERSEDED = 1`; the existing default keeps running.
|
||||
|
||||
## FR.06 — existing not yet active
|
||||
|
||||
A scheduled control whose `startTime > now` is "not yet active". When a new
|
||||
control overrules it (lower priority value), the supersede is **immediate**:
|
||||
the existing row is flagged `IS_SUPERSEDED = 1` inside the same transaction
|
||||
that persists the new row.
|
||||
|
||||
The response's `supersededIds` lists the existing `controlId`(s).
|
||||
|
||||
## FR.07 — existing currently active
|
||||
|
||||
When the existing control is currently active (`startTime <= now < startTime + duration`)
|
||||
and the new control has a future `startTime`, the supersede is **deferred**:
|
||||
|
||||
- The new row is stored with `PENDING_SUPERSEDE_ID = <existing controlId>`.
|
||||
- The existing row keeps running until `now >= new.startTime`.
|
||||
- The 30 s periodic check fires the deferred flip the moment `new.startTime`
|
||||
is reached: existing flips to `IS_SUPERSEDED = 1`, the new row's
|
||||
`PENDING_SUPERSEDE_ID` is cleared, and `NotifyDERStartStop(started=true,
|
||||
supersededIds=[existing])` is dispatched for the new control.
|
||||
|
||||
The `SetDERControl` response **omits** `supersededIds` for the deferred case.
|
||||
The CSMS learns about the supersede via the later `NotifyDERStartStop`, not
|
||||
synchronously.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant CSMS
|
||||
participant CS as CS (DERControl)
|
||||
participant DB as DER_CONTROLS
|
||||
|
||||
Note over DB: existing: startTime <= now < expiry
|
||||
CSMS->>CS: SetDERControl(new, startTime=T_future, prio < existing.prio)
|
||||
CS->>DB: BEGIN TRANSACTION
|
||||
CS->>DB: SELECT existing rows for controlType+isDefault
|
||||
Note over CS: existing currently active + new has future startTime
|
||||
CS->>DB: INSERT new row<br/>PENDING_SUPERSEDE_ID = existing.controlId
|
||||
CS->>DB: COMMIT
|
||||
CS-->>CSMS: SetDERControlResponse(Accepted)<br/>(no supersededIds yet)
|
||||
Note over CS: existing keeps running until T_future...
|
||||
Note over CS: 30 s periodic check<br/>now >= T_future
|
||||
CS->>DB: BEGIN TRANSACTION
|
||||
CS->>DB: existing.IS_SUPERSEDED = 1<br/>new.PENDING_SUPERSEDE_ID = NULL<br/>new.STARTED_NOTIFIED = 1
|
||||
CS->>DB: COMMIT
|
||||
CS-->>CSMS: NotifyDERStartStop(new, started=true,<br/>supersededIds=[existing])
|
||||
```
|
||||
|
||||
## FR.08 — new is itself superseded
|
||||
|
||||
If any existing row of the same `controlType` + `isDefault` has **strictly
|
||||
lower** priority value than the new control, the new control loses. It is
|
||||
stored with `IS_SUPERSEDED = 1`, and the response's `supersededIds` echoes
|
||||
the **new** `controlId` to tell the CSMS its just-set control will not run.
|
||||
|
||||
When this happens, any pending immediate or deferred supersede side effects
|
||||
the new control would have caused against other rows are dropped — a row
|
||||
that will never actually take effect must not flag other rows as superseded
|
||||
by it.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant CSMS
|
||||
participant CS as CS (DERControl)
|
||||
participant DB as DER_CONTROLS
|
||||
|
||||
Note over DB: existing: priority = 3 (high)
|
||||
CSMS->>CS: SetDERControl(new, priority = 5 (low))
|
||||
CS->>DB: BEGIN TRANSACTION
|
||||
CS->>DB: SELECT existing rows
|
||||
Note over CS: existing.priority < new.priority<br/>=> new is self-superseded
|
||||
CS->>DB: INSERT new row<br/>IS_SUPERSEDED = 1
|
||||
Note over CS: drop any pending supersede effects new would have caused
|
||||
CS->>DB: COMMIT
|
||||
CS-->>CSMS: SetDERControlResponse(Accepted,<br/>supersededIds=[new.controlId])
|
||||
Note over DB: existing keeps running
|
||||
```
|
||||
|
||||
## `displacedIds` persistence and FR.22 expiry-notify
|
||||
|
||||
Every row records the controlIds it has displaced in
|
||||
`CONTROL_JSON.displacedIds`. The list is populated:
|
||||
|
||||
- at `SetDERControl` insert from the immediate-supersede path
|
||||
(`pending_immediate_supersedes`), and
|
||||
- at deferred-supersede activation via
|
||||
`DatabaseHandler::append_der_control_displaced_id(new_id, existing_id)`.
|
||||
|
||||
When the row expires (`startTime + duration <= now`), the periodic check
|
||||
emits `NotifyDERStartStopRequest(started=false, supersededIds=<displacedIds>)`
|
||||
so the CSMS receives the same supersede relationship on expiry that it saw on
|
||||
the matching start notify. The field is optional per the OCPP 2.1 Part 2
|
||||
§1.48.1 schema (`0..24`), so emitting it on `started=false` is permitted.
|
||||
|
||||
## Side-effect resolution order
|
||||
|
||||
`handle_set_der_control` collects supersede side effects in two slots before
|
||||
applying them:
|
||||
|
||||
- `pending_immediate_supersedes` — list of existing `controlId`s the new row
|
||||
would flip to `IS_SUPERSEDED = 1` synchronously (FR.03 / FR.06 path).
|
||||
- `deferred_supersede_target` — the single existing `controlId` the new row
|
||||
defers superseding via `PENDING_SUPERSEDE_ID` (FR.07 path).
|
||||
|
||||
If during the scan a higher-priority existing row is found (`new_is_superseded =
|
||||
true`, FR.08), both slots are cleared before any DB write. Otherwise, the
|
||||
immediate flips are applied and `superseded_ids` is populated for the
|
||||
response. The single transaction wrapping the whole handler ensures no
|
||||
concurrent CSMS call or scheduled-check pass can observe a half-applied
|
||||
state.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
@@ -0,0 +1,78 @@
|
||||
@startuml
|
||||
'https://plantuml.com/sequence-diagram
|
||||
!pragma teoz true
|
||||
actor user order 10
|
||||
participant core order 20
|
||||
participant libocpp order 30
|
||||
database database order 40
|
||||
participant csms order 50
|
||||
|
||||
autonumber "<b><font color=red>"
|
||||
skinparam sequenceArrowThickness 2
|
||||
|
||||
== Start a transaction ==
|
||||
user->core: Swipe ID tag
|
||||
core->libocpp ++: Authorize
|
||||
libocpp-->core --: Authorized
|
||||
user->core: Plug in cable
|
||||
core->libocpp: on_transaction_started()
|
||||
libocpp->csms: TransactionEventRequest::Started
|
||||
libocpp->database: Insert transaction details
|
||||
|
||||
== Charging state changed (Example) ==
|
||||
user->core: Pauses charging of car
|
||||
core->libocpp: on_charging_state_changed()
|
||||
libocpp->csms: TransactionEventRequest::Updated
|
||||
libocpp->database: Update transaction charging state
|
||||
libocpp->database: Update transaction sequence number
|
||||
|
||||
== Power Outage / Crash of libocpp ==
|
||||
core->libocpp ++ : Construct ChargePoint
|
||||
|
||||
loop For each evse_id
|
||||
alt Database does not contain transaction
|
||||
libocpp->database ++: Get transaction for evse_id
|
||||
database-->libocpp --: Return empty
|
||||
else Database contains transaction
|
||||
libocpp->database ++: Get transaction for evse_id
|
||||
database-->libocpp --: Return transaction details
|
||||
libocpp->libocpp: Re-initialize transaction for evse_id
|
||||
end
|
||||
end
|
||||
|
||||
libocpp-->core -- : Construction complete
|
||||
|
||||
|
||||
note over core,database: Continue normal operations
|
||||
|
||||
user->core: Continues charging of car
|
||||
core->libocpp: on_charging_state_changed()
|
||||
|
||||
alt No support for resuming transactions
|
||||
|
||||
libocpp->libocpp: Print warning of no know transaction
|
||||
|
||||
else With support for resuming transactions
|
||||
|
||||
libocpp->csms: TransactionEventRequest::Updated
|
||||
libocpp->database: Update transaction charging state
|
||||
libocpp->database: Update transaction sequence number
|
||||
|
||||
end
|
||||
|
||||
|
||||
user->core: Swipe ID tag
|
||||
core->libocpp: on_transaction_finished()
|
||||
|
||||
alt No support for resuming transactions
|
||||
|
||||
libocpp->libocpp: Print warning of no know transaction
|
||||
|
||||
else With support for resuming transactions
|
||||
|
||||
libocpp->csms: TransactionEventRequest::Ended
|
||||
libocpp->database: Delete transaction
|
||||
|
||||
end
|
||||
|
||||
@enduml
|
||||
Reference in New Issue
Block a user