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,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