Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into harden_against_invali…
Browse files Browse the repository at this point in the history
…d_connectorRange_setChargingProfile
  • Loading branch information
Matthias-NIDEC committed Dec 20, 2024
2 parents b7303e9 + 61a1c54 commit 1d90f5b
Show file tree
Hide file tree
Showing 41 changed files with 2,789 additions and 1,413 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.14)

project(ocpp
VERSION 0.21.0
VERSION 0.22.0
DESCRIPTION "A C++ implementation of the Open Charge Point Protocol"
LANGUAGES CXX
)
Expand Down
6 changes: 4 additions & 2 deletions config/v16/profile_schemas/Internal.json
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,9 @@
"ConnectorEvseIds": {
"$comment": "Comma separated EVSEIDs for OCPP connectors starting with connector 1 in one string.",
"type": "string",
"readOnly": false
"readOnly": false,
"minLength": 7,
"maxLength": 1000
},
"AllowChargingProfileWithoutStartSchedule": {
"$comment": "OCPP1.6 specifies that for certain ChargingProfiles the startSchedule field needs to be set. This flag ignores this requirement and will accept those profiles without startSchedule, assuming startSchedule is now.",
Expand Down Expand Up @@ -326,7 +328,7 @@
"$comment": "Comma separated list of supported measurands of the powermeter",
"type": "string",
"readOnly": true,
"default": "Energy.Active.Import.Register,Energy.Active.Export.Register,Power.Active.Import,Voltage,Current.Import,Frequency,Current.Offered,Power.Offered,SoC"
"default": "Energy.Active.Import.Register,Energy.Active.Export.Register,Power.Active.Import,Voltage,Current.Import,Frequency,Current.Offered,Power.Offered,SoC,Temperature"
},
"MaxMessageSize": {
"$comment": "Maximum size in bytes for messages sent to the CSMS via websocket. If a message exceeds this size, data of the message may be dropped",
Expand Down
4 changes: 3 additions & 1 deletion config/v201/component_config/custom/EVSE_1.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,9 @@
"variable_name": "ISO15118EvseId",
"characteristics": {
"supportsMonitoring": true,
"dataType": "string"
"dataType": "string",
"minLimit": 7,
"maxLimit": 37
},
"attributes": [
{
Expand Down
4 changes: 3 additions & 1 deletion config/v201/component_config/custom/EVSE_2.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,9 @@
"variable_name": "ISO15118EvseId",
"characteristics": {
"supportsMonitoring": true,
"dataType": "string"
"dataType": "string",
"minLimit": 7,
"maxLimit": 37
},
"attributes": [
{
Expand Down
181 changes: 181 additions & 0 deletions doc/v201/ocpp_201_smart_charging_in_depth.md
Original file line number Diff line number Diff line change
@@ -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_201_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
```
8 changes: 4 additions & 4 deletions doc/v201/ocpp_201_status.md
Original file line number Diff line number Diff line change
Expand Up @@ -1086,9 +1086,9 @@ This document contains the status of which OCPP 2.0.1 numbered functional requir
| H01.FR.17 || |
| H01.FR.18 || |
| H01.FR.19 || |
| H01.FR.20 | | |
| H01.FR.23 | | |
| H01.FR.24 | | |
| H01.FR.20 | ⛽️ | |
| H01.FR.23 | ⛽️ | |
| H01.FR.24 | ⛽️ | |

## Reservation - Cancel Reservation

Expand Down Expand Up @@ -1118,7 +1118,7 @@ This document contains the status of which OCPP 2.0.1 numbered functional requir
|-----------|--------|--------|
| H04.FR.01 || |
| H04.FR.02 || |
| H04.FR.03 | | Not all the connectors maybe? |
| H04.FR.03 | ⛽️ | |

## TariffAndCost - Show EV Driver-specific Tariff Information

Expand Down
121 changes: 121 additions & 0 deletions include/ocpp/common/safe_queue.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2024 Pionix GmbH and Contributors to EVerest

#pragma once

#include <condition_variable>
#include <mutex>
#include <queue>

namespace ocpp {

/// \brief Thread safe message queue. Holds a conditional variable
/// that can be waited upon. Will take up the waiting thread on each
/// operation of push/pop/clear
template <typename T> class SafeQueue {
public:
/// \return True if the queue is empty
inline bool empty() const {
std::lock_guard lock(mutex);
return queue.empty();
}

/// \brief We return a copy here, since while might be accessing the
/// reference while another thread uses pop and makes the reference stale
inline T front() {
std::lock_guard lock(mutex);
return queue.front();
}

/// \return retrieves and removes the first element in the queue. Undefined behavior if the queue is empty
inline T pop() {
std::unique_lock<std::mutex> lock(mutex);

T front = std::move(queue.front());
queue.pop();

// Unlock here and notify
lock.unlock();

notify_waiting_thread();

return front;
}

/// \brief Queues an element and notifies any threads waiting on the internal conditional variable
inline void push(T&& value) {
{
std::lock_guard<std::mutex> lock(mutex);
queue.push(value);
}

notify_waiting_thread();
}

/// \brief Queues an element and notifies any threads waiting on the internal conditional variable
inline void push(const T& value) {
{
std::lock_guard<std::mutex> lock(mutex);
queue.push(value);
}

notify_waiting_thread();
}

/// \brief Clears the queue
inline void clear() {
{
std::lock_guard<std::mutex> lock(mutex);

std::queue<T> empty;
empty.swap(queue);
}

notify_waiting_thread();
}

/// \brief Waits for the queue to receive an element
/// \param timeout to wait for an element, pass in a value <= 0 to wait indefinitely
inline void wait_on_queue_element(std::chrono::milliseconds timeout = std::chrono::milliseconds(0)) {
wait_on_queue_element_or_predicate([]() { return false; }, timeout);
}

/// \brief Same as 'wait_on_queue' but receives an additional predicate to wait upon
template <class Predicate>
inline void wait_on_queue_element_or_predicate(Predicate pred,
std::chrono::milliseconds timeout = std::chrono::milliseconds(0)) {
std::unique_lock<std::mutex> lock(mutex);

if (timeout.count() > 0) {
cv.wait_for(lock, timeout, [&]() { return (false == queue.empty()) or pred(); });
} else {
cv.wait(lock, [&]() { return (false == queue.empty()) or pred(); });
}
}

/// \brief Waits on the queue for a custom event
/// \param timeout to wait for an element, pass in a value <= 0 to wait indefinitely
template <class Predicate>
inline void wait_on_custom_event(Predicate pred, std::chrono::milliseconds timeout = std::chrono::milliseconds(0)) {
std::unique_lock<std::mutex> lock(mutex);

if (timeout.count() > 0) {
cv.wait_for(lock, timeout, [&]() { return pred(); });
} else {
cv.wait(lock, [&]() { return pred(); });
}
}

/// \brief Notifies a single waiting thread to wake up
inline void notify_waiting_thread() {
cv.notify_one();
}

private:
std::queue<T> queue;

mutable std::mutex mutex;
std::condition_variable cv;
};

} // namespace ocpp
2 changes: 1 addition & 1 deletion include/ocpp/common/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ struct RPM {
struct Measurement {
Powermeter power_meter; ///< Powermeter data
std::optional<StateOfCharge> soc_Percent; ///< State of Charge in percent
std::optional<Temperature> temperature_C; ///< Temperature in degree Celsius
std::vector<Temperature> temperature_C; ///< Temperature in degree Celsius
std::optional<RPM> rpm; ///< RPM

/// \brief Conversion from a given Measurement \p k to a given json object \p j
Expand Down
Loading

0 comments on commit 1d90f5b

Please sign in to comment.