From 49e059678e6b4d1e6a3ceb00acac34c188b3644a Mon Sep 17 00:00:00 2001 From: Kai Hermann Date: Wed, 13 Nov 2024 13:18:27 +0100 Subject: [PATCH 1/5] Add coverage and use reusable workflow from everest-ci (#858) * Enable coverage generation * Use reusable workflow * Remove old install_and_test script * Set specific paths for ctest and coverage results * Exclude tests directory from code coverage --------- Signed-off-by: Kai-Uwe Hermann --- .ci/build-kit/docker/Dockerfile | 3 ++ .ci/build-kit/install_and_test.sh | 21 -------- .ci/build-kit/scripts/compile.sh | 24 +++++++++ .ci/build-kit/scripts/install.sh | 9 ++++ .ci/build-kit/scripts/run_coverage.sh | 25 +++++++++ .ci/build-kit/scripts/run_unit_tests.sh | 12 +++++ .github/workflows/build_and_test.yaml | 67 ++++++++----------------- CMakeLists.txt | 3 ++ tests/CMakeLists.txt | 14 ++++++ 9 files changed, 112 insertions(+), 66 deletions(-) create mode 100644 .ci/build-kit/docker/Dockerfile delete mode 100755 .ci/build-kit/install_and_test.sh create mode 100755 .ci/build-kit/scripts/compile.sh create mode 100755 .ci/build-kit/scripts/install.sh create mode 100755 .ci/build-kit/scripts/run_coverage.sh create mode 100755 .ci/build-kit/scripts/run_unit_tests.sh diff --git a/.ci/build-kit/docker/Dockerfile b/.ci/build-kit/docker/Dockerfile new file mode 100644 index 000000000..ca655b11d --- /dev/null +++ b/.ci/build-kit/docker/Dockerfile @@ -0,0 +1,3 @@ +# syntax=docker/dockerfile:1 +ARG BASE_IMAGE_TAG=latest +FROM ghcr.io/everest/everest-ci/build-kit-base:${BASE_IMAGE_TAG} diff --git a/.ci/build-kit/install_and_test.sh b/.ci/build-kit/install_and_test.sh deleted file mode 100755 index 96e58b9d5..000000000 --- a/.ci/build-kit/install_and_test.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh - -set -e - -cmake \ - -B build \ - -S "$EXT_MOUNT/source" \ - -G Ninja \ - -DBUILD_TESTING=ON \ - -DLIBOCPP16_BUILD_EXAMPLES=ON \ - -DCMAKE_BUILD_TYPE=Debug \ - -DCMAKE_INSTALL_PREFIX="$WORKSPACE_PATH/dist" \ - -DCMAKE_EXPORT_COMPILE_COMMANDS=ON - -ninja -j$(nproc) -C build install - -trap "cp build/Testing/Temporary/LastTest.log /ext/ctest-report" EXIT - -ninja -j$(nproc) -C build test - -# cmake -B build -G Ninja -DBUILD_TESTING=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX="./dist" -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ No newline at end of file diff --git a/.ci/build-kit/scripts/compile.sh b/.ci/build-kit/scripts/compile.sh new file mode 100755 index 000000000..2dd8870d5 --- /dev/null +++ b/.ci/build-kit/scripts/compile.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +cmake \ + -B "$EXT_MOUNT/build" \ + -S "$EXT_MOUNT/source" \ + -G Ninja \ + -DBUILD_TESTING=ON \ + -DLIBOCPP16_BUILD_EXAMPLES=ON \ + -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_INSTALL_PREFIX="$EXT_MOUNT/dist" \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=ON + +retVal=$? +if [ $retVal -ne 0 ]; then + echo "Configuring failed with return code $retVal" + exit $retVal +fi + +ninja -C "$EXT_MOUNT/build" +retVal=$? +if [ $retVal -ne 0 ]; then + echo "Compiling failed with return code $retVal" + exit $retVal +fi diff --git a/.ci/build-kit/scripts/install.sh b/.ci/build-kit/scripts/install.sh new file mode 100755 index 000000000..174dbc773 --- /dev/null +++ b/.ci/build-kit/scripts/install.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +ninja -C "$EXT_MOUNT/build" install +retVal=$? + +if [ $retVal -ne 0 ]; then + echo "Installation failed with return code $retVal" + exit $retVal +fi diff --git a/.ci/build-kit/scripts/run_coverage.sh b/.ci/build-kit/scripts/run_coverage.sh new file mode 100755 index 000000000..4bf1e88b4 --- /dev/null +++ b/.ci/build-kit/scripts/run_coverage.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +ninja \ + -C "$EXT_MOUNT/build" \ + ocpp_gcovr_coverage +retValHTML=$? + +ninja \ + -C "$EXT_MOUNT/build" \ + ocpp_gcovr_coverage_xml +retValXML=$? + +# Copy the generated coverage report and xml to the mounted directory in any case +cp -R "$EXT_MOUNT/build/ocpp_gcovr_coverage" "$EXT_MOUNT/gcovr-coverage" +cp "$EXT_MOUNT/build/ocpp_gcovr_coverage_xml.xml" "$EXT_MOUNT/gcovr-coverage-xml.xml" + +if [ $retValHTML -ne 0 ]; then + echo "Coverage HTML report failed with return code $retValHTML" + exit $retValHTML +fi + +if [ $retValXML -ne 0 ]; then + echo "Coverage XML report failed with return code $retValXML" + exit $retValXML +fi diff --git a/.ci/build-kit/scripts/run_unit_tests.sh b/.ci/build-kit/scripts/run_unit_tests.sh new file mode 100755 index 000000000..5fa187064 --- /dev/null +++ b/.ci/build-kit/scripts/run_unit_tests.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +ninja -C "$EXT_MOUNT/build" test +retVal=$? + +# Copy the LastTest.log file to the mounted directory in any case +cp "$EXT_MOUNT/build/Testing/Temporary/LastTest.log" "$EXT_MOUNT/ctest-report" + +if [ $retVal -ne 0 ]; then + echo "Unit tests failed with return code $retVal" + exit $retVal +fi diff --git a/.github/workflows/build_and_test.yaml b/.github/workflows/build_and_test.yaml index 295061e17..415b94e1f 100644 --- a/.github/workflows/build_and_test.yaml +++ b/.github/workflows/build_and_test.yaml @@ -1,5 +1,5 @@ -name: Build and test libocpp -on: +name: Build, Lint and Test +on: pull_request: {} workflow_dispatch: inputs: @@ -11,47 +11,24 @@ on: options: - 'ubuntu-22.04' - 'large-ubuntu-22.04-xxl' -jobs: - lint: - name: Lint - runs-on: ${{ inputs.runner || 'ubuntu-22.04' }} - steps: - - name: Checkout libocpp - uses: actions/checkout@v3 - with: - path: source - - name: Run clang-format - uses: everest/everest-ci/github-actions/run-clang-format@v1.1.0 - with: - source-dir: source - extensions: hpp,cpp - exclude: cache - install_and_test: - name: Install and test - runs-on: ${{ inputs.runner || 'ubuntu-22.04' }} - steps: - - name: Checkout libocpp - uses: actions/checkout@v3 - with: - path: source - - name: Setup run scripts - run: | - mkdir scripts - rsync -a source/.ci/build-kit/ scripts - - name: Pull docker container - run: | - 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 \ - --volume "$(pwd):/ext" \ - --name test-container \ - build-kit run-script install_and_test - - name: Archive test results - if: always() - uses: actions/upload-artifact@v3 - with: - name: ctest-report - path: ${{ github.workspace }}/ctest-report + schedule: + - cron: '37 13,1 * * *' +jobs: + ci: + name: Build, Lint and Test + uses: everest/everest-ci/.github/workflows/continuous_integration.yml@v1.4.2 + permissions: + contents: read + secrets: + coverage_deploy_token: ${{ secrets.SA_GITHUB_PAT }} + with: + runner: ${{ inputs.runner || 'ubuntu-22.04' }} + artifact_deploy_target_repo: EVerest/everest.github.io + run_coverage: true + do_not_run_coverage_badge_creation: false + run_install_wheels: false + run_integration_tests: false + ctest_report_path: ctest-report + coverage_report_path: gcovr-coverage + coverage_xml_path: gcovr-coverage-xml.xml diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f8181071..aa3cee13d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,9 @@ endif() if((${CMAKE_PROJECT_NAME} STREQUAL ${PROJECT_NAME} OR ${PROJECT_NAME}_BUILD_TESTING) AND BUILD_TESTING) set(LIBOCPP_BUILD_TESTING ON) + evc_include(CodeCoverage) + + append_coverage_compiler_flags() endif() # dependencies diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c5e188c69..01e7b40c8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -88,3 +88,17 @@ endif() if(LIBOCPP_ENABLE_V201) add_subdirectory(lib/ocpp/v201) endif() + +setup_target_for_coverage_gcovr_html( + NAME ${PROJECT_NAME}_gcovr_coverage + EXECUTABLE ctest + DEPENDENCIES libocpp_unit_tests + EXCLUDE "src/*" "tests/*" +) + +setup_target_for_coverage_gcovr_xml( + NAME ${PROJECT_NAME}_gcovr_coverage_xml + EXECUTABLE ctest + DEPENDENCIES libocpp_unit_tests + EXCLUDE "src/*" "tests/*" +) From d042629d7f8f3177e1cae6519f243c9c59864054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piet=20G=C3=B6mpel?= <37657534+Pietfried@users.noreply.github.com> Date: Thu, 14 Nov 2024 08:29:42 +0100 Subject: [PATCH 2/5] Use transaction BecomeAvailable when stopping transactions also when this->status->get_state(connector) == ChargePointStatus::Faulted since the internal state of the state machine could be other then Faulted, which would lead to an unspecified transaction not moving back to Available (#865) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Piet Gömpel --- lib/ocpp/v16/charge_point_impl.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/ocpp/v16/charge_point_impl.cpp b/lib/ocpp/v16/charge_point_impl.cpp index 05ab20ab5..639d3c33a 100644 --- a/lib/ocpp/v16/charge_point_impl.cpp +++ b/lib/ocpp/v16/charge_point_impl.cpp @@ -4084,11 +4084,8 @@ void ChargePointImpl::on_session_started(int32_t connector, const std::string& s } void ChargePointImpl::on_session_stopped(const int32_t connector, const std::string& session_id) { - // TODO(piet) fix this when evse manager signals clearance of an error - if (this->status->get_state(connector) == ChargePointStatus::Faulted) { - this->status->submit_event(connector, FSMEvent::I1_ReturnToAvailable, ocpp::DateTime()); - } else if (this->status->get_state(connector) != ChargePointStatus::Reserved && - this->status->get_state(connector) != ChargePointStatus::Unavailable) { + if (this->status->get_state(connector) != ChargePointStatus::Reserved && + this->status->get_state(connector) != ChargePointStatus::Unavailable) { this->status->submit_event(connector, FSMEvent::BecomeAvailable, ocpp::DateTime()); } From 5416829ec663544173dbb17f082571579f479432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piet=20G=C3=B6mpel?= <37657534+Pietfried@users.noreply.github.com> Date: Thu, 14 Nov 2024 10:31:14 +0100 Subject: [PATCH 3/5] Fixed bug setting NetworkConfigurationPriority (#866) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fixed an issue when setting the NetworkConfigurationPriority. During the validation, every entry of the new priority list was checked using the connectivity_managers cached slots. The slots of the connectivity manager are only initialized at startup. A new profile could have been send using a SetNetworkProfile.req by the CSMS at runtime, so when validating the NetworkConfigurationPriority we shall not use the cached connectivity_manager slots but the NetworkConnectionProfiles stored in the device model --------- Signed-off-by: Piet Gömpel --- lib/ocpp/v201/charge_point.cpp | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 8fc4f5387..68201ff72 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -1855,16 +1855,23 @@ bool ChargePoint::validate_set_variable(const SetVariableData& set_variable_data const auto network_configuration_priorities = ocpp::split_string(set_variable_data.attributeValue.get(), ','); const auto active_security_profile = this->device_model->get_value(ControllerComponentVariables::SecurityProfile); - for (const auto configuration_slot : network_configuration_priorities) { - try { - auto network_profile_opt = - this->connectivity_manager->get_network_connection_profile(std::stoi(configuration_slot)); - if (!network_profile_opt.has_value()) { + + try { + const auto network_connection_profiles = json::parse( + this->device_model->get_value(ControllerComponentVariables::NetworkConnectionProfiles)); + for (const auto configuration_slot : network_configuration_priorities) { + auto network_profile_it = + std::find_if(network_connection_profiles.begin(), network_connection_profiles.end(), + [configuration_slot](const SetNetworkProfileRequest& network_profile) { + return network_profile.configurationSlot == std::stoi(configuration_slot); + }); + + if (network_profile_it == network_connection_profiles.end()) { EVLOG_warning << "Could not find network profile for configurationSlot: " << configuration_slot; return false; } - auto network_profile = network_profile_opt.value(); + auto network_profile = SetNetworkProfileRequest(*network_profile_it).connectionData; if (network_profile.securityProfile <= active_security_profile) { continue; @@ -1884,10 +1891,14 @@ bool ChargePoint::validate_set_variable(const SetVariableData& set_variable_data << " is >= 2 but no CSMS Root Certifciate is installed"; return false; } - } catch (const std::invalid_argument& e) { - EVLOG_warning << "NetworkConfigurationPriority is not an integer: " << configuration_slot; - return false; } + } catch (const std::invalid_argument& e) { + EVLOG_warning << "NetworkConfigurationPriority contains at least one value which is not an integer: " + << set_variable_data.attributeValue.get(); + return false; + } catch (const json::exception& e) { + EVLOG_warning << "Could not parse NetworkConnectionProfiles or SetNetworkProfileRequest: " << e.what(); + return false; } } return true; From b00d278cdfc6308c91dbafaa1a808fdea777806d Mon Sep 17 00:00:00 2001 From: Maaike Zijderveld Date: Thu, 14 Nov 2024 10:59:05 +0100 Subject: [PATCH 4/5] Rename `DeviceModelStorage` to `DeviceModelInterface` (#768) * Rename `DeviceModelStorage` to `DeviceModelInterface` * Changes to the database and distinguish between external and ocpp values. Signed-off-by: Maaike Zijderveld, iolar --- CMakeLists.txt | 2 +- .../2_down-variable_source.sql | 2 + .../2_up-variable_source.sql | 2 + include/ocpp/v201/charge_point.hpp | 12 +- include/ocpp/v201/device_model.hpp | 25 ++--- ...hpp => device_model_storage_interface.hpp} | 20 ++-- .../ocpp/v201/device_model_storage_sqlite.hpp | 4 +- include/ocpp/v201/enums.hpp | 2 + include/ocpp/v201/init_device_model_db.hpp | 4 +- include/ocpp/v201/monitoring_updater.hpp | 4 +- lib/ocpp/v201/charge_point.cpp | 23 ++-- lib/ocpp/v201/device_model.cpp | 103 +++++++++--------- lib/ocpp/v201/device_model_storage_sqlite.cpp | 13 ++- lib/ocpp/v201/enums.cpp | 3 +- lib/ocpp/v201/init_device_model_db.cpp | 44 ++++++-- lib/ocpp/v201/monitoring_updater.cpp | 2 +- .../standardized/UnitTestCtrlr.json | 1 + ...> device_model_storage_interface_mock.hpp} | 4 +- tests/lib/ocpp/v201/test_charge_point.cpp | 4 +- .../lib/ocpp/v201/test_composite_schedule.cpp | 1 - .../v201/test_device_model_storage_sqlite.cpp | 2 +- .../ocpp/v201/test_init_device_model_db.cpp | 17 ++- .../ocpp/v201/test_smart_charging_handler.cpp | 2 +- tests/resources/unittest_device_model.db | Bin 65536 -> 65536 bytes .../unittest_device_model_missing_required.db | Bin 57344 -> 61440 bytes 25 files changed, 171 insertions(+), 125 deletions(-) create mode 100644 config/v201/device_model_migrations/2_down-variable_source.sql create mode 100644 config/v201/device_model_migrations/2_up-variable_source.sql rename include/ocpp/v201/{device_model_storage.hpp => device_model_storage_interface.hpp} (91%) rename tests/lib/ocpp/v201/mocks/{device_model_storage_mock.hpp => device_model_storage_interface_mock.hpp} (90%) diff --git a/CMakeLists.txt b/CMakeLists.txt index aa3cee13d..28bb3d7d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.14) project(ocpp - VERSION 0.19.0 + VERSION 0.20.0 DESCRIPTION "A C++ implementation of the Open Charge Point Protocol" LANGUAGES CXX ) diff --git a/config/v201/device_model_migrations/2_down-variable_source.sql b/config/v201/device_model_migrations/2_down-variable_source.sql new file mode 100644 index 000000000..0b21b0445 --- /dev/null +++ b/config/v201/device_model_migrations/2_down-variable_source.sql @@ -0,0 +1,2 @@ +ALTER TABLE VARIABLE +DROP COLUMN SOURCE; diff --git a/config/v201/device_model_migrations/2_up-variable_source.sql b/config/v201/device_model_migrations/2_up-variable_source.sql new file mode 100644 index 000000000..65ebf907f --- /dev/null +++ b/config/v201/device_model_migrations/2_up-variable_source.sql @@ -0,0 +1,2 @@ +ALTER TABLE VARIABLE +ADD COLUMN SOURCE TEXT; diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index 2bd12cf01..35a963e3a 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include #include @@ -808,17 +808,17 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa /// \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 device_model_storage_interface device model interface instance /// \param ocpp_main_path Path where utility files for OCPP are read and written to /// \param core_database_path Path to directory where core database is located /// \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::unique_ptr device_model_storage, const std::string& ocpp_main_path, - 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); + std::unique_ptr device_model_storage_interface, + const std::string& ocpp_main_path, 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 diff --git a/include/ocpp/v201/device_model.hpp b/include/ocpp/v201/device_model.hpp index 51b8f5c5c..72418ab19 100644 --- a/include/ocpp/v201/device_model.hpp +++ b/include/ocpp/v201/device_model.hpp @@ -8,7 +8,7 @@ #include -#include +#include namespace ocpp { namespace v201 { @@ -91,13 +91,13 @@ typedef std::function on_monitor_updated; -/// \brief This class manages access to the device model representation and to the device model storage and provides +/// \brief This class manages access to the device model representation and to the device model interface and provides /// functionality to support the use cases defined in the functional block Provisioning class DeviceModel { private: - DeviceModelMap device_model; - std::unique_ptr storage; + DeviceModelMap device_model_map; + std::unique_ptr device_model; /// \brief Listener for the internal change of a variable on_variable_changed variable_listener; @@ -106,7 +106,7 @@ class DeviceModel { /// \brief Private helper method that does some checks with the device model representation in memory to evaluate if /// a value for the given parameters can be requested. If it can be requested it will be retrieved from the device - /// model storage and the given \p value will be set to the value that was retrieved + /// model interface and the given \p value will be set to the value that was retrieved /// \param component_id /// \param variable_id /// \param attribute_enum @@ -138,15 +138,15 @@ class DeviceModel { public: /// \brief Constructor for the device model - /// \param device_model_storage pointer to a device model storage class - explicit DeviceModel(std::unique_ptr device_model_storage); + /// \param device_model_storage_interface pointer to a device model interface class + explicit DeviceModel(std::unique_ptr device_model_storage_interface); /// \brief Direct access to value of a VariableAttribute for the given component, variable and attribute_enum. This /// should only be called for variables that have a role standardized in the OCPP2.0.1 specification. /// \tparam T datatype of the value that is requested /// \param component_variable Combination of Component and Variable that identifies the Variable /// \param attribute_enum defaults to AttributeEnum::Actual - /// \return the requested value from the device model storage + /// \return the requested value from the device model interface template T get_value(const RequiredComponentVariable& component_variable, const AttributeEnum& attribute_enum = AttributeEnum::Actual) const { @@ -159,11 +159,10 @@ class DeviceModel { if (response == GetVariableStatusEnum::Accepted) { return to_specific_type(value); } else { - EVLOG_critical - << "Directly requested value for ComponentVariable that doesn't exist in the device model storage: " - << component_variable; + EVLOG_critical << "Directly requested value for ComponentVariable that doesn't exist in the device model: " + << component_variable; EVLOG_AND_THROW(std::runtime_error( - "Directly requested value for ComponentVariable that doesn't exist in the device model storage.")); + "Directly requested value for ComponentVariable that doesn't exist in the device model.")); } } @@ -190,7 +189,7 @@ class DeviceModel { } /// \brief Requests a value of a VariableAttribute specified by combination of \p component_id and \p variable_id - /// from the device model storage + /// from the device model /// \tparam T datatype of the value that is requested /// \param component_id /// \param variable_id diff --git a/include/ocpp/v201/device_model_storage.hpp b/include/ocpp/v201/device_model_storage_interface.hpp similarity index 91% rename from include/ocpp/v201/device_model_storage.hpp rename to include/ocpp/v201/device_model_storage_interface.hpp index 2892a781e..b666874e4 100644 --- a/include/ocpp/v201/device_model_storage.hpp +++ b/include/ocpp/v201/device_model_storage_interface.hpp @@ -1,8 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest -#ifndef OCPP_V201_DEVICE_MODEL_STORAGE_HPP -#define OCPP_V201_DEVICE_MODEL_STORAGE_HPP +#pragma once #include #include @@ -34,20 +33,21 @@ struct VariableMonitoringPeriodic { struct VariableMetaData { VariableCharacteristics characteristics; std::unordered_map monitors; + std::optional source; }; using VariableMap = std::map; using DeviceModelMap = std::map; -class DeviceModelStorageError : public std::exception { +class DeviceModelError : public std::exception { public: [[nodiscard]] const char* what() const noexcept override { return this->reason.c_str(); } - explicit DeviceModelStorageError(std::string msg) { + explicit DeviceModelError(std::string msg) { this->reason = std::move(msg); } - explicit DeviceModelStorageError(const char* msg) { + explicit DeviceModelError(const char* msg) { this->reason = std::string(msg); } @@ -55,15 +55,15 @@ class DeviceModelStorageError : public std::exception { std::string reason; }; -/// \brief Abstract base class for device model storage. This class provides an interface for accessing and modifying +/// \brief Abstract base class for device model interface. This class provides an interface for accessing and modifying /// device model data. Implementations of this class should provide concrete implementations for the virtual methods /// declared here. -class DeviceModelStorage { +class DeviceModelStorageInterface { public: - virtual ~DeviceModelStorage() = default; + virtual ~DeviceModelStorageInterface() = default; - /// \brief Gets the device model from the device model storage + /// \brief Gets the device model from the device model interface /// \return std::map> that will contain a full representation of the /// device model except for the VariableAttribute(s) of each Variable. virtual DeviceModelMap get_device_model() = 0; @@ -140,5 +140,3 @@ class DeviceModelStorage { } // namespace v201 } // namespace ocpp - -#endif // OCPP_V201_DEVICE_MODEL_STORAGE_HPP diff --git a/include/ocpp/v201/device_model_storage_sqlite.hpp b/include/ocpp/v201/device_model_storage_sqlite.hpp index 662e4ef01..e5370f59c 100644 --- a/include/ocpp/v201/device_model_storage_sqlite.hpp +++ b/include/ocpp/v201/device_model_storage_sqlite.hpp @@ -9,12 +9,12 @@ #include #include -#include +#include namespace ocpp { namespace v201 { -class DeviceModelStorageSqlite : public DeviceModelStorage { +class DeviceModelStorageSqlite : public DeviceModelStorageInterface { private: std::unique_ptr db; diff --git a/include/ocpp/v201/enums.hpp b/include/ocpp/v201/enums.hpp index 88ad67724..cb13ab4d3 100644 --- a/include/ocpp/v201/enums.hpp +++ b/include/ocpp/v201/enums.hpp @@ -5,6 +5,8 @@ #ifndef OCPP_V201_ENUMS_HPP #define OCPP_V201_ENUMS_HPP +#include + namespace ocpp { namespace v201 { diff --git a/include/ocpp/v201/init_device_model_db.hpp b/include/ocpp/v201/init_device_model_db.hpp index 709b29c9c..a74cca055 100644 --- a/include/ocpp/v201/init_device_model_db.hpp +++ b/include/ocpp/v201/init_device_model_db.hpp @@ -36,7 +36,7 @@ #include #include -#include +#include namespace ocpp::v201 { /// @@ -93,6 +93,8 @@ struct DeviceModelVariable { std::optional default_actual_value; /// \brief Config monitors, if any std::vector monitors; + /// \brief Source of the variable. + std::optional source; }; /// \brief Convert from json to a ComponentKey struct. diff --git a/include/ocpp/v201/monitoring_updater.hpp b/include/ocpp/v201/monitoring_updater.hpp index 6337ea939..69486ff76 100644 --- a/include/ocpp/v201/monitoring_updater.hpp +++ b/include/ocpp/v201/monitoring_updater.hpp @@ -11,7 +11,7 @@ #include #include -#include +#include namespace ocpp::v201 { @@ -187,4 +187,4 @@ class MonitoringUpdater { std::unordered_map updater_monitors_meta; }; -} // namespace ocpp::v201 \ No newline at end of file +} // namespace ocpp::v201 diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 68201ff72..6043d228c 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -78,12 +78,12 @@ ChargePoint::ChargePoint(const std::map& evse_connector_struct } ChargePoint::ChargePoint(const std::map& evse_connector_structure, - std::unique_ptr device_model_storage, const std::string& ocpp_main_path, - 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) : + std::unique_ptr device_model_storage_interface, + const std::string& ocpp_main_path, 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) : ChargePoint( - evse_connector_structure, std::make_shared(std::move(device_model_storage)), + evse_connector_structure, std::make_shared(std::move(device_model_storage_interface)), std::make_shared( std::make_unique(fs::path(core_database_path) / "cp.db"), sql_init_path), nullptr /* message_queue initialized in this constructor */, message_log_path, evse_security, callbacks) { @@ -2661,8 +2661,7 @@ void ChargePoint::handle_set_network_profile_req(Call ControllerComponentVariables::NetworkConnectionProfiles.variable.value(), AttributeEnum::Actual, network_connection_profiles.dump(), VARIABLE_ATTRIBUTE_VALUE_SOURCE_INTERNAL) != SetVariableStatusEnum::Accepted) { - EVLOG_warning - << "CSMS attempted to set a network profile that could not be written to the device model storage"; + EVLOG_warning << "CSMS attempted to set a network profile that could not be written to the device model"; response.status = SetNetworkProfileStatusEnum::Rejected; ocpp::CallResult call_result(response, call.uniqueId); this->send(call_result); @@ -3712,7 +3711,7 @@ void ChargePoint::handle_set_monitoring_base_req(Call msg.monitoringBase == MonitoringBaseEnum::FactoryDefault) { try { this->device_model->clear_custom_monitors(); - } catch (const DeviceModelStorageError& e) { + } catch (const DeviceModelError& e) { EVLOG_warning << "Could not clear custom monitors from DB: " << e.what(); response.status = GenericDeviceModelStatusEnum::Rejected; } @@ -3772,7 +3771,7 @@ void ChargePoint::handle_set_variable_monitoring_req(const EnhancedMessagedevice_model->set_monitors(msg.setMonitoringData); - } catch (const DeviceModelStorageError& e) { + } catch (const DeviceModelError& e) { EVLOG_error << "Set monitors failed:" << e.what(); } @@ -3852,7 +3851,7 @@ void ChargePoint::handle_get_monitoring_report_req(Calldevice_model->clear_monitors(msg.id); - } catch (const DeviceModelStorageError& e) { + } catch (const DeviceModelError& e) { EVLOG_error << "Clear variable monitoring failed:" << e.what(); } @@ -4143,7 +4142,7 @@ void ChargePoint::handle_send_local_authorization_list_req(Calldevice_model->set_read_only_value(local_entries.component, local_entries.variable.value(), AttributeEnum::Actual, std::to_string(entries), VARIABLE_ATTRIBUTE_VALUE_SOURCE_INTERNAL); - } catch (const DeviceModelStorageError& e) { + } catch (const DeviceModelError& e) { EVLOG_warning << "Could not get local list count from database:" << e.what(); } catch (const DatabaseException& e) { EVLOG_warning << "Could not get local list count from database: " << e.what(); diff --git a/lib/ocpp/v201/device_model.cpp b/lib/ocpp/v201/device_model.cpp index 28f119458..b409af02c 100644 --- a/lib/ocpp/v201/device_model.cpp +++ b/lib/ocpp/v201/device_model.cpp @@ -219,8 +219,8 @@ bool include_in_summary_inventory(const ComponentVariable& cv, const VariableAtt GetVariableStatusEnum DeviceModel::request_value_internal(const Component& component_id, const Variable& variable_id, const AttributeEnum& attribute_enum, std::string& value, bool allow_write_only) const { - const auto component_it = this->device_model.find(component_id); - if (component_it == this->device_model.end()) { + const auto component_it = this->device_model_map.find(component_id); + if (component_it == this->device_model_map.end()) { EVLOG_debug << "unknown component in " << component_id.name << "." << variable_id.name; return GetVariableStatusEnum::UnknownComponent; } @@ -233,7 +233,7 @@ GetVariableStatusEnum DeviceModel::request_value_internal(const Component& compo return GetVariableStatusEnum::UnknownVariable; } - const auto attribute_opt = this->storage->get_variable_attribute(component_id, variable_id, attribute_enum); + const auto attribute_opt = this->device_model->get_variable_attribute(component_id, variable_id, attribute_enum); if ((not attribute_opt) or (not attribute_opt->value)) { return GetVariableStatusEnum::NotSupportedAttributeType; @@ -253,11 +253,11 @@ SetVariableStatusEnum DeviceModel::set_value(const Component& component, const V const AttributeEnum& attribute_enum, const std::string& value, const std::string& source, bool allow_read_only) { - if (this->device_model.find(component) == this->device_model.end()) { + if (this->device_model_map.find(component) == this->device_model_map.end()) { return SetVariableStatusEnum::UnknownComponent; } - auto variable_map = this->device_model[component]; + auto variable_map = this->device_model_map[component]; if (variable_map.find(variable) == variable_map.end()) { return SetVariableStatusEnum::UnknownVariable; @@ -274,7 +274,7 @@ SetVariableStatusEnum DeviceModel::set_value(const Component& component, const V return SetVariableStatusEnum::Rejected; } - const auto attribute = this->storage->get_variable_attribute(component, variable, attribute_enum); + const auto attribute = this->device_model->get_variable_attribute(component, variable, attribute_enum); if (!attribute.has_value()) { return SetVariableStatusEnum::NotSupportedAttributeType; @@ -287,7 +287,7 @@ SetVariableStatusEnum DeviceModel::set_value(const Component& component, const V } const auto success = - this->storage->set_variable_attribute_value(component, variable, attribute_enum, value, source); + this->device_model->set_variable_attribute_value(component, variable, attribute_enum, value, source); // Only trigger for actual values if ((attribute_enum == AttributeEnum::Actual) && success && variable_listener) { @@ -310,9 +310,9 @@ SetVariableStatusEnum DeviceModel::set_value(const Component& component, const V return success ? SetVariableStatusEnum::Accepted : SetVariableStatusEnum::Rejected; }; -DeviceModel::DeviceModel(std::unique_ptr device_model_storage) : - storage{std::move(device_model_storage)} { - this->device_model = this->storage->get_device_model(); +DeviceModel::DeviceModel(std::unique_ptr device_model_storage_interface) : + device_model{std::move(device_model_storage_interface)} { + this->device_model_map = this->device_model->get_device_model(); } SetVariableStatusEnum DeviceModel::set_read_only_value(const Component& component, const Variable& variable, @@ -328,8 +328,8 @@ SetVariableStatusEnum DeviceModel::set_read_only_value(const Component& componen std::optional DeviceModel::get_variable_meta_data(const Component& component, const Variable& variable) { - if (this->device_model.count(component) and this->device_model.at(component).count(variable)) { - return this->device_model.at(component).at(variable); + if (this->device_model_map.count(component) and this->device_model_map.at(component).count(variable)) { + return this->device_model_map.at(component).at(variable); } else { return std::nullopt; } @@ -338,7 +338,7 @@ std::optional DeviceModel::get_variable_meta_data(const Compon std::vector DeviceModel::get_base_report_data(const ReportBaseEnum& report_base) { std::vector report_data_vec; - for (auto const& [component, variable_map] : this->device_model) { + for (auto const& [component, variable_map] : this->device_model_map) { for (auto const& [variable, variable_meta_data] : variable_map) { ReportData report_data; @@ -347,8 +347,8 @@ std::vector DeviceModel::get_base_report_data(const ReportBaseEnum& ComponentVariable cv = {component, std::nullopt, variable}; - // request the variable attribute from the device model storage - const auto variable_attributes = this->storage->get_variable_attributes(component, variable); + // request the variable attribute from the device model + const auto variable_attributes = this->device_model->get_variable_attributes(component, variable); // iterate over possibly (Actual, Target, MinSet, MaxSet) for (const auto& variable_attribute : variable_attributes) { @@ -381,7 +381,7 @@ DeviceModel::get_custom_report_data(const std::optional>& component_criteria) { std::vector report_data_vec; - for (auto const& [component, variable_map] : this->device_model) { + for (auto const& [component, variable_map] : this->device_model_map) { if (!component_criteria.has_value() or component_criteria_match(component, component_criteria.value())) { for (auto const& [variable, variable_meta_data] : variable_map) { @@ -391,8 +391,8 @@ DeviceModel::get_custom_report_data(const std::optionalstorage->get_variable_attributes(component, variable); + // request the variable attribute from the device model + const auto variable_attributes = this->device_model->get_variable_attributes(component, variable); for (const auto& variable_attribute : variable_attributes) { report_data.variableAttribute.push_back(variable_attribute); @@ -412,12 +412,12 @@ DeviceModel::get_custom_report_data(const std::optional& evse_connector_structure) { EVLOG_debug << "Checking integrity of device model in storage"; try { - this->storage->check_integrity(); + this->device_model->check_integrity(); int32_t nr_evse_components = 0; std::map evse_id_nr_connector_components; - for (const auto& [component, variable_map] : this->device_model) { + for (const auto& [component, variable_map] : this->device_model_map) { if (component.name == "EVSE") { nr_evse_components++; } else if (component.name == "Connector") { @@ -431,34 +431,33 @@ void DeviceModel::check_integrity(const std::map& evse_connect // check if number of EVSE in the device model matches the configured number if (nr_evse_components != evse_connector_structure.size()) { - throw DeviceModelStorageError("Number of EVSE configured in device model is incompatible with number of " - "configured EVSEs of the ChargePoint"); + throw DeviceModelError("Number of EVSE configured in device model is incompatible with number of " + "configured EVSEs of the ChargePoint"); } for (const auto [evse_id, nr_of_connectors] : evse_connector_structure) { // check if number of Cpnnectors for this EVSE in the device model matches the configured number if (evse_id_nr_connector_components[evse_id] != nr_of_connectors) { - throw DeviceModelStorageError( - "Number of Connectors configured in device model is incompatible with number " - "of configured Connectors of the ChargePoint"); + throw DeviceModelError("Number of Connectors configured in device model is incompatible with number " + "of configured Connectors of the ChargePoint"); } // check if all relevant EVSE and Connector components can be found EVSE evse = {evse_id}; Component evse_component = {"EVSE", std::nullopt, evse}; - if (!this->device_model.count(evse_component)) { - throw DeviceModelStorageError("Could not find required EVSE component in device model"); + if (!this->device_model_map.count(evse_component)) { + throw DeviceModelError("Could not find required EVSE component in device model"); } for (size_t connector_id = 1; connector_id <= nr_of_connectors; connector_id++) { evse_component.name = "Connector"; evse_component.evse.value().connectorId = connector_id; - if (!this->device_model.count(evse_component)) { - throw DeviceModelStorageError("Could not find required Connector component in device model"); + if (!this->device_model_map.count(evse_component)) { + throw DeviceModelError("Could not find required Connector component in device model"); } } } - } catch (const DeviceModelStorageError& e) { - EVLOG_error << "Integrity check in Device Model storage failed:" << e.what(); + } catch (const DeviceModelError& e) { + EVLOG_error << "Integrity check in Device Model failed:" << e.what(); throw e; } } @@ -468,7 +467,7 @@ bool DeviceModel::update_monitor_reference(int32_t monitor_id, const std::string VariableMonitoringMeta* monitor_meta = nullptr; // See if this is a trivial delta monitor and that it exists - for (auto& [component, variable_map] : this->device_model) { + for (auto& [component, variable_map] : this->device_model_map) { bool found_monitor_id = false; for (auto& [variable, variable_meta_data] : variable_map) { @@ -500,7 +499,7 @@ bool DeviceModel::update_monitor_reference(int32_t monitor_id, const std::string if (found_monitor) { try { - if (this->storage->update_monitoring_reference(monitor_id, reference_value)) { + if (this->device_model->update_monitoring_reference(monitor_id, reference_value)) { // Update value in-memory too monitor_meta->reference_value = reference_value; return true; @@ -510,7 +509,7 @@ bool DeviceModel::update_monitor_reference(int32_t monitor_id, const std::string } } catch (const DatabaseException& e) { EVLOG_error << "Exception while updating trivial delta monitor reference with ID: " << monitor_id; - throw DeviceModelStorageError(e.what()); + throw DeviceModelError(e.what()); } } else { EVLOG_warning << "Could not find trivial delta monitor with ID: " << monitor_id @@ -546,7 +545,7 @@ std::vector DeviceModel::set_monitors(const std::vectordevice_model) { + for (const auto& [component, variable_map] : this->device_model_map) { for (const auto& [variable, variable_meta] : variable_map) { if (variable_meta.monitors.find(request.id.value()) != std::end(variable_meta.monitors)) { id_found = true; @@ -566,9 +565,9 @@ std::vector DeviceModel::set_monitors(const std::vectordevice_model.find(request.component); + auto component_it = this->device_model_map.find(request.component); - if (component_it == this->device_model.end()) { + if (component_it == this->device_model_map.end()) { // N04.FR.16 if (request_has_id && id_found) { result.status = SetMonitoringStatusEnum::Rejected; @@ -580,7 +579,7 @@ std::vector DeviceModel::set_monitors(const std::vectordevice_model[request.component]; + auto& variable_map = this->device_model_map[request.component]; auto variable_it = variable_map.find(request.variable); if (variable_it == variable_map.end()) { @@ -655,14 +654,14 @@ std::vector DeviceModel::set_monitors(const std::vectorstorage->set_monitoring_data(request, type); + auto monitor_meta = this->device_model->set_monitoring_data(request, type); if (monitor_meta.has_value()) { // N07.FR.11 // In case of an existing monitor update if (request_has_id && monitor_update_listener) { - auto attribute = this->storage->get_variable_attribute(component_it->first, variable_it->first, - AttributeEnum::Actual); + auto attribute = this->device_model->get_variable_attribute(component_it->first, variable_it->first, + AttributeEnum::Actual); if (attribute.has_value()) { static std::string empty_value{}; @@ -686,7 +685,7 @@ std::vector DeviceModel::set_monitors(const std::vector DeviceModel::set_monitors(const std::vector DeviceModel::get_periodic_monitors() { std::vector periodics; - for (const auto& [component, variable_map] : this->device_model) { + for (const auto& [component, variable_map] : this->device_model_map) { for (const auto& [variable, variable_metadata] : variable_map) { std::vector monitors; @@ -725,11 +724,11 @@ std::vector DeviceModel::get_monitors(const std::vectordevice_model.find(component_variable.component) == this->device_model.end()) { + if (this->device_model_map.find(component_variable.component) == this->device_model_map.end()) { continue; } - auto& variable_map = this->device_model[component_variable.component]; + auto& variable_map = this->device_model_map[component_variable.component]; // N02.FR.16 - if variable is missing, report all existing variables inside that component if (component_variable.variable.has_value() == false) { @@ -777,7 +776,7 @@ std::vector DeviceModel::get_monitors(const std::vectordevice_model) { + for (const auto& [component, variable_map] : this->device_model_map) { for (const auto& [variable, variable_metadata] : variable_map) { std::vector monitors; @@ -811,10 +810,10 @@ std::vector DeviceModel::clear_monitors(const std::vector clear_monitor_res.id = id; try { - auto clear_result = this->storage->clear_variable_monitor(id, allow_protected); + auto clear_result = this->device_model->clear_variable_monitor(id, allow_protected); if (clear_result == ClearMonitoringStatusEnum::Accepted) { // Clear from memory too - for (auto& [component, variable_map] : this->device_model) { + for (auto& [component, variable_map] : this->device_model_map) { for (auto& [variable, variable_metadata] : variable_map) { variable_metadata.monitors.erase(static_cast(id)); } @@ -824,7 +823,7 @@ std::vector DeviceModel::clear_monitors(const std::vector clear_monitor_res.status = clear_result; } catch (const DatabaseException& e) { EVLOG_error << "Clear monitors failed:" << e.what(); - throw DeviceModelStorageError(e.what()); + throw DeviceModelError(e.what()); } clear_monitors_vec.push_back(clear_monitor_res); @@ -835,10 +834,10 @@ std::vector DeviceModel::clear_monitors(const std::vector int32_t DeviceModel::clear_custom_monitors() { try { - int32_t deleted = this->storage->clear_custom_variable_monitors(); + int32_t deleted = this->device_model->clear_custom_variable_monitors(); // Clear from memory too - for (auto& [component, variable_map] : this->device_model) { + for (auto& [component, variable_map] : this->device_model_map) { for (auto& [variable, variable_metadata] : variable_map) { // Delete while iterating all custom monitors for (auto it = variable_metadata.monitors.begin(); it != variable_metadata.monitors.end();) { @@ -854,7 +853,7 @@ int32_t DeviceModel::clear_custom_monitors() { return deleted; } catch (const DatabaseException& e) { EVLOG_error << "Clear custom monitors failed:" << e.what(); - throw DeviceModelStorageError(e.what()); + throw DeviceModelError(e.what()); } return 0; diff --git a/lib/ocpp/v201/device_model_storage_sqlite.cpp b/lib/ocpp/v201/device_model_storage_sqlite.cpp index 90aff34e9..0d6146506 100644 --- a/lib/ocpp/v201/device_model_storage_sqlite.cpp +++ b/lib/ocpp/v201/device_model_storage_sqlite.cpp @@ -22,8 +22,7 @@ DeviceModelStorageSqlite::DeviceModelStorageSqlite(const fs::path& db_path, cons const fs::path& config_path, const bool init_db) { if (init_db) { if (db_path.empty() || migration_files_path.empty() || config_path.empty()) { - EVLOG_AND_THROW( - DeviceModelStorageError("Can not initialize device model storage: one of the paths is empty.")); + EVLOG_AND_THROW(DeviceModelError("Can not initialize device model storage: one of the paths is empty.")); } InitDeviceModelDb init_device_model_db(db_path, migration_files_path); init_device_model_db.initialize_database(config_path, false); @@ -97,7 +96,7 @@ DeviceModelMap DeviceModelStorageSqlite::get_device_model() { std::string select_query = "SELECT c.NAME, c.EVSE_ID, c.CONNECTOR_ID, c.INSTANCE, v.NAME, v.INSTANCE, vc.DATATYPE_ID, " - "vc.SUPPORTS_MONITORING, vc.UNIT, vc.MIN_LIMIT, vc.MAX_LIMIT, vc.VALUES_LIST " + "vc.SUPPORTS_MONITORING, vc.UNIT, vc.MIN_LIMIT, vc.MAX_LIMIT, vc.VALUES_LIST, v.SOURCE " "FROM COMPONENT c " "JOIN VARIABLE v ON c.ID = v.COMPONENT_ID " "JOIN VARIABLE_CHARACTERISTICS vc ON vc.VARIABLE_ID = v.ID"; @@ -130,6 +129,7 @@ DeviceModelMap DeviceModelStorageSqlite::get_device_model() { } VariableCharacteristics characteristics; + VariableMetaData meta_data; characteristics.dataType = static_cast(select_stmt->column_int(6)); characteristics.supportsMonitoring = select_stmt->column_int(7) != 0; @@ -149,7 +149,10 @@ DeviceModelMap DeviceModelStorageSqlite::get_device_model() { characteristics.valuesList = select_stmt->column_text(11); } - VariableMetaData meta_data; + if (select_stmt->column_type(12) != SQLITE_NULL) { + meta_data.source = select_stmt->column_text(12); + } + meta_data.characteristics = characteristics; // Query all monitors for this variable @@ -481,7 +484,7 @@ void DeviceModelStorageSqlite::check_integrity() { << "/" << select_stmt->column_text_nullable(4).value_or("") << ")" << std::endl; } while (select_stmt->step() == SQLITE_ROW); - throw DeviceModelStorageError(error.str()); + throw DeviceModelError(error.str()); } } diff --git a/lib/ocpp/v201/enums.cpp b/lib/ocpp/v201/enums.cpp index 0293a9ba6..939ad3551 100644 --- a/lib/ocpp/v201/enums.cpp +++ b/lib/ocpp/v201/enums.cpp @@ -23,6 +23,7 @@ VariableMonitorType string_to_variable_monitor_type(const std::string& s) { throw std::out_of_range("Provided string " + s + " could not be converted to enum of type VariableMonitorType"); } + } // namespace conversions -} // namespace ocpp::v201 \ No newline at end of file +} // namespace ocpp::v201 diff --git a/lib/ocpp/v201/init_device_model_db.cpp b/lib/ocpp/v201/init_device_model_db.cpp index d236eed65..0b36a1be8 100644 --- a/lib/ocpp/v201/init_device_model_db.cpp +++ b/lib/ocpp/v201/init_device_model_db.cpp @@ -362,14 +362,14 @@ void InitDeviceModelDb::update_variable_characteristics(const VariableCharacteri void InitDeviceModelDb::insert_variable(const DeviceModelVariable& variable, const uint64_t& component_id) { static const std::string statement = - "INSERT OR REPLACE INTO VARIABLE (NAME, INSTANCE, COMPONENT_ID, REQUIRED) VALUES " - "(@name, @instance, @component_id, @required)"; + "INSERT OR REPLACE INTO VARIABLE (NAME, INSTANCE, COMPONENT_ID, REQUIRED, SOURCE) VALUES " + "(@name, @instance, @component_id, @required, @source)"; std::unique_ptr insert_variable_statement; try { insert_variable_statement = this->database->new_statement(statement); - } catch (const common::QueryExecutionException&) { - throw InitDeviceModelDbError("Could not create statement " + statement); + } catch (const common::QueryExecutionException& e) { + throw InitDeviceModelDbError("Could not create statement " + statement + ": " + e.what()); } insert_variable_statement->bind_text("@name", variable.name, ocpp::common::SQLiteString::Transient); @@ -385,6 +385,12 @@ void InitDeviceModelDb::insert_variable(const DeviceModelVariable& variable, con const uint8_t required_int = (variable.required ? 1 : 0); insert_variable_statement->bind_int("@required", required_int); + if (variable.source.has_value()) { + insert_variable_statement->bind_text("@source", variable.source.value()); + } else { + insert_variable_statement->bind_null("@source"); + } + if (insert_variable_statement->step() != SQLITE_DONE) { throw InitDeviceModelDbError("Variable " + variable.name + " could not be inserted: " + std::string(this->database->get_error_message())); @@ -405,8 +411,8 @@ void InitDeviceModelDb::update_variable(const DeviceModelVariable& variable, con } static const std::string update_variable_statement = - "UPDATE VARIABLE SET NAME=@name, INSTANCE=@instance, COMPONENT_ID=@component_id, REQUIRED=@required WHERE " - "ID=@variable_id"; + "UPDATE VARIABLE SET NAME=@name, INSTANCE=@instance, COMPONENT_ID=@component_id, REQUIRED=@required, " + "SOURCE=@source WHERE ID=@variable_id"; std::unique_ptr update_statement; try { @@ -428,6 +434,12 @@ void InitDeviceModelDb::update_variable(const DeviceModelVariable& variable, con const uint8_t required_int = (variable.required ? 1 : 0); update_statement->bind_int("@required", required_int); + if (variable.source.has_value()) { + update_statement->bind_text("@source", variable.source.value(), ocpp::common::SQLiteString::Transient); + } else { + update_statement->bind_null("@source"); + } + if (update_statement->step() != SQLITE_DONE) { throw InitDeviceModelDbError("Could not update variable " + variable.name + ": " + std::string(this->database->get_error_message())); @@ -804,7 +816,8 @@ std::map> InitDeviceModelDb::get_ "c.ID, c.NAME, c.INSTANCE, c.EVSE_ID, c.CONNECTOR_ID, " "v.ID, v.NAME, v.INSTANCE, v.REQUIRED, " "vc.ID, vc.DATATYPE_ID, vc.MAX_LIMIT, vc.MIN_LIMIT, vc.SUPPORTS_MONITORING, vc.UNIT, vc.VALUES_LIST, " - "va.ID, va.MUTABILITY_ID, va.PERSISTENT, va.CONSTANT, va.TYPE_ID, va.VALUE, va.VALUE_SOURCE " + "va.ID, va.MUTABILITY_ID, va.PERSISTENT, va.CONSTANT, va.TYPE_ID, va.VALUE, va.VALUE_SOURCE," + "v.SOURCE " "FROM " "COMPONENT c " "JOIN VARIABLE v ON v.COMPONENT_ID = c.ID " @@ -895,6 +908,14 @@ std::map> InitDeviceModelDb::get_ attribute.variable_attribute.value = select_statement->column_text_nullable(21); attribute.value_source = select_statement->column_text_nullable(22); + if (select_statement->column_type(23) != SQLITE_NULL) { + try { + variable->source = select_statement->column_text(23); + } catch (const std::out_of_range& e) { + EVLOG_error << e.what() << ": Variable Source will not be set (so default will be used)"; + } + } + variable->attributes.push_back(attribute); // Query all monitors @@ -1209,6 +1230,10 @@ void from_json(const json& j, DeviceModelVariable& c) { c.default_actual_value = get_string_value_from_json(default_value); } + if (j.contains("source")) { + c.source = j.at("source"); + } + if (j.contains("monitors")) { if (!c.characteristics.supportsMonitoring) { const std::string error = @@ -1284,6 +1309,11 @@ static void check_integrity(const std::map check_integrity_required_value(const DeviceModelVariable& variable) { + // Required value has a different source so it is correct that the value is not set here. + if (variable.source.has_value() && variable.source != "OCPP") { + return std::nullopt; + } + // For now, we assume that if a variable is required, it should have an 'Actual' value. But the spec is not clear // about this. There are some implicit signs in favor of having always at least an 'Actual' value, but it is not // explicitly stated. Robert asked OCA about this. diff --git a/lib/ocpp/v201/monitoring_updater.cpp b/lib/ocpp/v201/monitoring_updater.cpp index d1e7594f9..f7ff5c8e2 100644 --- a/lib/ocpp/v201/monitoring_updater.cpp +++ b/lib/ocpp/v201/monitoring_updater.cpp @@ -265,7 +265,7 @@ void MonitoringUpdater::evaluate_monitor(const VariableMonitoringMeta& monitor_m if (!this->device_model->update_monitor_reference(monitor_id, value_current)) { EVLOG_warning << "Could not update delta monitor: " << monitor_id << " reference!"; } - } catch (const DeviceModelStorageError& e) { + } catch (const DeviceModelError& e) { EVLOG_error << "Could not update delta monitor reference with exception: " << e.what(); } } diff --git a/tests/config/v201/resources/component_config/standardized/UnitTestCtrlr.json b/tests/config/v201/resources/component_config/standardized/UnitTestCtrlr.json index e282268b6..67f3a5b4f 100644 --- a/tests/config/v201/resources/component_config/standardized/UnitTestCtrlr.json +++ b/tests/config/v201/resources/component_config/standardized/UnitTestCtrlr.json @@ -8,6 +8,7 @@ "properties": { "UnitTestPropertyA": { "variable_name": "UnitTestPropertyAName", + "source": "OCPP", "characteristics": { "supportsMonitoring": true, "dataType": "boolean" diff --git a/tests/lib/ocpp/v201/mocks/device_model_storage_mock.hpp b/tests/lib/ocpp/v201/mocks/device_model_storage_interface_mock.hpp similarity index 90% rename from tests/lib/ocpp/v201/mocks/device_model_storage_mock.hpp rename to tests/lib/ocpp/v201/mocks/device_model_storage_interface_mock.hpp index e882a66ad..9dcf872bd 100644 --- a/tests/lib/ocpp/v201/mocks/device_model_storage_mock.hpp +++ b/tests/lib/ocpp/v201/mocks/device_model_storage_interface_mock.hpp @@ -5,10 +5,10 @@ #include -#include "ocpp/v201/device_model_storage.hpp" +#include "ocpp/v201/device_model_storage_interface.hpp" namespace ocpp::v201 { -class DeviceModelStorageMock : public DeviceModelStorage { +class DeviceModelStorageMock : public DeviceModelStorageInterface { public: MOCK_METHOD(DeviceModelMap, get_device_model, ()); MOCK_METHOD(std::optional, get_variable_attribute, diff --git a/tests/lib/ocpp/v201/test_charge_point.cpp b/tests/lib/ocpp/v201/test_charge_point.cpp index c96fd7673..f42331e6a 100644 --- a/tests/lib/ocpp/v201/test_charge_point.cpp +++ b/tests/lib/ocpp/v201/test_charge_point.cpp @@ -538,13 +538,13 @@ TEST_F(ChargePointConstructorTestFixtureV201, CreateChargePoint_InitializeInCorr } TEST_F(ChargePointConstructorTestFixtureV201, - CreateChargePoint_EVSEConnectorStructureDefinedBadly_ThrowsDeviceModelStorageError) { + CreateChargePoint_EVSEConnectorStructureDefinedBadly_ThrowsDeviceModelError) { configure_callbacks_with_mocks(); auto evse_connector_structure = std::map(); EXPECT_THROW(ocpp::v201::ChargePoint(evse_connector_structure, device_model, database_handler, create_message_queue(database_handler), "/tmp", evse_security, callbacks), - DeviceModelStorageError); + DeviceModelError); } TEST_F(ChargePointConstructorTestFixtureV201, CreateChargePoint_MissingDeviceModel_ThrowsInvalidArgument) { diff --git a/tests/lib/ocpp/v201/test_composite_schedule.cpp b/tests/lib/ocpp/v201/test_composite_schedule.cpp index 654f3032f..b8d5e13eb 100644 --- a/tests/lib/ocpp/v201/test_composite_schedule.cpp +++ b/tests/lib/ocpp/v201/test_composite_schedule.cpp @@ -25,7 +25,6 @@ #include #include -#include #include #include #include diff --git a/tests/lib/ocpp/v201/test_device_model_storage_sqlite.cpp b/tests/lib/ocpp/v201/test_device_model_storage_sqlite.cpp index 2197fc600..28a0da2ba 100644 --- a/tests/lib/ocpp/v201/test_device_model_storage_sqlite.cpp +++ b/tests/lib/ocpp/v201/test_device_model_storage_sqlite.cpp @@ -25,7 +25,7 @@ TEST_F(DeviceModelStorageSQLiteTest, test_check_integrity_valid) { TEST_F(DeviceModelStorageSQLiteTest, test_check_integrity_invalid) { auto dm_storage = DeviceModelStorageSqlite(INVALID_DEVICE_MODEL_DATABASE); - EXPECT_THROW(dm_storage.check_integrity(), DeviceModelStorageError); + EXPECT_THROW(dm_storage.check_integrity(), DeviceModelError); } } // namespace v201 diff --git a/tests/lib/ocpp/v201/test_init_device_model_db.cpp b/tests/lib/ocpp/v201/test_init_device_model_db.cpp index cd41175ec..74fbbd0cc 100644 --- a/tests/lib/ocpp/v201/test_init_device_model_db.cpp +++ b/tests/lib/ocpp/v201/test_init_device_model_db.cpp @@ -87,7 +87,8 @@ class InitDeviceModelDbTest : public DatabaseTestingUtils { /// bool variable_exists(const std::string& component_name, const std::optional& component_instance, const std::optional& component_evse_id, const std::optional& component_connector_id, - const std::string& variable_name, const std::optional& variable_instance); + const std::string& variable_name, const std::optional& variable_instance, + const std::optional source = std::nullopt); /// /// \brief Check if variable characteristics exists in the database. @@ -243,7 +244,7 @@ TEST_F(InitDeviceModelDbTest, init_db) { EXPECT_TRUE(characteristics_exists("UnitTestCtrlr", std::nullopt, 2, 3, "UnitTestPropertyAName", std::nullopt, DataEnum::boolean, std::nullopt, std::nullopt, true, std::nullopt, std::nullopt)); - EXPECT_TRUE(variable_exists("UnitTestCtrlr", std::nullopt, 2, 3, "UnitTestPropertyAName", std::nullopt)); + EXPECT_TRUE(variable_exists("UnitTestCtrlr", std::nullopt, 2, 3, "UnitTestPropertyAName", std::nullopt, "OCPP")); EXPECT_TRUE(variable_exists("UnitTestCtrlr", std::nullopt, 2, 3, "UnitTestPropertyBName", std::nullopt)); EXPECT_TRUE(variable_exists("UnitTestCtrlr", std::nullopt, 2, 3, "UnitTestPropertyCName", std::nullopt)); @@ -589,7 +590,8 @@ bool InitDeviceModelDbTest::variable_exists(const std::string& component_name, const std::optional& component_evse_id, const std::optional& component_connector_id, const std::string& variable_name, - const std::optional& variable_instance) { + const std::optional& variable_instance, + const std::optional source) { static const std::string select_variable_statement = "SELECT ID " "FROM VARIABLE v " "WHERE v.COMPONENT_ID=(" @@ -600,7 +602,8 @@ bool InitDeviceModelDbTest::variable_exists(const std::string& component_name, "AND c.EVSE_ID IS @evse_id " "AND c.CONNECTOR_ID IS @connector_id) " "AND v.NAME=@variable_name " - "AND v.INSTANCE IS @variable_instance"; + "AND v.INSTANCE IS @variable_instance " + "AND v.source IS @variable_source"; std::unique_ptr statement = this->database->new_statement(select_variable_statement); @@ -632,6 +635,12 @@ bool InitDeviceModelDbTest::variable_exists(const std::string& component_name, statement->bind_null("@variable_instance"); } + if (source.has_value()) { + statement->bind_text("@variable_source", source.value(), ocpp::common::SQLiteString::Transient); + } else { + statement->bind_null("@variable_source"); + } + if (statement->step() == SQLITE_ERROR) { return false; } diff --git a/tests/lib/ocpp/v201/test_smart_charging_handler.cpp b/tests/lib/ocpp/v201/test_smart_charging_handler.cpp index ace74ce2d..8b7a199d5 100644 --- a/tests/lib/ocpp/v201/test_smart_charging_handler.cpp +++ b/tests/lib/ocpp/v201/test_smart_charging_handler.cpp @@ -19,7 +19,7 @@ #include #include -#include +#include #include #include #include diff --git a/tests/resources/unittest_device_model.db b/tests/resources/unittest_device_model.db index 5150f035685c52f8d8352f941fec42d81b046a85..f1b3d8b8ab41769c1f867ed72ed538d5b70b231e 100644 GIT binary patch delta 329 zcmZo@U}DZURn3V6a_Y>YWCOM0h&XZEg*X4|XA=zTwRD{>S}KEx$Ic{Q`Z zdh)@3;myDL*#tH-e)!Kn`Tu$L&1@Tf*$eOjwXyKe02zIZebYz*cMoE($auNSqFW`b}+^D;|9Qj1Fhit-Cmi%Ke; S{1S6hr5VAJoamB{AV~m_`BsMj diff --git a/tests/resources/unittest_device_model_missing_required.db b/tests/resources/unittest_device_model_missing_required.db index 503769c9eeab231beab5c8880c36b34539a166e8..fc5ea45a8aa62b60b0c454e530d6215506037b48 100644 GIT binary patch delta 592 zcmZ`$&r3o<5MF!udM~uCE=i#*?+{8BL`2tC8|9I2pRM~MPm$mu`bkOANg+CQF!Rze z{{jimf_3TIrMvwX9ox-BB^nrp`DVXwW_BtatcbX^PMDUx zX_+BtzkTn=`ckuAtat&wgq!XX75&qOK;MQ4^0UD_HNTwjAU2%HPBd{MeeFD&YEMH8PLiP*7f^X8~Oc z`}#ks)tl9jx|s4`YkgD~H(I3A#kD8$@U9yTgg@~MZb-p8-iv4kT1TeOK;FqgNfa+i i+4Ix0;(6&h$@Y(nT4e0MdP3F5!jdWZrum0YRs9=7oRXLT delta 431 zcmZp8z}#?vd4jYc9|Hpe2*Uu^L>*%uJ_fzK9lZQM7h2n(!=<1w*^ym37Fi(3H89jO$Q79X0E z<4`&CasKr{kEZZXF6)-#16cvzTa8DZXJ-h8!R+hNiJHjX6>{Ezq# z^DhBvn#La^%goMT&cMmY5t^4-5|Ub65>S+1kXlqy>Fk%7o60Q9ghSd1B+Vql2$tsL KMAz*Ik_G^5kaaQu From ac68603f1f6b70d82fb49774eafb110778ded7d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piet=20G=C3=B6mpel?= <37657534+Pietfried@users.noreply.github.com> Date: Thu, 14 Nov 2024 12:10:45 +0100 Subject: [PATCH 5/5] Added fully featured v16 config as an example (#829) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added fully featured v16 config as an example --------- Signed-off-by: Piet Gömpel Signed-off-by: Piet Gömpel <37657534+Pietfried@users.noreply.github.com> --- config/v16/config-full.json | 180 ++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 config/v16/config-full.json diff --git a/config/v16/config-full.json b/config/v16/config-full.json new file mode 100644 index 000000000..4dfa8435a --- /dev/null +++ b/config/v16/config-full.json @@ -0,0 +1,180 @@ +{ + "Internal": { + "ChargePointId": "cp001", + "CentralSystemURI": "127.0.0.1:8180/steve/websocket/CentralSystemService/", + "ChargeBoxSerialNumber": "cp001", + "ChargePointModel": "Yeti", + "ChargePointSerialNumber": "cp001", + "ChargePointVendor": "Pionix", + "FirmwareVersion": "0.1", + "ICCID": "891004234814455936F", + "IMSI": "262 01 9876543210", + "MeterSerialNumber": "123-456-789", + "MeterType": "AC", + "SupportedCiphers12": [ + "ECDHE-ECDSA-AES128-GCM-SHA256", + "ECDHE-ECDSA-AES256-GCM-SHA384", + "AES128-GCM-SHA256", + "AES256-GCM-SHA384" + ], + "SupportedCiphers13": [ + "TLS_AES_256_GCM_SHA384", + "TLS_AES_128_GCM_SHA256" + ], + "UseTPM": false, + "RetryBackoffRandomRange": 10, + "RetryBackoffRepeatTimes": 3, + "RetryBackoffWaitMinimum": 3, + "AuthorizeConnectorZeroOnConnectorOne": true, + "LogMessages": true, + "LogMessagesFormat": [ + "log", + "html", + "session_logging", + "security" + ], + "LogRotation": false, + "LogRotationDateSuffix": false, + "LogRotationMaximumFileSize": 0, + "LogRotationMaximumFileCount": 0, + "SupportedChargingProfilePurposeTypes": [ + "ChargePointMaxProfile", + "TxDefaultProfile", + "TxProfile" + ], + "MaxCompositeScheduleDuration": 31536000, + "WebsocketPingPayload": "Hello from EVerest!", + "WebsocketPongTimeout": 5, + "UseSslDefaultVerifyPaths": true, + "VerifyCsmsCommonName": true, + "VerifyCsmsAllowWildcards": true, + "OcspRequestInterval": 604800, + "SeccLeafSubjectCommonName": "cp001", + "SeccLeafSubjectCountry": "DE", + "SeccLeafSubjectOrganization": "Pionix", + "ConnectorEvseIds": "DE*PNX*100001,DE*PNX*100002", + "AllowChargingProfileWithoutStartSchedule": false, + "WaitForStopTransactionsOnResetTimeout": 60, + "QueueAllMessages": true, + "MessageTypesDiscardForQueueing": "Heartbeat", + "MessageQueueSizeThreshold": 5000, + "SupportedMeasurands": "Energy.Active.Import.Register,Energy.Active.Export.Register,Power.Active.Import,Voltage,Current.Import,Frequency,Current.Offered,Power.Offered,SoC", + "MaxMessageSize": 65000, + "TLSKeylogFile": "/tmp/ocpp_tls_keylog.txt", + "EnableTLSKeylog": false + }, + "Core": { + "AllowOfflineTxForUnknownId": true, + "AuthorizationCacheEnabled": true, + "AuthorizeRemoteTxRequests": true, + "BlinkRepeat": 0, + "ClockAlignedDataInterval": 900, + "ConnectionTimeOut": 120, + "ConnectorPhaseRotation": "RST0.RST,1.RST,2.RTS", + "ConnectorPhaseRotationMaxLength": 100, + "GetConfigurationMaxKeys": 1024, + "HeartbeatInterval": 900, + "LightIntensity": 0, + "LocalAuthorizeOffline": true, + "LocalPreAuthorize": true, + "MaxEnergyOnInvalidId": 500, + "MeterValuesAlignedData": "Energy.Active.Import.Register", + "MeterValuesAlignedDataMaxLength": 1024, + "MeterValuesSampledData": "Energy.Active.Import.Register", + "MeterValuesSampledDataMaxLength": 1024, + "MeterValueSampleInterval": 300, + "MinimumStatusDuration": 2, + "NumberOfConnectors": 2, + "ResetRetries": 1, + "StopTransactionOnEVSideDisconnect": true, + "StopTransactionOnInvalidId": true, + "StopTxnAlignedData": "Energy.Active.Import.Register", + "StopTxnAlignedDataMaxLength": 1024, + "StopTxnSampledData": "Energy.Active.Import.Register", + "StopTxnSampledDataMaxLength": 1024, + "SupportedFeatureProfiles": "Core,FirmwareManagement,RemoteTrigger,Reservation,LocalAuthListManagement,SmartCharging", + "SupportedFeatureProfilesMaxLength": 1024, + "TransactionMessageAttempts": 1, + "TransactionMessageRetryInterval": 10, + "UnlockConnectorOnEVSideDisconnect": true, + "WebsocketPingInterval": 10 + }, + "LocalAuthListManagement": { + "LocalAuthListEnabled": true, + "LocalAuthListMaxLength": 1024, + "SendLocalListMaxLength": 1024 + }, + "SmartCharging": { + "ChargeProfileMaxStackLevel": 1000, + "ChargingScheduleAllowedChargingRateUnit": "W,A", + "ChargingScheduleMaxPeriods": 1000, + "ConnectorSwitch3to1PhaseSupported": true, + "MaxChargingProfilesInstalled": 1000 + }, + "FirmwareManagement": { + "SupportedFileTransferProtocols": "FTP" + }, + "Reservation": { + "ReserveConnectorZeroSupported": false + }, + "Security": { + "AdditionalRootCertificateCheck": false, + "AuthorizationKey": "DEADBEEFDEADBEEF", + "CertificateSignedMaxChainSize": 10000, + "CertificateStoreMaxLength": 1000, + "CpoName": "Pionix", + "SecurityProfile": 1, + "DisableSecurityEventNotifications": true + }, + "PnC": { + "ISO15118PnCEnabled": true, + "CentralContractValidationAllowed": true, + "CertificateSignedMaxChainSize": 10000, + "CertSigningWaitMinimum": 30, + "CertSigningRepeatTimes": 2, + "CertificateStoreMaxLength": 1000, + "ContractValidationOffline": true + }, + "CostAndPrice": { + "CustomDisplayCostAndPrice": true, + "NumberOfDecimalsForCostValues": 4, + "DefaultPrice": + { + "priceText": "This is the price", + "priceTextOffline": "Show this price text when offline!", + "chargingPrice": + { + "kWhPrice": 3.14, + "hourPrice": 0.42 + } + }, + "DefaultPriceText": + { + "priceTexts": + [ + { + "priceText": "This is the price", + "priceTextOffline": "Show this price text when offline!", + "language": "en" + }, + { + "priceText": "Dit is de prijs", + "priceTextOffline": "Laat dit zien wanneer de charging station offline is!", + "language": "nl" + }, + { + "priceText": "Dette er prisen", + "priceTextOffline": "Vis denne pristeksten når du er frakoblet", + "language": "nb_NO" + } + ] + }, + "TimeOffset": "02:00", + "NextTimeOffsetTransitionDateTime": "2024-01-01T00:00:00", + "TimeOffsetNextTransition": "01:00", + "CustomIdleFeeAfterStop": false, + "MultiLanguageSupportedLanguages": "en, nl, de, nb_NO", + "CustomMultiLanguageMessages": true, + "Language": "en" + } +}