Skip to content

Commit

Permalink
Support configurable TxStart and TxStop points (EVerest#546)
Browse files Browse the repository at this point in the history
Changes to be able to support configurable TxStart and TxStop points:
* Now also handling TransactionEvent.conf for Updated (not only Started)
* IdToken became optional for TransactionEvent(Started). To be able to support e.g. TxStartPoint EVConnected
* Added on_authorized handler to report when an Evse has been authorized. This allows sending of TransactionEvent(Updated) with IdToken
* Added trigger_reason as argument to on_charging_state_changed
* make TxStartPoint and TxStopPoint RW and remove ParkingBayOccupancy from valuesList

---------

Signed-off-by: Piet Gömpel <[email protected]>
Signed-off-by: pietfried <[email protected]>
Co-authored-by: marcemmers <[email protected]>
  • Loading branch information
2 people authored and folkengine committed Apr 15, 2024
1 parent e214c50 commit 29e50d9
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 56 deletions.
8 changes: 4 additions & 4 deletions config/v201/component_schemas/standardized/TxCtrlr.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,14 @@
"TxStartPoint": {
"variable_name": "TxStartPoint",
"characteristics": {
"valuesList": "ParkingBayOccupancy,EVConnected,Authorized,PowerPathClosed,EnergyTransfer,DataSigned",
"valuesList": "EVConnected,Authorized,PowerPathClosed,EnergyTransfer,DataSigned",
"supportsMonitoring": true,
"dataType": "MemberList"
},
"attributes": [
{
"type": "Actual",
"mutability": "ReadOnly"
"mutability": "ReadWrite"
}
],
"description": "Defines when the Charging Station starts a new transaction",
Expand All @@ -134,14 +134,14 @@
"TxStopPoint": {
"variable_name": "TxStopPoint",
"characteristics": {
"valuesList": "ParkingBayOccupancy,EVConnected,Authorized,PowerPathClosed,EnergyTransfer",
"valuesList": "EVConnected,Authorized,PowerPathClosed,EnergyTransfer",
"supportsMonitoring": true,
"dataType": "MemberList"
},
"attributes": [
{
"type": "Actual",
"mutability": "ReadOnly"
"mutability": "ReadWrite"
}
],
"description": "Defines when the Charging Station ends a transaction",
Expand Down
11 changes: 8 additions & 3 deletions include/ocpp/v201/charge_point.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ class ChargePoint : ocpp::ChargingStationBase {
void handle_get_local_authorization_list_version_req(Call<GetLocalListVersionRequest> call);

// Functional Block E: Transaction
void handle_start_transaction_event_response(const EnhancedMessage<v201::MessageType>& message);
void handle_transaction_event_response(const EnhancedMessage<v201::MessageType>& message);
void handle_get_transaction_status(const Call<GetTransactionStatusRequest> call);

// Function Block F: Remote transaction control
Expand Down Expand Up @@ -613,7 +613,7 @@ class ChargePoint : ocpp::ChargingStationBase {
/// \param charging_state The new charging state
void on_transaction_started(const int32_t evse_id, const int32_t connector_id, const std::string& session_id,
const DateTime& timestamp, const ocpp::v201::TriggerReasonEnum trigger_reason,
const MeterValue& meter_start, const IdToken& id_token,
const MeterValue& meter_start, const std::optional<IdToken>& id_token,
const std::optional<IdToken>& group_id_token,
const std::optional<int32_t>& reservation_id,
const std::optional<int32_t>& remote_start_id, const ChargingStateEnum charging_state);
Expand All @@ -635,6 +635,9 @@ class ChargePoint : ocpp::ChargingStationBase {
/// \param connector_id
void on_session_finished(const int32_t evse_id, const int32_t connector_id);

/// \brief Event handler that should be called when the given \p id_token is authorized
void on_authorized(const int32_t evse_id, const int32_t connector_id, const IdToken& id_token);

/// \brief Event handler that should be called when a new meter value is present
/// \param evse_id
/// \param meter_value
Expand All @@ -661,8 +664,10 @@ class ChargePoint : ocpp::ChargingStationBase {
/// \brief Event handler that will update the charging state internally when it has been changed.
/// \param evse_id The evse id of which the charging state has changed.
/// \param charging_state The new charging state.
/// \param trigger_reason The trigger reason of the event. Defaults to ChargingStateChanged
/// \return True on success. False if evse id does not exist.
bool on_charging_state_changed(const uint32_t evse_id, ChargingStateEnum charging_state);
bool on_charging_state_changed(const uint32_t evse_id, const ChargingStateEnum charging_state,
const TriggerReasonEnum trigger_reason = TriggerReasonEnum::ChargingStateChanged);

/// \brief Validates provided \p id_token \p certificate and \p ocsp_request_data using CSMS, AuthCache or AuthList
/// \param id_token
Expand Down
6 changes: 3 additions & 3 deletions include/ocpp/v201/evse.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ class EvseInterface {
/// \param sampled_data_tx_updated_interval Interval between sampling of metering (or other) data, intended to
/// be transmitted via TransactionEventRequest (eventType = Updated) messages
virtual void open_transaction(const std::string& transaction_id, const int32_t connector_id,
const DateTime& timestamp, const MeterValue& meter_start, const IdToken& id_token,
const std::optional<IdToken>& group_id_token,
const DateTime& timestamp, const MeterValue& meter_start,
const std::optional<IdToken>& id_token, const std::optional<IdToken>& group_id_token,
const std::optional<int32_t> reservation_id,
const std::chrono::seconds sampled_data_tx_updated_interval,
const std::chrono::seconds sampled_data_tx_ended_interval,
Expand Down Expand Up @@ -170,7 +170,7 @@ class Evse : public EvseInterface {
uint32_t get_number_of_connectors();

void open_transaction(const std::string& transaction_id, const int32_t connector_id, const DateTime& timestamp,
const MeterValue& meter_start, const IdToken& id_token,
const MeterValue& meter_start, const std::optional<IdToken>& id_token,
const std::optional<IdToken>& group_id_token, const std::optional<int32_t> reservation_id,
const std::chrono::seconds sampled_data_tx_updated_interval,
const std::chrono::seconds sampled_data_tx_ended_interval,
Expand Down
2 changes: 1 addition & 1 deletion include/ocpp/v201/transaction.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace v201 {

/// \brief Struct that enhances the OCPP Transaction by some meta data and functionality
struct EnhancedTransaction : public Transaction {
IdToken id_token;
std::optional<IdToken> id_token;
std::optional<IdToken> group_id_token;
std::optional<int32_t> reservation_id;
int32_t connector_id;
Expand Down
127 changes: 83 additions & 44 deletions lib/ocpp/v201/charge_point.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -294,11 +294,14 @@ ChargePoint::on_get_15118_ev_certificate_request(const Get15118EVCertificateRequ
return call_result.msg;
}

void ChargePoint::on_transaction_started(
const int32_t evse_id, const int32_t connector_id, const std::string& session_id, const DateTime& timestamp,
const ocpp::v201::TriggerReasonEnum trigger_reason, const MeterValue& meter_start, const IdToken& id_token,
const std::optional<IdToken>& group_id_token, const std::optional<int32_t>& reservation_id,
const std::optional<int32_t>& remote_start_id, const ChargingStateEnum charging_state) {
void ChargePoint::on_transaction_started(const int32_t evse_id, const int32_t connector_id,
const std::string& session_id, const DateTime& timestamp,
const ocpp::v201::TriggerReasonEnum trigger_reason,
const MeterValue& meter_start, const std::optional<IdToken>& id_token,
const std::optional<IdToken>& group_id_token,
const std::optional<int32_t>& reservation_id,
const std::optional<int32_t>& remote_start_id,
const ChargingStateEnum charging_state) {

this->evses.at(evse_id)->open_transaction(
session_id, connector_id, timestamp, meter_start, id_token, group_id_token, reservation_id,
Expand Down Expand Up @@ -436,6 +439,27 @@ void ChargePoint::on_session_finished(const int32_t evse_id, const int32_t conne
this->evses.at(evse_id)->submit_event(connector_id, ConnectorEvent::PlugOut);
}

void ChargePoint::on_authorized(const int32_t evse_id, const int32_t connector_id, const IdToken& id_token) {
if (this->evses.at(evse_id)->get_transaction() == nullptr) {
// nothing to report in case transaction is not yet open
return;
}

std::unique_ptr<EnhancedTransaction>& transaction =
this->evses.at(static_cast<int32_t>(evse_id))->get_transaction();

if (transaction->id_token.has_value()) {
// if transactions id_token is already set, it is assumed it has already been reported
return;
}

// set id_token of transaction and send TransactionEvent(Updated) with id_token
transaction->id_token = id_token;
this->transaction_event_req(TransactionEventEnum::Updated, ocpp::DateTime(), transaction->get_transaction(),
TriggerReasonEnum::Authorized, transaction->get_seq_no(), std::nullopt, std::nullopt,
id_token, std::nullopt, std::nullopt, this->is_offline(), std::nullopt);
}

void ChargePoint::on_meter_value(const int32_t evse_id, const MeterValue& meter_value) {
if (evse_id == 0) {
// if evseId = 0 then store in the chargepoint metervalues
Expand Down Expand Up @@ -519,7 +543,8 @@ void ChargePoint::on_reserved(const int32_t evse_id, const int32_t connector_id)
this->evses.at(evse_id)->submit_event(connector_id, ConnectorEvent::Reserve);
}

bool ChargePoint::on_charging_state_changed(const uint32_t evse_id, ChargingStateEnum charging_state) {
bool ChargePoint::on_charging_state_changed(const uint32_t evse_id, const ChargingStateEnum charging_state,
const TriggerReasonEnum trigger_reason) {
if (this->evses.find(static_cast<int32_t>(evse_id)) != this->evses.end()) {
std::unique_ptr<EnhancedTransaction>& transaction =
this->evses.at(static_cast<int32_t>(evse_id))->get_transaction();
Expand All @@ -529,8 +554,7 @@ bool ChargePoint::on_charging_state_changed(const uint32_t evse_id, ChargingStat
} else {
transaction->chargingState = charging_state;
this->transaction_event_req(TransactionEventEnum::Updated, DateTime(), transaction->get_transaction(),
TriggerReasonEnum::ChargingStateChanged, transaction->get_seq_no(),
std::nullopt,
trigger_reason, transaction->get_seq_no(), std::nullopt,
this->evses.at(static_cast<int32_t>(evse_id))->get_evse_info(),
std::nullopt, std::nullopt, std::nullopt, this->is_offline(), std::nullopt);
}
Expand Down Expand Up @@ -1080,7 +1104,7 @@ void ChargePoint::handle_message(const EnhancedMessage<v201::MessageType>& messa
this->handle_change_availability_req(json_message);
break;
case MessageType::TransactionEventResponse:
this->handle_start_transaction_event_response(message);
this->handle_transaction_event_response(message);
break;
case MessageType::RequestStartTransaction:
this->handle_remote_start_transaction_request(json_message);
Expand Down Expand Up @@ -1919,12 +1943,6 @@ void ChargePoint::transaction_event_req(const TransactionEventEnum& event_type,
remote_start_id_per_evse.erase(it);
}

if (event_type == TransactionEventEnum::Started and (!evse.has_value() or !id_token.has_value())) {
EVLOG_error << "Request to send TransactionEvent(Started) without given evse or id_token. These properties "
"are required for this eventType \"Started\"!";
return;
}

this->send<TransactionEventRequest>(call);

if (this->callbacks.transaction_event_callback.has_value()) {
Expand Down Expand Up @@ -2441,53 +2459,74 @@ void ChargePoint::handle_clear_cache_req(Call<ClearCacheRequest> call) {
this->send<ClearCacheResponse>(call_result);
}

void ChargePoint::handle_start_transaction_event_response(const EnhancedMessage<v201::MessageType>& message) {
void ChargePoint::handle_transaction_event_response(const EnhancedMessage<v201::MessageType>& message) {
CallResult<TransactionEventResponse> call_result = message.message;
const Call<TransactionEventRequest>& original_call = message.call_message;
const auto& original_msg = original_call.msg;

if (original_msg.eventType != TransactionEventEnum::Started) {
if (original_msg.eventType == TransactionEventEnum::Ended) {
// nothing to do for TransactionEventEnum::Ended
return;
}

if (!original_msg.evse.has_value()) {
EVLOG_error << "Start transaction event sent without without evse id";
const auto msg = call_result.msg;

if (!msg.idTokenInfo.has_value()) {
// nothing to do when the response does not contain idTokenInfo
return;
}

if (!original_msg.idToken.has_value()) {
EVLOG_error << "Start transaction event sent without without idToken info";
EVLOG_error
<< "TransactionEvent.conf contains idTokenInfo when no idToken was part of the TransactionEvent.req";
return;
}

const int32_t evse_id = original_msg.evse.value().id;
const IdToken& id_token = original_msg.idToken.value();

const auto msg = call_result.msg;
if (msg.idTokenInfo.has_value()) {
// C03.FR.0x and C05.FR.01: We SHALL NOT store central information in the Authorization Cache
// C10.FR.05
if (id_token.type != IdTokenEnum::Central and
this->device_model->get_optional_value<bool>(ControllerComponentVariables::AuthCacheCtrlrEnabled)
.value_or(true)) {
auto id_token_info = msg.idTokenInfo.value();
this->update_id_token_cache_lifetime(id_token_info);
this->database_handler->authorization_cache_insert_entry(utils::generate_token_hash(id_token),
id_token_info);
this->update_authorization_cache_size();
// C03.FR.0x and C05.FR.01: We SHALL NOT store central information in the Authorization Cache
// C10.FR.05
if (id_token.type != IdTokenEnum::Central and
this->device_model->get_optional_value<bool>(ControllerComponentVariables::AuthCacheCtrlrEnabled)
.value_or(true)) {
auto id_token_info = msg.idTokenInfo.value();
this->update_id_token_cache_lifetime(id_token_info);
this->database_handler->authorization_cache_insert_entry(utils::generate_token_hash(id_token), id_token_info);
this->update_authorization_cache_size();
}

if (msg.idTokenInfo.value().status == AuthorizationStatusEnum::Accepted) {
// nothing to do in case status is accepted
return;
}

std::vector<int32_t> evse_ids; // this is a vector because the id token could be active for multiple transactions
if (original_msg.evse.has_value()) {
evse_ids.push_back(original_msg.evse.value().id);
} else {
// add every evse_id with active transaction and given token
for (const auto& [evse_id, evse] : this->evses) {
const auto& transaction = evse->get_transaction();
if (transaction != nullptr and transaction->id_token.has_value()) {
if (transaction->id_token.value().idToken.get() == id_token.idToken.get()) {
evse_ids.push_back(evse_id);
}
}
}
if (msg.idTokenInfo.value().status != AuthorizationStatusEnum::Accepted) {
if (this->device_model->get_value<bool>(ControllerComponentVariables::StopTxOnInvalidId)) {
this->callbacks.stop_transaction_callback(evse_id, ReasonEnum::DeAuthorized);
}

// post handling of transactions in case status is not Accepted
for (const auto evse_id : evse_ids) {
if (this->device_model->get_value<bool>(ControllerComponentVariables::StopTxOnInvalidId)) {
this->callbacks.stop_transaction_callback(evse_id, ReasonEnum::DeAuthorized);
} else {
if (this->device_model->get_optional_value<int32_t>(ControllerComponentVariables::MaxEnergyOnInvalidId)
.has_value()) {
// Energy delivery to the EV SHALL be allowed until the amount of energy specified in
// MaxEnergyOnInvalidId has been reached.
this->evses.at(evse_id)->start_checking_max_energy_on_invalid_id();
} else {
if (this->device_model->get_optional_value<int32_t>(ControllerComponentVariables::MaxEnergyOnInvalidId)
.has_value()) {
// Energy delivery to the EV SHALL be allowed until the amount of energy specified in
// MaxEnergyOnInvalidId has been reached.
this->evses.at(evse_id)->start_checking_max_energy_on_invalid_id();
} else {
this->callbacks.pause_charging_callback(evse_id);
}
this->callbacks.pause_charging_callback(evse_id);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion lib/ocpp/v201/evse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ uint32_t Evse::get_number_of_connectors() {
}

void Evse::open_transaction(const std::string& transaction_id, const int32_t connector_id, const DateTime& timestamp,
const MeterValue& meter_start, const IdToken& id_token,
const MeterValue& meter_start, const std::optional<IdToken>& id_token,
const std::optional<IdToken>& group_id_token, const std::optional<int32_t> reservation_id,
const std::chrono::seconds sampled_data_tx_updated_interval,
const std::chrono::seconds sampled_data_tx_ended_interval,
Expand Down

0 comments on commit 29e50d9

Please sign in to comment.