From 99a88e3435757d31633413eae368a8fe8f0a312d Mon Sep 17 00:00:00 2001 From: Gianfranco Berardi <54074967+gberardi-pillar@users.noreply.github.com> Date: Fri, 5 Jul 2024 15:38:06 -0500 Subject: [PATCH 01/16] Use dependency injection for ChargePoint. Signed-off-by: Gianfranco Berardi <54074967+gberardi-pillar@users.noreply.github.com> --- include/ocpp/v201/charge_point.hpp | 23 ++++- lib/ocpp/v201/charge_point.cpp | 108 ++++++++++++++++++++++ tests/lib/ocpp/v201/test_charge_point.cpp | 91 +++++++++++++++++- 3 files changed, 220 insertions(+), 2 deletions(-) diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index dbbb29ad9..5aa500f24 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -422,7 +422,7 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa std::unique_ptr evse_manager; // utility - std::unique_ptr> message_queue; + std::shared_ptr> message_queue; std::shared_ptr device_model; std::shared_ptr database_handler; @@ -800,6 +800,11 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa void load_charging_profiles(); public: + /// \brief Construct a new ChargePoint object + /// \param evse_connector_structure Map that defines the structure of EVSE and connectors of the chargepoint. The + /// key represents the id of the EVSE and the value represents the number of connectors for this EVSE. The ids of + /// the EVSEs have to increment starting with 1. + /// \param device_model_storage_address address to device model storage (e.g. location of SQLite database) /// \brief Construct a new ChargePoint object /// \param evse_connector_structure Map that defines the structure of EVSE and connectors of the chargepoint. The /// key represents the id of the EVSE and the value represents the number of connectors for this EVSE. The ids of @@ -857,6 +862,22 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa const std::string& core_database_path, const std::string& sql_init_path, const std::string& message_log_path, const std::shared_ptr evse_security, const Callbacks& callbacks); + + /// \brief Construct a new ChargePoint object + /// \param evse_connector_structure Map that defines the structure of EVSE and connectors of the chargepoint. The + /// key represents the id of the EVSE and the value represents the number of connectors for this EVSE. The ids of + /// the EVSEs have to increment starting with 1. + /// \param device_model_storage device model storage instance + /// \param database_handler database handler instance + /// \param message_queue message queue instance + /// \param message_log_path Path to where logfiles are written to + /// \param evse_security Pointer to evse_security that manages security related operations + /// \param callbacks Callbacks that will be registered for ChargePoint + ChargePoint(const std::map& evse_connector_structure, std::shared_ptr device_model, + std::shared_ptr database_handler, + std::shared_ptr> message_queue, const std::string& message_log_path, + const std::shared_ptr evse_security, const Callbacks& callbacks); + ~ChargePoint(); void start(BootReasonEnum bootreason = BootReasonEnum::PowerUp) override; diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 6383a3e6e..db7bf7eac 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -62,6 +62,114 @@ bool Callbacks::all_callbacks_valid() const { this->transaction_event_response_callback.value() != nullptr); } +ChargePoint::ChargePoint(const std::map& evse_connector_structure, + std::shared_ptr device_model, std::shared_ptr database_handler, + std::shared_ptr> message_queue, + const std::string& message_log_path, const std::shared_ptr evse_security, + const Callbacks& callbacks) : + ocpp::ChargingStationBase(evse_security), + message_queue(message_queue), + device_model(device_model), + database_handler(database_handler), + registration_status(RegistrationStatusEnum::Rejected), + network_configuration_priority(0), + disable_automatic_websocket_reconnects(false), + skip_invalid_csms_certificate_notifications(false), + reset_scheduled(false), + reset_scheduled_evseids{}, + firmware_status(FirmwareStatusEnum::Idle), + upload_log_status(UploadLogStatusEnum::Idle), + bootreason(BootReasonEnum::PowerUp), + ocsp_updater(this->evse_security, this->send_callback( + MessageType::GetCertificateStatusResponse)), + monitoring_updater( + device_model, [this](const std::vector& events) { this->notify_event_req(events); }, + [this]() { return this->is_offline(); }), + csr_attempt(1), + client_certificate_expiration_check_timer([this]() { this->scheduled_check_client_certificate_expiration(); }), + v2g_certificate_expiration_check_timer([this]() { this->scheduled_check_v2g_certificate_expiration(); }), + callbacks(callbacks) { + + // Make sure the received callback struct is completely filled early before we actually start running + if (!this->callbacks.all_callbacks_valid()) { + EVLOG_AND_THROW(std::invalid_argument("All non-optional callbacks must be supplied")); + } + + if (!this->device_model) { + EVLOG_AND_THROW(std::invalid_argument("Device model should not be null")); + } + this->device_model->check_integrity(evse_connector_structure); + + if (!this->database_handler) { + EVLOG_AND_THROW(std::invalid_argument("Database handler should not be null")); + } + this->database_handler->open_connection(); + + // Component state manager - needs evse_connector_structure, database_handler, + // send_connector_status_notification_callback Setup callbacks using compomnent state manager + + this->component_state_manager = std::make_shared( + evse_connector_structure, database_handler, + [this](auto evse_id, auto connector_id, auto status, bool initiated_by_trigger_message) { + this->update_dm_availability_state(evse_id, connector_id, status); + if (this->websocket == nullptr || !this->websocket->is_connected() || + this->registration_status != RegistrationStatusEnum::Accepted) { + return false; + } else { + this->status_notification_req(evse_id, connector_id, status, initiated_by_trigger_message); + return true; + } + }); + if (this->callbacks.cs_effective_operative_status_changed_callback.has_value()) { + this->component_state_manager->set_cs_effective_availability_changed_callback( + this->callbacks.cs_effective_operative_status_changed_callback.value()); + } + if (this->callbacks.evse_effective_operative_status_changed_callback.has_value()) { + this->component_state_manager->set_evse_effective_availability_changed_callback( + this->callbacks.evse_effective_operative_status_changed_callback.value()); + } + this->component_state_manager->set_connector_effective_availability_changed_callback( + this->callbacks.connector_effective_operative_status_changed_callback); + + auto transaction_meter_value_callback = [this](const MeterValue& _meter_value, EnhancedTransaction& transaction) { + if (_meter_value.sampledValue.empty() or !_meter_value.sampledValue.at(0).context.has_value()) { + EVLOG_info << "Not sending MeterValue due to no values"; + return; + } + + auto type = _meter_value.sampledValue.at(0).context.value(); + if (type != ReadingContextEnum::Sample_Clock and type != ReadingContextEnum::Sample_Periodic) { + EVLOG_info << "Not sending MeterValue due to wrong context"; + return; + } + + const auto filter_vec = utils::get_measurands_vec(this->device_model->get_value( + type == ReadingContextEnum::Sample_Clock ? ControllerComponentVariables::AlignedDataMeasurands + : ControllerComponentVariables::SampledDataTxUpdatedMeasurands)); + + const auto filtered_meter_value = utils::get_meter_value_with_measurands_applied(_meter_value, filter_vec); + + if (!filtered_meter_value.sampledValue.empty()) { + const auto trigger = type == ReadingContextEnum::Sample_Clock ? TriggerReasonEnum::MeterValueClock + : TriggerReasonEnum::MeterValuePeriodic; + this->transaction_event_req(TransactionEventEnum::Updated, DateTime(), transaction, trigger, + transaction.get_seq_no(), std::nullopt, std::nullopt, std::nullopt, + std::vector(1, filtered_meter_value), std::nullopt, + this->is_offline(), std::nullopt); + } + }; + + // Setup EvseManager - needs evse_connector_structure, device_model, database_handler, component_state_manager, + // callbacks + this->evse_manager = std::make_unique( + evse_connector_structure, *this->device_model, this->database_handler, component_state_manager, + transaction_meter_value_callback, this->callbacks.pause_charging_callback); + + this->configure_message_logging_format(message_log_path); + + this->auth_cache_cleanup_thread = std::thread(&ChargePoint::cache_cleanup_handler, this); +} + ChargePoint::ChargePoint(const std::map& evse_connector_structure, const std::string& device_model_storage_address, const bool initialize_device_model, const std::string& device_model_migration_path, const std::string& device_model_config_path, diff --git a/tests/lib/ocpp/v201/test_charge_point.cpp b/tests/lib/ocpp/v201/test_charge_point.cpp index 5c82b6d5c..423320264 100644 --- a/tests/lib/ocpp/v201/test_charge_point.cpp +++ b/tests/lib/ocpp/v201/test_charge_point.cpp @@ -60,6 +60,13 @@ class ChargePointFixture : public DatabaseTestingUtils { charge_point->stop(); } + std::map create_evse_connector_structure() { + std::map evse_connector_structure; + evse_connector_structure.insert_or_assign(1, 1); + evse_connector_structure.insert_or_assign(2, 1); + return evse_connector_structure; + } + void create_device_model_db(const std::string& path) { InitDeviceModelDb db(path, MIGRATION_FILES_PATH); db.initialize_database(SCHEMAS_PATH, true); @@ -70,7 +77,6 @@ class ChargePointFixture : public DatabaseTestingUtils { create_device_model_db(DEVICE_MODEL_DB_IN_MEMORY_PATH); auto device_model_storage = std::make_unique(DEVICE_MODEL_DB_IN_MEMORY_PATH); auto device_model = std::make_shared(std::move(device_model_storage)); - // Defaults const auto& charging_rate_unit_cv = ControllerComponentVariables::ChargingScheduleChargingRateUnit; device_model->set_value(charging_rate_unit_cv.component, charging_rate_unit_cv.variable.value(), @@ -173,6 +179,27 @@ class ChargePointFixture : public DatabaseTestingUtils { std::unique_ptr charge_point = create_charge_point(); boost::uuids::random_generator uuid_generator = boost::uuids::random_generator(); + std::shared_ptr create_database_handler() { + auto database_connection = std::make_unique(fs::path("/tmp/ocpp201") / "cp.db"); + return std::make_shared(std::move(database_connection), MIGRATION_FILES_LOCATION_V201); + } + + std::shared_ptr> + create_message_queue(std::shared_ptr& database_handler) { + const auto DEFAULT_MESSAGE_QUEUE_SIZE_THRESHOLD = 2E5; + return std::make_shared>( + [this](json message) -> bool { return false; }, + MessageQueueConfig{ + this->device_model->get_value(ControllerComponentVariables::MessageAttempts), + this->device_model->get_value(ControllerComponentVariables::MessageAttemptInterval), + this->device_model->get_optional_value(ControllerComponentVariables::MessageQueueSizeThreshold) + .value_or(DEFAULT_MESSAGE_QUEUE_SIZE_THRESHOLD), + this->device_model->get_optional_value(ControllerComponentVariables::QueueAllMessages) + .value_or(false), + this->device_model->get_value(ControllerComponentVariables::MessageTimeout)}, + database_handler); + } + void configure_callbacks_with_mocks() { callbacks.is_reset_allowed_callback = is_reset_allowed_callback_mock.AsStdFunction(); callbacks.reset_callback = reset_callback_mock.AsStdFunction(); @@ -259,6 +286,68 @@ class ChargePointFixture : public DatabaseTestingUtils { * is provided. */ +TEST_F(ChargePointFixture, CreateChargePoint) { + auto evse_connector_structure = create_evse_connector_structure(); + auto database_handler = create_database_handler(); + auto evse_security = std::make_shared(); + configure_callbacks_with_mocks(); + auto message_queue = create_message_queue(database_handler); + + EXPECT_NO_THROW(ocpp::v201::ChargePoint(evse_connector_structure, device_model, database_handler, message_queue, + "/tmp", evse_security, callbacks)); +} + +TEST_F(ChargePointFixture, CreateChargePoint_EVSEConnectorStructureDefinedBadly_ThrowsDeviceModelStorageError) { + auto database_handler = create_database_handler(); + auto evse_security = std::make_shared(); + configure_callbacks_with_mocks(); + auto message_queue = create_message_queue(database_handler); + + auto evse_connector_structure = std::map(); + + EXPECT_THROW(ocpp::v201::ChargePoint(evse_connector_structure, device_model, database_handler, message_queue, + "/tmp", evse_security, callbacks), + DeviceModelStorageError); +} + +TEST_F(ChargePointFixture, CreateChargePoint_MissingDeviceModel_ThrowsInvalidArgument) { + auto evse_connector_structure = create_evse_connector_structure(); + auto database_handler = create_database_handler(); + auto evse_security = std::make_shared(); + configure_callbacks_with_mocks(); + auto message_queue = std::make_shared>( + [this](json message) -> bool { return false; }, MessageQueueConfig{}, database_handler); + + EXPECT_THROW(ocpp::v201::ChargePoint(evse_connector_structure, nullptr, database_handler, message_queue, "/tmp", + evse_security, callbacks), + std::invalid_argument); +} + +TEST_F(ChargePointFixture, CreateChargePoint_MissingDatabaseHandler_ThrowsInvalidArgument) { + auto evse_connector_structure = create_evse_connector_structure(); + auto evse_security = std::make_shared(); + configure_callbacks_with_mocks(); + auto message_queue = std::make_shared>( + [this](json message) -> bool { return false; }, MessageQueueConfig{}, nullptr); + + auto database_handler = nullptr; + + EXPECT_THROW(ocpp::v201::ChargePoint(evse_connector_structure, device_model, database_handler, message_queue, + "/tmp", evse_security, callbacks), + std::invalid_argument); +} + +TEST_F(ChargePointFixture, CreateChargePoint_CallbacksNotValid_ThrowsInvalidArgument) { + auto evse_connector_structure = create_evse_connector_structure(); + auto database_handler = create_database_handler(); + auto evse_security = std::make_shared(); + auto message_queue = create_message_queue(database_handler); + + EXPECT_THROW(ocpp::v201::ChargePoint(evse_connector_structure, device_model, database_handler, message_queue, + "/tmp", evse_security, callbacks), + std::invalid_argument); +} + TEST_F(ChargePointFixture, K01FR02_CallbacksValidityChecksIfSetChargingProfilesCallbackExists) { configure_callbacks_with_mocks(); callbacks.set_charging_profiles_callback = nullptr; From 809c458bc1bbe460d6cbdbf44f961b91353cf413 Mon Sep 17 00:00:00 2001 From: Christoph <367712+folkengine@users.noreply.github.com> Date: Thu, 8 Aug 2024 10:40:01 -0700 Subject: [PATCH 02/16] Added check for SmartChargingCtrlrAvailable to ChargePoint::handle_set_charging_profile_req Signed-off-by: Christoph <367712+folkengine@users.noreply.github.com> Signed-off-by: Gianfranco Berardi <54074967+gberardi-pillar@users.noreply.github.com> --- lib/ocpp/v201/charge_point.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index db7bf7eac..b57d032de 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -3341,6 +3341,21 @@ void ChargePoint::handle_set_charging_profile_req(Calldevice_model->get_optional_value(ControllerComponentVariables::SmartChargingCtrlrAvailable) + .value_or(false); + + if (!is_charging_station_enable) { + EVLOG_warning << "SmartChargingCtrlrAvailable is not set for Charging Station. Returning NotSupported error"; + + const auto call_error = + CallError(call.uniqueId, "NotSupported", "Charging Station does not support smart charging", json({})); + this->send(call_error); + + return; + } + // K01.FR.22: Reject ChargingStationExternalConstraints profiles in SetChargingProfileRequest if (msg.chargingProfile.chargingProfilePurpose == ChargingProfilePurposeEnum::ChargingStationExternalConstraints) { response.statusInfo = StatusInfo(); From 8c99d321e945215cb39336748ed232ca7070269e Mon Sep 17 00:00:00 2001 From: Christoph <367712+folkengine@users.noreply.github.com> Date: Thu, 8 Aug 2024 12:34:44 -0700 Subject: [PATCH 03/16] Added test DISABLED_K01FR29_SmartChargingCtrlrAvailableIsFalse_RespondsCallError Signed-off-by: Christoph <367712+folkengine@users.noreply.github.com> Signed-off-by: Gianfranco Berardi <54074967+gberardi-pillar@users.noreply.github.com> --- tests/lib/ocpp/v201/test_charge_point.cpp | 45 +++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/lib/ocpp/v201/test_charge_point.cpp b/tests/lib/ocpp/v201/test_charge_point.cpp index 423320264..242e76324 100644 --- a/tests/lib/ocpp/v201/test_charge_point.cpp +++ b/tests/lib/ocpp/v201/test_charge_point.cpp @@ -709,4 +709,49 @@ TEST_F(ChargePointFixture, K01FR22_SetChargingProfileRequest_RejectsChargingStat charge_point->handle_message(set_charging_profile_req); } +/// This test ensures that when Smart Charging isn't available it doesn't process the submitted profile. +/// +/// This test is disabled because it only passes if the ControllerComponentVariables::SmartChargingCtrlrAvailable +/// entry is removed from the config/v201/config.json file. +TEST_F(ChargePointFixture, DISABLED_K01FR29_SmartChargingCtrlrAvailableIsFalse_RespondsCallError) { + auto evse_connector_structure = create_evse_connector_structure(); + auto database_handler = create_database_handler(); + auto evse_security = std::make_shared(); + configure_callbacks_with_mocks(); + + const auto DEFAULT_MESSAGE_QUEUE_SIZE_THRESHOLD = 2E5; + std::shared_ptr> message_queue = + std::make_shared>( + [this](json message) -> bool { return false; }, + MessageQueueConfig{ + this->device_model->get_value(ControllerComponentVariables::MessageAttempts), + this->device_model->get_value(ControllerComponentVariables::MessageAttemptInterval), + this->device_model->get_optional_value(ControllerComponentVariables::MessageQueueSizeThreshold) + .value_or(DEFAULT_MESSAGE_QUEUE_SIZE_THRESHOLD), + this->device_model->get_optional_value(ControllerComponentVariables::QueueAllMessages) + .value_or(false), + this->device_model->get_value(ControllerComponentVariables::MessageTimeout)}, + database_handler); + + ocpp::v201::ChargePoint chargePoint(evse_connector_structure, device_model, database_handler, message_queue, "/tmp", + evse_security, callbacks); + + auto periods = create_charging_schedule_periods({0, 1, 2}); + + auto profile = create_charging_profile( + DEFAULT_PROFILE_ID, ChargingProfilePurposeEnum::TxProfile, + create_charge_schedule(ChargingRateUnitEnum::A, periods, ocpp::DateTime("2024-01-17T17:00:00")), DEFAULT_TX_ID); + + SetChargingProfileRequest req; + req.evseId = DEFAULT_EVSE_ID; + req.chargingProfile = profile; + + auto set_charging_profile_req = + request_to_enhanced_message(req); + + EXPECT_CALL(*smart_charging_handler, validate_profile(testing::_, testing::_)).Times(0); + + charge_point->handle_message(set_charging_profile_req); +} + } // namespace ocpp::v201 From c88f3fda740c664d40d8a98d9c9b029fcfd72d1c Mon Sep 17 00:00:00 2001 From: Christoph <367712+folkengine@users.noreply.github.com> Date: Fri, 9 Aug 2024 05:51:25 -0700 Subject: [PATCH 04/16] Added update to status doc Signed-off-by: Christoph <367712+folkengine@users.noreply.github.com> Signed-off-by: Gianfranco Berardi <54074967+gberardi-pillar@users.noreply.github.com> --- doc/ocpp_201_status.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ocpp_201_status.md b/doc/ocpp_201_status.md index 28d30a514..4d3c52048 100644 --- a/doc/ocpp_201_status.md +++ b/doc/ocpp_201_status.md @@ -1254,7 +1254,7 @@ This document contains the status of which OCPP 2.0.1 numbered functional requir | K01.FR.26 | ✅ | | | K01.FR.27 | ✅ | | | K01.FR.28 | ✅ | | -| K01.FR.29 | | | +| K01.FR.29 | ✅ | | | K01.FR.30 | | | | K01.FR.31 | | | | K01.FR.32 | ✅ | | From 342874d1543e06210749c4894ab7bac54d0ed83f Mon Sep 17 00:00:00 2001 From: Christoph <367712+folkengine@users.noreply.github.com> Date: Mon, 12 Aug 2024 09:13:20 -0700 Subject: [PATCH 05/16] Updated Component to SmartChargingCtrlrAvailableEnabled as per PR Review. Updated config to true Signed-off-by: Christoph <367712+folkengine@users.noreply.github.com> Signed-off-by: Gianfranco Berardi <54074967+gberardi-pillar@users.noreply.github.com> --- lib/ocpp/v201/charge_point.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index b57d032de..ccbd4422e 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -3343,7 +3343,7 @@ void ChargePoint::handle_set_charging_profile_req(Calldevice_model->get_optional_value(ControllerComponentVariables::SmartChargingCtrlrAvailable) + this->device_model->get_optional_value(ControllerComponentVariables::SmartChargingCtrlrAvailableEnabled) .value_or(false); if (!is_charging_station_enable) { From 00b5a139d0c8ab23a33c7ebbc8126094448f6ca4 Mon Sep 17 00:00:00 2001 From: Coury Richards <146002925+couryrr-afs@users.noreply.github.com> Date: Tue, 23 Jul 2024 20:09:31 +0000 Subject: [PATCH 06/16] updated status doc to represent all completed and planned K01 function requirements updated some comment and test names after community discussion. Signed-off-by: Coury Richards <146002925+couryrr-afs@users.noreply.github.com> Signed-off-by: Gianfranco Berardi <54074967+gberardi-pillar@users.noreply.github.com> --- doc/ocpp_201_status.md | 56 +++++++++---------- lib/ocpp/v201/smart_charging.cpp | 4 +- .../ocpp/v201/test_smart_charging_handler.cpp | 10 ++-- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/doc/ocpp_201_status.md b/doc/ocpp_201_status.md index 4d3c52048..b307f73ce 100644 --- a/doc/ocpp_201_status.md +++ b/doc/ocpp_201_status.md @@ -12,7 +12,6 @@ This document contains the status of which OCPP 2.0.1 numbered functional requir | ❓ | Actor responsible for or status of requirement is unknown | | 🤓 | Catch-all for FRs that are satisfied for other reasons (see the Remark column) | - ## General - General | ID | Status | Remark | @@ -1225,7 +1224,6 @@ This document contains the status of which OCPP 2.0.1 numbered functional requir |-----------|--------|--------| | J03.FR.04 | | | - ## SmartCharging - SetChargingProfile | ID | Status | Remark | @@ -1233,50 +1231,50 @@ This document contains the status of which OCPP 2.0.1 numbered functional requir | K01.FR.01 | 🌐 | `TxProfile`s are supported. | | K01.FR.02 | 🌐 | | | K01.FR.03 | 🌐 💂 | `TxProfile`s without `transactionId`s are rejected. | -| K01.FR.04 | ✅ | | +| K01.FR.04 | 🌐 | | | K01.FR.05 | ✅ | | -| K01.FR.06 | 🌐 | | -| K01.FR.07 | ⛽️ | Notified through the `signal_set_charging_profiles` callback. | +| K01.FR.06 | ✅ | | +| K01.FR.07 | ⛽️K08 | Notified through the `signal_set_charging_profiles` callback. | | K01.FR.08 | 🌐 | `TxDefaultProfile`s are supported. | | K01.FR.09 | ✅ | | -| K01.FR.10 | ✅ | | -| K01.FR.11 | | | -| K01.FR.12 | | | -| K01.FR.13 | | | +| K01.FR.10 | ⛽️K08 | During validation `validFrom` and `validTo` are sets if they are blank to support this | +| K01.FR.11 | ⛽️K08 | | +| K01.FR.12 | ⛽️K08 | | +| K01.FR.13 | ⛽️K08 | | | K01.FR.14 | ✅ | | | K01.FR.15 | ✅ | | | K01.FR.16 | ✅ | | -| K01.FR.17 | | | -| K01.FR.19 | | | +| K01.FR.17 | ⛽️K08 | | +| K01.FR.19 | ✅ | | | K01.FR.20 | ✅ | Suggests `ACPhaseSwitchingSupported` should be per EVSE, conflicting with the rest of the spec. | -| K01.FR.21 | | | +| K01.FR.21 | | There is an active community discussion on this topic. | | K01.FR.22 | | | | K01.FR.26 | ✅ | | | K01.FR.27 | ✅ | | | K01.FR.28 | ✅ | | | K01.FR.29 | ✅ | | -| K01.FR.30 | | | -| K01.FR.31 | | | -| K01.FR.32 | ✅ | | +| K01.FR.30 | ⛽️K08 | | +| K01.FR.31 | ✅ | | +| K01.FR.32 | ⛽️K08 | | | K01.FR.33 | ✅ | | -| K01.FR.34 | ✅ | | +| K01.FR.34 | | Defer to K15 - K17 work | | K01.FR.35 | ✅ | | -| K01.FR.36 | ✅ | | -| K01.FR.37 | | | -| K01.FR.38 | 🌐 💂 | `ChargingStationMaxProfile`s with `Relative` for `chargingProfileKind` are rejected. | -| K01.FR.39 | 🌐 💂 | New `TxProfile`s matching existing `(stackLevel, transactionId)` are rejected. | -| K01.FR.40 | 🌐 💂 | `Absolute`/`Recurring` profiles without `startSchedule` fields are rejected. | -| K01.FR.41 | 🌐 💂 | `Relative` profiles with `startSchedule` fields are rejected. | -| K01.FR.42 | | | -| K01.FR.43 | | | +| K01.FR.36 | ⛽️K08 | | +| K01.FR.37 | ⛽️K08 | | +| K01.FR.38 | ✅ | `ChargingStationMaxProfile`s with `Relative` for `chargingProfileKind` are rejected. | +| K01.FR.39 | ✅ | New `TxProfile`s matching existing `(stackLevel, transactionId)` are rejected. | +| K01.FR.40 | ✅ | `Absolute`/`Recurring` profiles without `startSchedule` fields are rejected. | +| K01.FR.41 | ✅ | `Relative` profiles with `startSchedule` fields are rejected. | +| K01.FR.42 | ⛽️K08 | | +| K01.FR.43 | | Open question to OCA | | K01.FR.44 | ✅ | We reject invalid profiles instead of modifying and accepting them. | | K01.FR.45 | ✅ | We reject invalid profiles instead of modifying and accepting them. | -| K01.FR.46 | | | -| K01.FR.47 | | | -| K01.FR.48 | | | +| K01.FR.46 | ⛽️K08 | | +| K01.FR.47 | ⛽️K08 | | +| K01.FR.48 | ✅ | | | K01.FR.49 | ✅ | | -| K01.FR.50 | | | -| K01.FR.51 | | | +| K01.FR.50 | ⛽️K08 | | +| K01.FR.51 | ⛽️K08 | | | K01.FR.52 | ✅ | | | K01.FR.53 | ✅ | | diff --git a/lib/ocpp/v201/smart_charging.cpp b/lib/ocpp/v201/smart_charging.cpp index e05406741..6271ea120 100644 --- a/lib/ocpp/v201/smart_charging.cpp +++ b/lib/ocpp/v201/smart_charging.cpp @@ -391,12 +391,12 @@ SmartChargingHandler::validate_profile_schedules(ChargingProfile& profile, for (auto i = 0; i < schedule.chargingSchedulePeriod.size(); i++) { auto& charging_schedule_period = schedule.chargingSchedulePeriod[i]; - // K01.FR.19 + // K01.FR.48 and K01.FR.19 if (charging_schedule_period.numberPhases != 1 && charging_schedule_period.phaseToUse.has_value()) { return ProfileValidationResultEnum::ChargingSchedulePeriodInvalidPhaseToUse; } - // K01.FR.20 + // K01.FR.48 and K01.FR.20 if (charging_schedule_period.phaseToUse.has_value() && !device_model->get_optional_value(ControllerComponentVariables::ACPhaseSwitchingSupported) .value_or(false)) { diff --git a/tests/lib/ocpp/v201/test_smart_charging_handler.cpp b/tests/lib/ocpp/v201/test_smart_charging_handler.cpp index 5dcf30ced..5bc280b9a 100644 --- a/tests/lib/ocpp/v201/test_smart_charging_handler.cpp +++ b/tests/lib/ocpp/v201/test_smart_charging_handler.cpp @@ -308,7 +308,7 @@ TEST_F(ChargepointTestFixtureV201, K01FR09_IfTxProfileEvseHasNoActiveTransaction EXPECT_THAT(sut, testing::Eq(ProfileValidationResultEnum::TxProfileEvseHasNoActiveTransaction)); } -TEST_F(ChargepointTestFixtureV201, K01FR19_NumberPhasesOtherThan1AndPhaseToUseSet_ThenProfileInvalid) { +TEST_F(ChargepointTestFixtureV201, K01FR48FR19_NumberPhasesOtherThan1AndPhaseToUseSet_ThenProfileInvalid) { auto periods = create_charging_schedule_periods_with_phases(0, 0, 1); auto profile = create_charging_profile( DEFAULT_PROFILE_ID, ChargingProfilePurposeEnum::TxProfile, @@ -320,7 +320,8 @@ TEST_F(ChargepointTestFixtureV201, K01FR19_NumberPhasesOtherThan1AndPhaseToUseSe EXPECT_THAT(sut, testing::Eq(ProfileValidationResultEnum::ChargingSchedulePeriodInvalidPhaseToUse)); } -TEST_F(ChargepointTestFixtureV201, K01FR20_IfPhaseToUseSetAndACPhaseSwitchingSupportedUndefined_ThenProfileIsInvalid) { +TEST_F(ChargepointTestFixtureV201, + K01FR48FR20_IfPhaseToUseSetAndACPhaseSwitchingSupportedUndefined_ThenProfileIsInvalid) { // As a device model with ac switching supported default set to 'true', we want to create a new database with the // ac switching support not set. But this is an in memory database, which is kept open until all handles to it are // closed. So we close all connections to the database. @@ -344,7 +345,7 @@ TEST_F(ChargepointTestFixtureV201, K01FR20_IfPhaseToUseSetAndACPhaseSwitchingSup testing::Eq(ProfileValidationResultEnum::ChargingSchedulePeriodPhaseToUseACPhaseSwitchingUnsupported)); } -TEST_F(ChargepointTestFixtureV201, K01FR20_IfPhaseToUseSetAndACPhaseSwitchingSupportedFalse_ThenProfileIsInvalid) { +TEST_F(ChargepointTestFixtureV201, K01FR48FR20_IfPhaseToUseSetAndACPhaseSwitchingSupportedFalse_ThenProfileIsInvalid) { // As a device model with ac switching supported default set to 'true', we want to create a new database with the // ac switching support not set. But this is an in memory database, which is kept open until all handles to it are // closed. So we close all connections to the database. @@ -368,7 +369,8 @@ TEST_F(ChargepointTestFixtureV201, K01FR20_IfPhaseToUseSetAndACPhaseSwitchingSup testing::Eq(ProfileValidationResultEnum::ChargingSchedulePeriodPhaseToUseACPhaseSwitchingUnsupported)); } -TEST_F(ChargepointTestFixtureV201, K01FR20_IfPhaseToUseSetAndACPhaseSwitchingSupportedTrue_ThenProfileIsNotInvalid) { +TEST_F(ChargepointTestFixtureV201, + K01FR48FR20_IfPhaseToUseSetAndACPhaseSwitchingSupportedTrue_ThenProfileIsNotInvalid) { auto periods = create_charging_schedule_periods_with_phases(0, 1, 1); auto profile = create_charging_profile( DEFAULT_PROFILE_ID, ChargingProfilePurposeEnum::TxProfile, From 2aec751e66f2df359d9276a08d6dc190855aa90a Mon Sep 17 00:00:00 2001 From: Gianfranco Berardi <54074967+gberardi-pillar@users.noreply.github.com> Date: Fri, 16 Aug 2024 14:43:24 -0500 Subject: [PATCH 07/16] Refactor so that ChargePoint constructors share logic and remove duplication. Signed-off-by: Gianfranco Berardi <54074967+gberardi-pillar@users.noreply.github.com> --- include/ocpp/v201/charge_point.hpp | 1 + lib/ocpp/v201/charge_point.cpp | 203 ++++++++++------------------- 2 files changed, 70 insertions(+), 134 deletions(-) diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index 5aa500f24..f9968ac16 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -507,6 +507,7 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa bool send(CallError call_error); // internal helper functions + void initialize(const std::map& evse_connector_structure, const std::string& message_log_path); void init_websocket(); WebsocketConnectionOptions get_ws_connection_options(const int32_t configuration_slot); void init_certificate_expiration_check_timers(); diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index ccbd4422e..fe252d57d 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -98,76 +98,12 @@ ChargePoint::ChargePoint(const std::map& evse_connector_struct if (!this->device_model) { EVLOG_AND_THROW(std::invalid_argument("Device model should not be null")); } - this->device_model->check_integrity(evse_connector_structure); if (!this->database_handler) { EVLOG_AND_THROW(std::invalid_argument("Database handler should not be null")); } - this->database_handler->open_connection(); - - // Component state manager - needs evse_connector_structure, database_handler, - // send_connector_status_notification_callback Setup callbacks using compomnent state manager - - this->component_state_manager = std::make_shared( - evse_connector_structure, database_handler, - [this](auto evse_id, auto connector_id, auto status, bool initiated_by_trigger_message) { - this->update_dm_availability_state(evse_id, connector_id, status); - if (this->websocket == nullptr || !this->websocket->is_connected() || - this->registration_status != RegistrationStatusEnum::Accepted) { - return false; - } else { - this->status_notification_req(evse_id, connector_id, status, initiated_by_trigger_message); - return true; - } - }); - if (this->callbacks.cs_effective_operative_status_changed_callback.has_value()) { - this->component_state_manager->set_cs_effective_availability_changed_callback( - this->callbacks.cs_effective_operative_status_changed_callback.value()); - } - if (this->callbacks.evse_effective_operative_status_changed_callback.has_value()) { - this->component_state_manager->set_evse_effective_availability_changed_callback( - this->callbacks.evse_effective_operative_status_changed_callback.value()); - } - this->component_state_manager->set_connector_effective_availability_changed_callback( - this->callbacks.connector_effective_operative_status_changed_callback); - - auto transaction_meter_value_callback = [this](const MeterValue& _meter_value, EnhancedTransaction& transaction) { - if (_meter_value.sampledValue.empty() or !_meter_value.sampledValue.at(0).context.has_value()) { - EVLOG_info << "Not sending MeterValue due to no values"; - return; - } - - auto type = _meter_value.sampledValue.at(0).context.value(); - if (type != ReadingContextEnum::Sample_Clock and type != ReadingContextEnum::Sample_Periodic) { - EVLOG_info << "Not sending MeterValue due to wrong context"; - return; - } - - const auto filter_vec = utils::get_measurands_vec(this->device_model->get_value( - type == ReadingContextEnum::Sample_Clock ? ControllerComponentVariables::AlignedDataMeasurands - : ControllerComponentVariables::SampledDataTxUpdatedMeasurands)); - const auto filtered_meter_value = utils::get_meter_value_with_measurands_applied(_meter_value, filter_vec); - - if (!filtered_meter_value.sampledValue.empty()) { - const auto trigger = type == ReadingContextEnum::Sample_Clock ? TriggerReasonEnum::MeterValueClock - : TriggerReasonEnum::MeterValuePeriodic; - this->transaction_event_req(TransactionEventEnum::Updated, DateTime(), transaction, trigger, - transaction.get_seq_no(), std::nullopt, std::nullopt, std::nullopt, - std::vector(1, filtered_meter_value), std::nullopt, - this->is_offline(), std::nullopt); - } - }; - - // Setup EvseManager - needs evse_connector_structure, device_model, database_handler, component_state_manager, - // callbacks - this->evse_manager = std::make_unique( - evse_connector_structure, *this->device_model, this->database_handler, component_state_manager, - transaction_meter_value_callback, this->callbacks.pause_charging_callback); - - this->configure_message_logging_format(message_log_path); - - this->auth_cache_cleanup_thread = std::thread(&ChargePoint::cache_cleanup_handler, this); + initialize(evse_connector_structure, message_log_path); } ChargePoint::ChargePoint(const std::map& evse_connector_structure, @@ -222,76 +158,8 @@ ChargePoint::ChargePoint(const std::map& evse_connector_struct EVLOG_AND_THROW(std::invalid_argument("All non-optional callbacks must be supplied")); } - this->device_model->check_integrity(evse_connector_structure); - auto database_connection = std::make_unique(fs::path(core_database_path) / "cp.db"); this->database_handler = std::make_shared(std::move(database_connection), sql_init_path); - this->database_handler->open_connection(); - - // Set up the component state manager - this->component_state_manager = std::make_shared( - evse_connector_structure, database_handler, - [this](auto evse_id, auto connector_id, auto status, bool initiated_by_trigger_message) { - this->update_dm_availability_state(evse_id, connector_id, status); - if (this->websocket == nullptr || !this->websocket->is_connected() || - this->registration_status != RegistrationStatusEnum::Accepted) { - return false; - } else { - this->status_notification_req(evse_id, connector_id, status, initiated_by_trigger_message); - return true; - } - }); - if (this->callbacks.cs_effective_operative_status_changed_callback.has_value()) { - this->component_state_manager->set_cs_effective_availability_changed_callback( - this->callbacks.cs_effective_operative_status_changed_callback.value()); - } - if (this->callbacks.evse_effective_operative_status_changed_callback.has_value()) { - this->component_state_manager->set_evse_effective_availability_changed_callback( - this->callbacks.evse_effective_operative_status_changed_callback.value()); - } - this->component_state_manager->set_connector_effective_availability_changed_callback( - this->callbacks.connector_effective_operative_status_changed_callback); - - auto transaction_meter_value_callback = [this](const MeterValue& _meter_value, EnhancedTransaction& transaction) { - if (_meter_value.sampledValue.empty() or !_meter_value.sampledValue.at(0).context.has_value()) { - EVLOG_info << "Not sending MeterValue due to no values"; - return; - } - - auto type = _meter_value.sampledValue.at(0).context.value(); - if (type != ReadingContextEnum::Sample_Clock and type != ReadingContextEnum::Sample_Periodic) { - EVLOG_info << "Not sending MeterValue due to wrong context"; - return; - } - - const auto filter_vec = utils::get_measurands_vec(this->device_model->get_value( - type == ReadingContextEnum::Sample_Clock ? ControllerComponentVariables::AlignedDataMeasurands - : ControllerComponentVariables::SampledDataTxUpdatedMeasurands)); - - const auto filtered_meter_value = utils::get_meter_value_with_measurands_applied(_meter_value, filter_vec); - - if (!filtered_meter_value.sampledValue.empty()) { - const auto trigger = type == ReadingContextEnum::Sample_Clock ? TriggerReasonEnum::MeterValueClock - : TriggerReasonEnum::MeterValuePeriodic; - this->transaction_event_req(TransactionEventEnum::Updated, DateTime(), transaction.get_transaction(), - trigger, transaction.get_seq_no(), std::nullopt, std::nullopt, std::nullopt, - std::vector(1, filtered_meter_value), std::nullopt, - this->is_offline(), std::nullopt); - } - }; - - this->evse_manager = std::make_unique( - evse_connector_structure, *this->device_model, this->database_handler, component_state_manager, - transaction_meter_value_callback, this->callbacks.pause_charging_callback); - - this->smart_charging_handler = - std::make_shared(*this->evse_manager, this->device_model, this->database_handler); - - // configure logging - this->configure_message_logging_format(message_log_path); - - // start monitoring - this->monitoring_updater.start_monitoring(); this->message_queue = std::make_unique>( [this](json message) -> bool { return this->websocket->send(message.dump()); }, @@ -305,7 +173,7 @@ ChargePoint::ChargePoint(const std::map& evse_connector_struct this->device_model->get_value(ControllerComponentVariables::MessageTimeout)}, this->database_handler); - this->auth_cache_cleanup_thread = std::thread(&ChargePoint::cache_cleanup_handler, this); + initialize(evse_connector_structure, message_log_path); } ChargePoint::~ChargePoint() { @@ -1078,6 +946,73 @@ bool ChargePoint::send(CallError call_error) { return true; } +void ChargePoint::initialize(const std::map& evse_connector_structure, + const std::string& message_log_path) { + this->device_model->check_integrity(evse_connector_structure); + this->database_handler->open_connection(); + this->smart_charging_handler = + std::make_shared(*this->evse_manager, this->device_model, this->database_handler); + this->component_state_manager = std::make_shared( + evse_connector_structure, database_handler, + [this](auto evse_id, auto connector_id, auto status, bool initiated_by_trigger_message) { + this->update_dm_availability_state(evse_id, connector_id, status); + if (this->websocket == nullptr || !this->websocket->is_connected() || + this->registration_status != RegistrationStatusEnum::Accepted) { + return false; + } else { + this->status_notification_req(evse_id, connector_id, status, initiated_by_trigger_message); + return true; + } + }); + if (this->callbacks.cs_effective_operative_status_changed_callback.has_value()) { + this->component_state_manager->set_cs_effective_availability_changed_callback( + this->callbacks.cs_effective_operative_status_changed_callback.value()); + } + if (this->callbacks.evse_effective_operative_status_changed_callback.has_value()) { + this->component_state_manager->set_evse_effective_availability_changed_callback( + this->callbacks.evse_effective_operative_status_changed_callback.value()); + } + this->component_state_manager->set_connector_effective_availability_changed_callback( + this->callbacks.connector_effective_operative_status_changed_callback); + + auto transaction_meter_value_callback = [this](const MeterValue& _meter_value, EnhancedTransaction& transaction) { + if (_meter_value.sampledValue.empty() or !_meter_value.sampledValue.at(0).context.has_value()) { + EVLOG_info << "Not sending MeterValue due to no values"; + return; + } + + auto type = _meter_value.sampledValue.at(0).context.value(); + if (type != ReadingContextEnum::Sample_Clock and type != ReadingContextEnum::Sample_Periodic) { + EVLOG_info << "Not sending MeterValue due to wrong context"; + return; + } + + const auto filter_vec = utils::get_measurands_vec(this->device_model->get_value( + type == ReadingContextEnum::Sample_Clock ? ControllerComponentVariables::AlignedDataMeasurands + : ControllerComponentVariables::SampledDataTxUpdatedMeasurands)); + + const auto filtered_meter_value = utils::get_meter_value_with_measurands_applied(_meter_value, filter_vec); + + if (!filtered_meter_value.sampledValue.empty()) { + const auto trigger = type == ReadingContextEnum::Sample_Clock ? TriggerReasonEnum::MeterValueClock + : TriggerReasonEnum::MeterValuePeriodic; + this->transaction_event_req(TransactionEventEnum::Updated, DateTime(), transaction, trigger, + transaction.get_seq_no(), std::nullopt, std::nullopt, std::nullopt, + std::vector(1, filtered_meter_value), std::nullopt, + this->is_offline(), std::nullopt); + } + }; + + this->evse_manager = std::make_unique( + evse_connector_structure, *this->device_model, this->database_handler, component_state_manager, + transaction_meter_value_callback, this->callbacks.pause_charging_callback); + + this->configure_message_logging_format(message_log_path); + this->monitoring_updater.start_monitoring(); + + this->auth_cache_cleanup_thread = std::thread(&ChargePoint::cache_cleanup_handler, this); +} + void ChargePoint::init_websocket() { if (this->device_model->get_value(ControllerComponentVariables::ChargePointId).find(':') != From 6c0c733bc448679a8f871ce07d4d7f3ca63574c0 Mon Sep 17 00:00:00 2001 From: Gianfranco Berardi <54074967+gberardi-pillar@users.noreply.github.com> Date: Fri, 16 Aug 2024 15:02:56 -0500 Subject: [PATCH 08/16] Remove duplicated comments/documentation. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Piet Gömpel <37657534+Pietfried@users.noreply.github.com> Signed-off-by: Gianfranco Berardi <54074967+gberardi-pillar@users.noreply.github.com> --- include/ocpp/v201/charge_point.hpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index f9968ac16..8077eb814 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -801,11 +801,6 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa void load_charging_profiles(); public: - /// \brief Construct a new ChargePoint object - /// \param evse_connector_structure Map that defines the structure of EVSE and connectors of the chargepoint. The - /// key represents the id of the EVSE and the value represents the number of connectors for this EVSE. The ids of - /// the EVSEs have to increment starting with 1. - /// \param device_model_storage_address address to device model storage (e.g. location of SQLite database) /// \brief Construct a new ChargePoint object /// \param evse_connector_structure Map that defines the structure of EVSE and connectors of the chargepoint. The /// key represents the id of the EVSE and the value represents the number of connectors for this EVSE. The ids of From c265adc76c53c960b19e85fee0d98d6df7de5baf Mon Sep 17 00:00:00 2001 From: Gianfranco Berardi <54074967+gberardi-pillar@users.noreply.github.com> Date: Fri, 16 Aug 2024 15:04:40 -0500 Subject: [PATCH 09/16] Move test comments/documentation above relevant tests. Signed-off-by: Gianfranco Berardi <54074967+gberardi-pillar@users.noreply.github.com> --- tests/lib/ocpp/v201/test_charge_point.cpp | 34 +++++++++++------------ 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/tests/lib/ocpp/v201/test_charge_point.cpp b/tests/lib/ocpp/v201/test_charge_point.cpp index 242e76324..98f3db120 100644 --- a/tests/lib/ocpp/v201/test_charge_point.cpp +++ b/tests/lib/ocpp/v201/test_charge_point.cpp @@ -269,23 +269,6 @@ class ChargePointFixture : public DatabaseTestingUtils { ocpp::v201::Callbacks callbacks; }; -/* - * K01.FR.02 states - * - * "The CSMS MAY send a new charging profile for the EVSE that SHALL be used - * as a limit schedule for the EV." - * - * When using libocpp, a charging station is notified of a new charging profile - * by means of the set_charging_profiles_callback. In order to ensure that a new - * profile can be immediately "used as a limit schedule for the EV", a - * valid set_charging_profiles_callback must be provided. - * - * As part of testing that K01.FR.02 is met, we provide the following tests that - * confirm an OCPP 2.0.1 ChargePoint with smart charging enabled will only - * consider its collection of callbacks valid if set_charging_profiles_callback - * is provided. - */ - TEST_F(ChargePointFixture, CreateChargePoint) { auto evse_connector_structure = create_evse_connector_structure(); auto database_handler = create_database_handler(); @@ -348,6 +331,23 @@ TEST_F(ChargePointFixture, CreateChargePoint_CallbacksNotValid_ThrowsInvalidArgu std::invalid_argument); } +/* + * K01.FR.02 states + * + * "The CSMS MAY send a new charging profile for the EVSE that SHALL be used + * as a limit schedule for the EV." + * + * When using libocpp, a charging station is notified of a new charging profile + * by means of the set_charging_profiles_callback. In order to ensure that a new + * profile can be immediately "used as a limit schedule for the EV", a + * valid set_charging_profiles_callback must be provided. + * + * As part of testing that K01.FR.02 is met, we provide the following tests that + * confirm an OCPP 2.0.1 ChargePoint with smart charging enabled will only + * consider its collection of callbacks valid if set_charging_profiles_callback + * is provided. + */ + TEST_F(ChargePointFixture, K01FR02_CallbacksValidityChecksIfSetChargingProfilesCallbackExists) { configure_callbacks_with_mocks(); callbacks.set_charging_profiles_callback = nullptr; From 1c680856a5961acdf6a58e50f865a70159ca7a79 Mon Sep 17 00:00:00 2001 From: Gianfranco Berardi <54074967+gberardi-pillar@users.noreply.github.com> Date: Fri, 16 Aug 2024 15:34:10 -0500 Subject: [PATCH 10/16] Reenable test; added positive test case. Signed-off-by: Gianfranco Berardi <54074967+gberardi-pillar@users.noreply.github.com> --- lib/ocpp/v201/charge_point.cpp | 6 +-- tests/lib/ocpp/v201/test_charge_point.cpp | 51 +++++++++++++---------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index fe252d57d..de5558750 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -3277,11 +3277,11 @@ void ChargePoint::handle_set_charging_profile_req(Calldevice_model->get_optional_value(ControllerComponentVariables::SmartChargingCtrlrAvailableEnabled) + bool is_smart_charging_available = + this->device_model->get_optional_value(ControllerComponentVariables::SmartChargingCtrlrAvailable) .value_or(false); - if (!is_charging_station_enable) { + if (!is_smart_charging_available) { EVLOG_warning << "SmartChargingCtrlrAvailable is not set for Charging Station. Returning NotSupported error"; const auto call_error = diff --git a/tests/lib/ocpp/v201/test_charge_point.cpp b/tests/lib/ocpp/v201/test_charge_point.cpp index 98f3db120..ebc2775e9 100644 --- a/tests/lib/ocpp/v201/test_charge_point.cpp +++ b/tests/lib/ocpp/v201/test_charge_point.cpp @@ -709,35 +709,42 @@ TEST_F(ChargePointFixture, K01FR22_SetChargingProfileRequest_RejectsChargingStat charge_point->handle_message(set_charging_profile_req); } -/// This test ensures that when Smart Charging isn't available it doesn't process the submitted profile. -/// -/// This test is disabled because it only passes if the ControllerComponentVariables::SmartChargingCtrlrAvailable -/// entry is removed from the config/v201/config.json file. -TEST_F(ChargePointFixture, DISABLED_K01FR29_SmartChargingCtrlrAvailableIsFalse_RespondsCallError) { +TEST_F(ChargePointFixture, K01FR29_SmartChargingCtrlrAvailableIsFalse_RespondsCallError) { auto evse_connector_structure = create_evse_connector_structure(); auto database_handler = create_database_handler(); auto evse_security = std::make_shared(); configure_callbacks_with_mocks(); - const auto DEFAULT_MESSAGE_QUEUE_SIZE_THRESHOLD = 2E5; - std::shared_ptr> message_queue = - std::make_shared>( - [this](json message) -> bool { return false; }, - MessageQueueConfig{ - this->device_model->get_value(ControllerComponentVariables::MessageAttempts), - this->device_model->get_value(ControllerComponentVariables::MessageAttemptInterval), - this->device_model->get_optional_value(ControllerComponentVariables::MessageQueueSizeThreshold) - .value_or(DEFAULT_MESSAGE_QUEUE_SIZE_THRESHOLD), - this->device_model->get_optional_value(ControllerComponentVariables::QueueAllMessages) - .value_or(false), - this->device_model->get_value(ControllerComponentVariables::MessageTimeout)}, - database_handler); - - ocpp::v201::ChargePoint chargePoint(evse_connector_structure, device_model, database_handler, message_queue, "/tmp", - evse_security, callbacks); + const auto cv = ControllerComponentVariables::SmartChargingCtrlrAvailable; + this->device_model->set_value(cv.component, cv.variable.value(), AttributeEnum::Actual, "false", "TEST", true); auto periods = create_charging_schedule_periods({0, 1, 2}); + auto profile = create_charging_profile( + DEFAULT_PROFILE_ID, ChargingProfilePurposeEnum::TxProfile, + create_charge_schedule(ChargingRateUnitEnum::A, periods, ocpp::DateTime("2024-01-17T17:00:00")), DEFAULT_TX_ID); + SetChargingProfileRequest req; + req.evseId = DEFAULT_EVSE_ID; + req.chargingProfile = profile; + + auto set_charging_profile_req = + request_to_enhanced_message(req); + + EXPECT_CALL(*smart_charging_handler, validate_and_add_profile(testing::_, testing::_)).Times(0); + + charge_point->handle_message(set_charging_profile_req); +} + +TEST_F(ChargePointFixture, K01FR29_SmartChargingCtrlrAvailableIsTrue_CallsValidateAndAddProfile) { + auto evse_connector_structure = create_evse_connector_structure(); + auto database_handler = create_database_handler(); + auto evse_security = std::make_shared(); + configure_callbacks_with_mocks(); + + const auto cv = ControllerComponentVariables::SmartChargingCtrlrAvailable; + this->device_model->set_value(cv.component, cv.variable.value(), AttributeEnum::Actual, "true", "TEST", true); + + auto periods = create_charging_schedule_periods({0, 1, 2}); auto profile = create_charging_profile( DEFAULT_PROFILE_ID, ChargingProfilePurposeEnum::TxProfile, create_charge_schedule(ChargingRateUnitEnum::A, periods, ocpp::DateTime("2024-01-17T17:00:00")), DEFAULT_TX_ID); @@ -749,7 +756,7 @@ TEST_F(ChargePointFixture, DISABLED_K01FR29_SmartChargingCtrlrAvailableIsFalse_R auto set_charging_profile_req = request_to_enhanced_message(req); - EXPECT_CALL(*smart_charging_handler, validate_profile(testing::_, testing::_)).Times(0); + EXPECT_CALL(*smart_charging_handler, validate_and_add_profile(profile, DEFAULT_EVSE_ID)); charge_point->handle_message(set_charging_profile_req); } From 0904124389919f2163c011da8262391972b398a3 Mon Sep 17 00:00:00 2001 From: Gianfranco Berardi <54074967+gberardi-pillar@users.noreply.github.com> Date: Fri, 16 Aug 2024 15:42:12 -0500 Subject: [PATCH 11/16] Correct comment. Signed-off-by: Gianfranco Berardi <54074967+gberardi-pillar@users.noreply.github.com> --- lib/ocpp/v201/charge_point.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index de5558750..fec36c63d 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -3276,7 +3276,7 @@ void ChargePoint::handle_set_charging_profile_req(Calldevice_model->get_optional_value(ControllerComponentVariables::SmartChargingCtrlrAvailable) .value_or(false); From 02e0f1d5bc5fd6107eee8e13628550691ea1f2a1 Mon Sep 17 00:00:00 2001 From: Coury Richards <146002925+couryrr-afs@users.noreply.github.com> Date: Tue, 20 Aug 2024 15:41:09 +0000 Subject: [PATCH 12/16] updated status document per PR comments to better highlight the reasoning Signed-off-by: Coury Richards <146002925+couryrr-afs@users.noreply.github.com> Signed-off-by: Gianfranco Berardi <54074967+gberardi-pillar@users.noreply.github.com> --- doc/ocpp_201_status.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/doc/ocpp_201_status.md b/doc/ocpp_201_status.md index b307f73ce..844eae05b 100644 --- a/doc/ocpp_201_status.md +++ b/doc/ocpp_201_status.md @@ -1231,20 +1231,20 @@ This document contains the status of which OCPP 2.0.1 numbered functional requir | K01.FR.01 | 🌐 | `TxProfile`s are supported. | | K01.FR.02 | 🌐 | | | K01.FR.03 | 🌐 💂 | `TxProfile`s without `transactionId`s are rejected. | -| K01.FR.04 | 🌐 | | +| K01.FR.04 | ✅ | | | K01.FR.05 | ✅ | | -| K01.FR.06 | ✅ | | -| K01.FR.07 | ⛽️K08 | Notified through the `signal_set_charging_profiles` callback. | +| K01.FR.06 | 🌐 💂 | As part of validation any `ChargingProile` with a stackLevel - chargingProfilePurpose - evseId combination is rejected | +| K01.FR.07 | ⛽️ | K08 - Notified through the `signal_set_charging_profiles` callback. | | K01.FR.08 | 🌐 | `TxDefaultProfile`s are supported. | | K01.FR.09 | ✅ | | -| K01.FR.10 | ⛽️K08 | During validation `validFrom` and `validTo` are sets if they are blank to support this | -| K01.FR.11 | ⛽️K08 | | -| K01.FR.12 | ⛽️K08 | | -| K01.FR.13 | ⛽️K08 | | +| K01.FR.10 | ⛽️ | K08 - During validation `validFrom` and `validTo` are set if they are blank to support this | +| K01.FR.11 | ❎ | K08 - The application of `ChargingProfileSchedules` are done via the `CompositeSchedule` from `GetCompositeSchedule` | +| K01.FR.12 | ❎ | K08 - The application of `ChargingProfileSchedules` are done via the `CompositeSchedule` from `GetCompositeSchedule` | +| K01.FR.13 | ❎ | K08 - The application of `ChargingProfileSchedules` are done via the `CompositeSchedule` from `GetCompositeSchedule` | | K01.FR.14 | ✅ | | | K01.FR.15 | ✅ | | | K01.FR.16 | ✅ | | -| K01.FR.17 | ⛽️K08 | | +| K01.FR.17 | ⛽️ | K08 - The application of `ChargingProfileSchedules` are done via the `CompositeSchedule` from `GetCompositeSchedule` | | K01.FR.19 | ✅ | | | K01.FR.20 | ✅ | Suggests `ACPhaseSwitchingSupported` should be per EVSE, conflicting with the rest of the spec. | | K01.FR.21 | | There is an active community discussion on this topic. | @@ -1253,28 +1253,28 @@ This document contains the status of which OCPP 2.0.1 numbered functional requir | K01.FR.27 | ✅ | | | K01.FR.28 | ✅ | | | K01.FR.29 | ✅ | | -| K01.FR.30 | ⛽️K08 | | +| K01.FR.30 | ⛽️ | K08 - The application of `ChargingProfileSchedules` are done via the `CompositeSchedule` from `GetCompositeSchedule` | | K01.FR.31 | ✅ | | -| K01.FR.32 | ⛽️K08 | | +| K01.FR.32 | ⛽️ | K08 - The application of `ChargingProfileSchedules` are done via the `CompositeSchedule` from `GetCompositeSchedule` | | K01.FR.33 | ✅ | | | K01.FR.34 | | Defer to K15 - K17 work | | K01.FR.35 | ✅ | | -| K01.FR.36 | ⛽️K08 | | -| K01.FR.37 | ⛽️K08 | | +| K01.FR.36 | ⛽️ | K08 | +| K01.FR.37 | ⛽️ | K08 | | K01.FR.38 | ✅ | `ChargingStationMaxProfile`s with `Relative` for `chargingProfileKind` are rejected. | | K01.FR.39 | ✅ | New `TxProfile`s matching existing `(stackLevel, transactionId)` are rejected. | | K01.FR.40 | ✅ | `Absolute`/`Recurring` profiles without `startSchedule` fields are rejected. | | K01.FR.41 | ✅ | `Relative` profiles with `startSchedule` fields are rejected. | -| K01.FR.42 | ⛽️K08 | | +| K01.FR.42 | ⛽️ | | | K01.FR.43 | | Open question to OCA | | K01.FR.44 | ✅ | We reject invalid profiles instead of modifying and accepting them. | | K01.FR.45 | ✅ | We reject invalid profiles instead of modifying and accepting them. | -| K01.FR.46 | ⛽️K08 | | -| K01.FR.47 | ⛽️K08 | | +| K01.FR.46 | ⛽️ | K08 | +| K01.FR.47 | ⛽️ | K08 | | K01.FR.48 | ✅ | | | K01.FR.49 | ✅ | | -| K01.FR.50 | ⛽️K08 | | -| K01.FR.51 | ⛽️K08 | | +| K01.FR.50 | ⛽️ | K08 | +| K01.FR.51 | ⛽️ | K08 | | K01.FR.52 | ✅ | | | K01.FR.53 | ✅ | | From 12f88a4e834a63898b9984c3705957657e1089d6 Mon Sep 17 00:00:00 2001 From: Gianfranco Berardi <54074967+gberardi-pillar@users.noreply.github.com> Date: Wed, 21 Aug 2024 11:08:26 -0500 Subject: [PATCH 13/16] Rename SmartChargingCtrlrAvailableEnabled to SmartChargingCtrlrEnabled. Signed-off-by: Gianfranco Berardi <54074967+gberardi-pillar@users.noreply.github.com> --- .../v201/component_config/standardized/SmartChargingCtrlr.json | 2 +- include/ocpp/v201/ctrlr_component_variables.hpp | 2 +- lib/ocpp/v201/ctrlr_component_variables.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/v201/component_config/standardized/SmartChargingCtrlr.json b/config/v201/component_config/standardized/SmartChargingCtrlr.json index 96cb71c77..a39ed1152 100644 --- a/config/v201/component_config/standardized/SmartChargingCtrlr.json +++ b/config/v201/component_config/standardized/SmartChargingCtrlr.json @@ -34,7 +34,7 @@ "description": "Whether smart charging is supported.", "type": "boolean" }, - "SmartChargingCtrlrAvailableEnabled": { + "SmartChargingCtrlrEnabled": { "variable_name": "Enabled", "characteristics": { "supportsMonitoring": true, diff --git a/include/ocpp/v201/ctrlr_component_variables.hpp b/include/ocpp/v201/ctrlr_component_variables.hpp index 677b5c015..96ab3c7f1 100644 --- a/include/ocpp/v201/ctrlr_component_variables.hpp +++ b/include/ocpp/v201/ctrlr_component_variables.hpp @@ -194,7 +194,7 @@ extern const RequiredComponentVariable& OrganizationName; extern const RequiredComponentVariable& SecurityProfile; extern const ComponentVariable& ACPhaseSwitchingSupported; extern const ComponentVariable& SmartChargingCtrlrAvailable; -extern const ComponentVariable& SmartChargingCtrlrAvailableEnabled; +extern const ComponentVariable& SmartChargingCtrlrEnabled; extern const RequiredComponentVariable& EntriesChargingProfiles; extern const ComponentVariable& ExternalControlSignalsEnabled; extern const RequiredComponentVariable& LimitChangeSignificance; diff --git a/lib/ocpp/v201/ctrlr_component_variables.cpp b/lib/ocpp/v201/ctrlr_component_variables.cpp index 074f11aaf..1607d6572 100644 --- a/lib/ocpp/v201/ctrlr_component_variables.cpp +++ b/lib/ocpp/v201/ctrlr_component_variables.cpp @@ -1081,7 +1081,7 @@ const ComponentVariable& SmartChargingCtrlrAvailable = { "Available", }), }; -const ComponentVariable& SmartChargingCtrlrAvailableEnabled = { +const ComponentVariable& SmartChargingCtrlrEnabled = { ControllerComponents::SmartChargingCtrlr, std::nullopt, std::optional({ From 484afc30b715dae0a112132dddf4d19082c804b2 Mon Sep 17 00:00:00 2001 From: Gianfranco Berardi <54074967+gberardi-pillar@users.noreply.github.com> Date: Mon, 26 Aug 2024 09:48:15 -0500 Subject: [PATCH 14/16] Update Smart Charging variables in new location. config.json was removed. Signed-off-by: Gianfranco Berardi <54074967+gberardi-pillar@users.noreply.github.com> --- .../component_config/standardized/SmartChargingCtrlr.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/config/v201/component_config/standardized/SmartChargingCtrlr.json b/config/v201/component_config/standardized/SmartChargingCtrlr.json index a39ed1152..3ca47b67e 100644 --- a/config/v201/component_config/standardized/SmartChargingCtrlr.json +++ b/config/v201/component_config/standardized/SmartChargingCtrlr.json @@ -28,7 +28,8 @@ "attributes": [ { "type": "Actual", - "mutability": "ReadOnly" + "mutability": "ReadOnly", + "value": true } ], "description": "Whether smart charging is supported.", @@ -44,7 +45,7 @@ { "type": "Actual", "mutability": "ReadWrite", - "value": false + "value": true } ], "description": "Whether smart charging is enabled.", From 831a247403b9a2e1254360f8e61d0d2099adf26b Mon Sep 17 00:00:00 2001 From: Gianfranco Berardi <54074967+gberardi-pillar@users.noreply.github.com> Date: Mon, 26 Aug 2024 13:38:36 -0500 Subject: [PATCH 15/16] Add link to OCA question in status docs. Signed-off-by: Gianfranco Berardi <54074967+gberardi-pillar@users.noreply.github.com> --- doc/ocpp_201_status.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ocpp_201_status.md b/doc/ocpp_201_status.md index 844eae05b..c99c62319 100644 --- a/doc/ocpp_201_status.md +++ b/doc/ocpp_201_status.md @@ -1266,7 +1266,7 @@ This document contains the status of which OCPP 2.0.1 numbered functional requir | K01.FR.40 | ✅ | `Absolute`/`Recurring` profiles without `startSchedule` fields are rejected. | | K01.FR.41 | ✅ | `Relative` profiles with `startSchedule` fields are rejected. | | K01.FR.42 | ⛽️ | | -| K01.FR.43 | | Open question to OCA | +| K01.FR.43 | | Open question to OCA - https://oca.causewaynow.com/wg/OCA-TWG/mail/thread/4254 | | K01.FR.44 | ✅ | We reject invalid profiles instead of modifying and accepting them. | | K01.FR.45 | ✅ | We reject invalid profiles instead of modifying and accepting them. | | K01.FR.46 | ⛽️ | K08 | From 92dbaeecb32caa0c25ab1dcbb79b575adc30344c Mon Sep 17 00:00:00 2001 From: Gianfranco Berardi <54074967+gberardi-pillar@users.noreply.github.com> Date: Mon, 26 Aug 2024 14:36:22 -0500 Subject: [PATCH 16/16] Use non-deprecated build kit Docker container. Signed-off-by: Gianfranco Berardi <54074967+gberardi-pillar@users.noreply.github.com> --- .github/workflows/build_and_test.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml index 30e7e9fee..295061e17 100644 --- a/.github/workflows/build_and_test.yaml +++ b/.github/workflows/build_and_test.yaml @@ -40,8 +40,8 @@ jobs: rsync -a source/.ci/build-kit/ scripts - name: Pull docker container run: | - docker pull --platform=linux/x86_64 --quiet ghcr.io/everest/build-kit-alpine:latest - docker image tag ghcr.io/everest/build-kit-alpine:latest build-kit + docker pull --platform=linux/x86_64 --quiet ghcr.io/everest/everest-ci/build-kit-base:latest + docker image tag ghcr.io/everest/everest-ci/build-kit-base:latest build-kit - name: Run install with tests run: | docker run \