From f108bf3898e0744f6964b3c960badf191108a12c Mon Sep 17 00:00:00 2001 From: "Maaike Zijderveld, iolar" Date: Wed, 4 Sep 2024 16:14:44 +0200 Subject: [PATCH] Changes to the database and distinguish between external and ocpp values. Currently only the 'OCPP' source is accepted. Signed-off-by: Maaike Zijderveld, iolar --- .../standardized/AlignedDataCtrlr.json | 3 ++ .../standardized/ClockCtrlr.json | 1 + .../standardized/SecurityCtrlr.json | 8 +++ .../2_down-variable_source.sql | 2 + .../2_up-variable_source.sql | 2 + include/ocpp/v201/device_model_interface.hpp | 1 + include/ocpp/v201/enums.hpp | 26 ++++++++++ include/ocpp/v201/init_device_model_db.hpp | 2 + lib/ocpp/v201/device_model_storage_sqlite.cpp | 11 +++-- lib/ocpp/v201/enums.cpp | 26 +++++++++- lib/ocpp/v201/init_device_model_db.cpp | 46 +++++++++++++++--- .../standardized/UnitTestCtrlr.json | 1 + .../ocpp/v201/test_init_device_model_db.cpp | 19 ++++++-- tests/resources/unittest_device_model.db | Bin 65536 -> 65536 bytes .../unittest_device_model_missing_required.db | Bin 57344 -> 61440 bytes 15 files changed, 132 insertions(+), 16 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 diff --git a/config/v201/component_config/standardized/AlignedDataCtrlr.json b/config/v201/component_config/standardized/AlignedDataCtrlr.json index 81f340379..da9259617 100644 --- a/config/v201/component_config/standardized/AlignedDataCtrlr.json +++ b/config/v201/component_config/standardized/AlignedDataCtrlr.json @@ -6,6 +6,7 @@ "properties": { "AlignedDataCtrlrEnabled": { "variable_name": "Enabled", + "source": "OCPP", "characteristics": { "supportsMonitoring": true, "dataType": "boolean" @@ -22,6 +23,7 @@ }, "AlignedDataCtrlrAvailable": { "variable_name": "Available", + "source": "OCPP", "characteristics": { "supportsMonitoring": true, "dataType": "boolean" @@ -38,6 +40,7 @@ }, "AlignedDataInterval": { "variable_name": "Interval", + "source": "OCPP", "characteristics": { "unit": "s", "supportsMonitoring": true, diff --git a/config/v201/component_config/standardized/ClockCtrlr.json b/config/v201/component_config/standardized/ClockCtrlr.json index 795f1116a..247d02b5b 100644 --- a/config/v201/component_config/standardized/ClockCtrlr.json +++ b/config/v201/component_config/standardized/ClockCtrlr.json @@ -6,6 +6,7 @@ "properties": { "ClockCtrlrEnabled": { "variable_name": "Enabled", + "source": "OCPP", "characteristics": { "supportsMonitoring": true, "dataType": "boolean" diff --git a/config/v201/component_config/standardized/SecurityCtrlr.json b/config/v201/component_config/standardized/SecurityCtrlr.json index ab13d92f4..e2d718c1e 100644 --- a/config/v201/component_config/standardized/SecurityCtrlr.json +++ b/config/v201/component_config/standardized/SecurityCtrlr.json @@ -22,6 +22,7 @@ }, "AdditionalRootCertificateCheck": { "variable_name": "AdditionalRootCertificateCheck", + "source": "OCPP", "characteristics": { "supportsMonitoring": true, "dataType": "boolean" @@ -38,6 +39,7 @@ }, "BasicAuthPassword": { "variable_name": "BasicAuthPassword", + "source": "OCPP", "characteristics": { "minLimit": 16, "maxLimit": 40, @@ -75,6 +77,7 @@ }, "CertSigningRepeatTimes": { "variable_name": "CertSigningRepeatTimes", + "source": "OCPP", "characteristics": { "supportsMonitoring": true, "dataType": "integer" @@ -92,6 +95,7 @@ }, "CertSigningWaitMinimum": { "variable_name": "CertSigningWaitMinimum", + "source": "OCPP", "characteristics": { "unit": "s", "supportsMonitoring": true, @@ -110,6 +114,7 @@ }, "SecurityCtrlrIdentity": { "variable_name": "Identity", + "source": "OCPP", "characteristics": { "supportsMonitoring": true, "dataType": "string" @@ -127,6 +132,7 @@ }, "MaxCertificateChainSize": { "variable_name": "MaxCertificateChainSize", + "source": "OCPP", "characteristics": { "supportsMonitoring": true, "dataType": "integer" @@ -142,6 +148,7 @@ }, "OrganizationName": { "variable_name": "OrganizationName", + "source": "OCPP", "characteristics": { "supportsMonitoring": true, "dataType": "string" @@ -159,6 +166,7 @@ }, "SecurityProfile": { "variable_name": "SecurityProfile", + "source": "OCPP", "characteristics": { "minLimit": 1, "maxLimit": 3, 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/device_model_interface.hpp b/include/ocpp/v201/device_model_interface.hpp index 3e590a594..1f7c77d0c 100644 --- a/include/ocpp/v201/device_model_interface.hpp +++ b/include/ocpp/v201/device_model_interface.hpp @@ -33,6 +33,7 @@ struct VariableMonitoringPeriodic { struct VariableMetaData { VariableCharacteristics characteristics; std::unordered_map monitors; + std::optional source; }; using VariableMap = std::map; diff --git a/include/ocpp/v201/enums.hpp b/include/ocpp/v201/enums.hpp index 2ca764d79..64eb1df1a 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 { @@ -36,6 +38,30 @@ constexpr int32_t MIN = Danger; constexpr int32_t MAX = Debug; } // namespace MonitoringLevelSeverity +/// +/// \brief The VariableSource enum used for the device model +/// +enum class VariableSource { + OCPP, + EVEREST_CORE +}; + +namespace conversions { +/// +/// \brief Converts the given VariableSource to a string. +/// \param s The source to convert. +/// \return The string representation of VariableSource +/// +std::string variable_source_enum_to_string(const VariableSource s); + +/// +/// \brief Converts the given string representation of VariableSource to VariableSource enum +/// \param s The string to convert +/// \return The VariableSource +/// +VariableSource string_to_variable_source_enum(const std::string& s); +} // namespace conversions + } // namespace v201 } // namespace ocpp diff --git a/include/ocpp/v201/init_device_model_db.hpp b/include/ocpp/v201/init_device_model_db.hpp index 05c02e0f9..cf19531db 100644 --- a/include/ocpp/v201/init_device_model_db.hpp +++ b/include/ocpp/v201/init_device_model_db.hpp @@ -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/lib/ocpp/v201/device_model_storage_sqlite.cpp b/lib/ocpp/v201/device_model_storage_sqlite.cpp index 636b846ec..b2960c6c2 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( - DeviceModelError("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 = conversions::string_to_variable_source_enum(select_stmt->column_text(12)); + } + meta_data.characteristics = characteristics; // Query all monitors for this variable diff --git a/lib/ocpp/v201/enums.cpp b/lib/ocpp/v201/enums.cpp index 0293a9ba6..c3469653d 100644 --- a/lib/ocpp/v201/enums.cpp +++ b/lib/ocpp/v201/enums.cpp @@ -23,6 +23,30 @@ 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"); } + +std::string variable_source_enum_to_string(const VariableSource s) { + switch (s) { + case VariableSource::OCPP: + return "OCPP"; + case VariableSource::EVEREST_CORE: + return "EVEREST_CORE"; + } + + throw std::out_of_range("VariableSource enum is out of range."); +} + +VariableSource string_to_variable_source_enum(const std::string& s) { + if (s == "OCPP") { + return VariableSource::OCPP; + } + + if (s == "EVEREST_CORE") { + return VariableSource::EVEREST_CORE; + } + + throw std::out_of_range("Provided string " + s + " could not be converted to enum of type VariableSource"); +} + } // 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 74fe11bc8..cee8af179 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,13 @@ 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", + conversions::variable_source_enum_to_string(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 +412,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 +435,13 @@ 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", conversions::variable_source_enum_to_string(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 +818,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 +910,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 = conversions::string_to_variable_source_enum(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 +1232,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 = conversions::string_to_variable_source_enum(j.at("source")); + } + if (j.contains("monitors")) { if (!c.characteristics.supportsMonitoring) { const std::string error = @@ -1284,6 +1311,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 != VariableSource::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/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/test_init_device_model_db.cpp b/tests/lib/ocpp/v201/test_init_device_model_db.cpp index 110fca4bd..0836a2f0d 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,8 @@ 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, + VariableSource::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 +591,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 +603,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 +636,13 @@ bool InitDeviceModelDbTest::variable_exists(const std::string& component_name, statement->bind_null("@variable_instance"); } + if (source.has_value()) { + statement->bind_text("@variable_source", conversions::variable_source_enum_to_string(source.value()), + ocpp::common::SQLiteString::Transient); + } else { + statement->bind_null("@variable_source"); + } + if (statement->step() == SQLITE_ERROR) { return false; } 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