Skip to content

Commit 3f367ab

Browse files
committed
Check for required variables.
Signed-off-by: Maaike Zijderveld, iolar <git.mail@iolar.nl>
1 parent ebd13d4 commit 3f367ab

File tree

7 files changed

+220
-39
lines changed

7 files changed

+220
-39
lines changed

include/ocpp/v201/ctrlr_component_variables.hpp

+23-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,29 @@
99
namespace ocpp {
1010
namespace v201 {
1111

12-
std::vector<RequiredComponentVariable> required_variables;
12+
///
13+
/// \brief Required variables per component.
14+
///
15+
/// First value is the 'available' variable from the specific component. Second value is a set of required variables.
16+
/// This makes it possible to check only for the required variables if a component is available.
17+
///
18+
extern std::vector<std::pair<ComponentVariable, std::vector<RequiredComponentVariable>>>
19+
required_component_available_variables;
20+
21+
///
22+
/// \brief Required variables that should always exist, regardless of any available or not available controller.
23+
///
24+
extern std::vector<RequiredComponentVariable> required_variables;
25+
26+
///
27+
/// \brief Required variables of an EVSE.
28+
///
29+
extern std::vector<Variable> required_evse_variables;
30+
31+
///
32+
/// \brief Required variables of a connector.
33+
///
34+
extern std::vector<Variable> required_connector_variables;
1335

1436
namespace ControllerComponents {
1537
extern const Component InternalCtrlr;

include/ocpp/v201/device_model.hpp

+30
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,36 @@ class DeviceModel {
136136
const ocpp::v201::Component& component_,
137137
const struct ocpp::v201::Variable& variable_);
138138

139+
///
140+
/// \brief Helper function to check if a variable has a value.
141+
/// \param component_variable Component variable to check.
142+
/// \param attribute Attribute to check.
143+
///
144+
/// \throws DeviceModelError if variable has no value or value is an empty string.
145+
///
146+
void check_variable_has_value(const ComponentVariable& component_variable,
147+
const AttributeEnum attribute = AttributeEnum::Actual);
148+
149+
///
150+
/// \brief Helper function to check if a required variable has a value.
151+
/// \param required_variable Required component variable to check.
152+
/// \param supported_versions The current supported ocpp versions.
153+
/// \throws DeviceModelError if variable has no value or value is an empty string.
154+
///
155+
void check_required_variable(const RequiredComponentVariable& required_variable,
156+
const std::vector<OcppProtocolVersion>& supported_versions);
157+
158+
///
159+
/// \brief Loop over all required variables to check if they have a value.
160+
///
161+
/// This will check for all required variables from `ctrlr_component_variables.cpp` `required_variables`.
162+
/// It will also check for specific required variables that belong to a specific controller. If a controller is not
163+
/// available, the 'required' variables of that component are not required at this point.
164+
///
165+
/// \throws DeviceModelError if one of the variables does not have a value or value is an empty string.
166+
///
167+
void check_required_variables();
168+
139169
public:
140170
/// \brief Constructor for the device model
141171
/// \param device_model_storage_interface pointer to a device model interface class

include/ocpp/v201/ocpp_types.hpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -2242,8 +2242,8 @@ std::ostream& operator<<(std::ostream& os, const Firmware& k);
22422242

22432243
struct RequiredComponentVariable : ComponentVariable {
22442244
RequiredComponentVariable() : required_for({OcppProtocolVersion::v201, OcppProtocolVersion::v21}) {};
2245-
RequiredComponentVariable(const Component component, const std::optional<CustomData> custom_data,
2246-
const std::optional<Variable> variable,
2245+
RequiredComponentVariable(const Component component, const std::optional<Variable> variable,
2246+
const std::optional<CustomData> custom_data = std::nullopt,
22472247
const std::set<OcppProtocolVersion> required_for = {OcppProtocolVersion::v201,
22482248
OcppProtocolVersion::v21}) :
22492249
ComponentVariable(), required_for(required_for) {

lib/ocpp/v201/ctrlr_component_variables.cpp

+78-6
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,84 @@ const Variable Fallback = {"Fallback"};
3737

3838
namespace ControllerComponentVariables {
3939

40+
std::vector<std::pair<ComponentVariable, std::vector<RequiredComponentVariable>>>
41+
required_component_available_variables{
42+
{AlignedDataCtrlrAvailable,
43+
{AlignedDataInterval, AlignedDataMeasurands, AlignedDataTxEndedInterval, AlignedDataTxEndedMeasurands}},
44+
{LocalAuthListCtrlrAvailable,
45+
{LocalAuthListCtrlrEntries, ItemsPerMessageSendLocalList, BytesPerMessageSendLocalList}},
46+
{SampledDataCtrlrAvailable,
47+
{SampledDataTxEndedMeasurands, SampledDataTxEndedInterval, SampledDataTxStartedMeasurands,
48+
SampledDataTxUpdatedMeasurands, SampledDataTxUpdatedInterval}},
49+
{SmartChargingCtrlrAvailable,
50+
{ChargingProfileMaxStackLevel, ChargingScheduleChargingRateUnit, PeriodsPerSchedule, EntriesChargingProfiles,
51+
LimitChangeSignificance, CompositeScheduleDefaultLimitAmps, CompositeScheduleDefaultLimitWatts,
52+
CompositeScheduleDefaultNumberPhases, SupplyVoltage}},
53+
{TariffCostCtrlrAvailableTariff, {TariffFallbackMessage}},
54+
{TariffCostCtrlrAvailableCost, {TotalCostFallbackMessage, TariffCostCtrlrCurrency}},
55+
{MonitoringCtrlrAvailable, {ItemsPerMessageSetVariableMonitoring, BytesPerMessageSetVariableMonitoring}},
56+
{DisplayMessageCtrlrAvailable,
57+
{NumberOfDisplayMessages, DisplayMessageSupportedFormats, DisplayMessageSupportedPriorities}}};
58+
59+
std::vector<RequiredComponentVariable> required_variables{ChargePointId,
60+
NetworkConnectionProfiles,
61+
ChargeBoxSerialNumber,
62+
ChargePointModel,
63+
ChargePointVendor,
64+
FirmwareVersion,
65+
SupportedCiphers12,
66+
SupportedCiphers13,
67+
LogMessagesFormat,
68+
NumberOfConnectors,
69+
SupportedOcppVersions,
70+
AuthorizeRemoteStart,
71+
LocalAuthorizeOffline,
72+
LocalPreAuthorize,
73+
ChargingStationAvailabilityState,
74+
ChargingStationAvailable,
75+
ChargingStationSupplyPhases,
76+
ClockCtrlrDateTime,
77+
TimeSource,
78+
BytesPerMessageGetReport,
79+
BytesPerMessageGetVariables,
80+
BytesPerMessageSetVariables,
81+
ItemsPerMessageGetReport,
82+
ItemsPerMessageGetVariables,
83+
ItemsPerMessageSetVariables,
84+
ContractValidationOffline,
85+
FileTransferProtocols,
86+
MessageTimeout,
87+
MessageAttemptInterval,
88+
MessageAttempts,
89+
NetworkConfigurationPriority,
90+
NetworkProfileConnectionAttempts,
91+
OfflineThreshold,
92+
ResetRetries,
93+
RetryBackOffRandomRange,
94+
RetryBackOffRepeatTimes,
95+
RetryBackOffWaitMinimum,
96+
UnlockOnEVSideDisconnect,
97+
WebSocketPingInterval,
98+
CertificateEntries,
99+
SecurityCtrlrIdentity,
100+
OrganizationName,
101+
SecurityProfile,
102+
EVConnectionTimeOut,
103+
StopTxOnEVSideDisconnect,
104+
StopTxOnInvalidId,
105+
TxStartPoint,
106+
TxStopPoint,
107+
TxStartPoint,
108+
TxStopPoint};
109+
110+
// Note: Power is also required, but the value is not required but the maxLimit. So that is why it is not added here.
111+
std::vector<Variable> required_evse_variables{
112+
EvseComponentVariables::Available, EvseComponentVariables::AvailabilityState, EvseComponentVariables::SupplyPhases};
113+
114+
std::vector<Variable> required_connector_variables{
115+
ConnectorComponentVariables::Available, ConnectorComponentVariables::AvailabilityState,
116+
ConnectorComponentVariables::SupplyPhases, ConnectorComponentVariables::Type};
117+
40118
const ComponentVariable InternalCtrlrEnabled = {
41119
ControllerComponents::InternalCtrlr,
42120
std::optional<Variable>({
@@ -1080,12 +1158,6 @@ const ComponentVariable TariffCostCtrlrAvailableTariff = {
10801158
ControllerComponents::TariffCostCtrlr,
10811159
std::optional<Variable>({"Available", "Tariff"}),
10821160
};
1083-
const ComponentVariable EvseSleep = {
1084-
ControllerComponents::SmartChargingCtrlr,
1085-
std::nullopt,
1086-
std::optional<Variable>({"EvseSleep", std::nullopt}),
1087-
{OcppProtocolVersion::v21}
1088-
};
10891161
const ComponentVariable TariffCostCtrlrAvailableCost = {
10901162
ControllerComponents::TariffCostCtrlr,
10911163
std::optional<Variable>({"Available", "Cost"}),

lib/ocpp/v201/device_model.cpp

+86
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <ocpp/v201/ctrlr_component_variables.hpp>
77
#include <ocpp/v201/device_model.hpp>
88
#include <ocpp/v201/device_model_storage_sqlite.hpp>
9+
#include <ocpp/v201/utils.hpp>
910

1011
namespace ocpp {
1112

@@ -123,6 +124,67 @@ bool DeviceModel::component_variables_match(const std::vector<ComponentVariable>
123124
}) != component_variables.end();
124125
}
125126

127+
void DeviceModel::check_variable_has_value(const ComponentVariable& component_variable, const AttributeEnum attribute) {
128+
std::string value;
129+
const auto response = this->request_value_internal(component_variable.component,
130+
component_variable.variable.value(), attribute, value, true);
131+
132+
if (response != GetVariableStatusEnum::Accepted || value.empty()) {
133+
throw DeviceModelError("Required variable " + component_variable.variable->name.get() +
134+
" does not have a value in the device model");
135+
}
136+
}
137+
138+
void DeviceModel::check_required_variable(const RequiredComponentVariable& required_variable,
139+
const std::vector<OcppProtocolVersion>& supported_versions) {
140+
if (supported_versions.empty()) {
141+
throw DeviceModelError("Could not find supported ocpp versions in the InternalCtrlr.");
142+
}
143+
144+
bool required = false;
145+
for (auto& supported_version : supported_versions) {
146+
if (required_variable.required_for.count(supported_version) > 0) {
147+
required = true;
148+
}
149+
}
150+
151+
// For the current supported ocpp protocol versions, this variable is not required. So skip further checks.
152+
if (!required) {
153+
return;
154+
}
155+
156+
if (!required_variable.variable.has_value()) {
157+
throw DeviceModelError("Required variable does not exist.");
158+
}
159+
160+
check_variable_has_value(required_variable);
161+
}
162+
163+
void DeviceModel::check_required_variables() {
164+
const auto supported_versions = utils::get_ocpp_protocol_versions(
165+
this->get_value<std::string>(ControllerComponentVariables::SupportedOcppVersions));
166+
167+
if (supported_versions.empty()) {
168+
throw DeviceModelError("Could not find supported ocpp versions in the InternalCtrlr.");
169+
}
170+
171+
for (const auto& required_variable : required_variables) {
172+
check_required_variable(required_variable, supported_versions);
173+
}
174+
175+
for (const auto& available_required : required_component_available_variables) {
176+
std::optional<bool> available = this->get_optional_value<bool>(available_required.first);
177+
if (!available.value_or(false)) {
178+
// Component not available, skip required checks.
179+
continue;
180+
}
181+
182+
for (const auto& required_variable : available_required.second) {
183+
check_required_variable(required_variable, supported_versions);
184+
}
185+
}
186+
}
187+
126188
bool validate_value(const VariableCharacteristics& characteristics, const std::string& value, bool allow_zero) {
127189
switch (characteristics.dataType) {
128190
case DataEnum::string:
@@ -427,6 +489,7 @@ DeviceModel::get_custom_report_data(const std::optional<std::vector<ComponentVar
427489
void DeviceModel::check_integrity(const std::map<int32_t, int32_t>& evse_connector_structure) {
428490
EVLOG_debug << "Checking integrity of device model in storage";
429491
try {
492+
this->check_required_variables();
430493
this->device_model->check_integrity();
431494

432495
// TODO: add required variable check here.
@@ -468,12 +531,35 @@ void DeviceModel::check_integrity(const std::map<int32_t, int32_t>& evse_connect
468531
if (!this->device_model_map.count(evse_component)) {
469532
throw DeviceModelError("Could not find required EVSE component in device model");
470533
}
534+
535+
for (const auto& required_variable : required_evse_variables) {
536+
const auto& variable = EvseComponentVariables::get_component_variable(evse_id, required_variable);
537+
check_variable_has_value(variable);
538+
}
539+
540+
const auto& variable =
541+
EvseComponentVariables::get_component_variable(evse_id, EvseComponentVariables::Power);
542+
std::map<Variable, VariableMetaData>& v = device_model_map[evse_component];
543+
if (!v.count(EvseComponentVariables::Power)) {
544+
throw DeviceModelError("Could not find required 'Power' variable in EVSE component in device model");
545+
}
546+
547+
if (!v[EvseComponentVariables::Power].characteristics.maxLimit.has_value()) {
548+
throw DeviceModelError("maxLimit of 'Power' not set");
549+
}
550+
471551
for (size_t connector_id = 1; connector_id <= nr_of_connectors; connector_id++) {
472552
evse_component.name = "Connector";
473553
evse_component.evse.value().connectorId = connector_id;
474554
if (!this->device_model_map.count(evse_component)) {
475555
throw DeviceModelError("Could not find required Connector component in device model");
476556
}
557+
558+
for (const auto& required_variable : required_connector_variables) {
559+
const auto& variable =
560+
ConnectorComponentVariables::get_component_variable(evse_id, connector_id, required_variable);
561+
check_variable_has_value(variable);
562+
}
477563
}
478564
}
479565
} catch (const DeviceModelError& e) {

lib/ocpp/v201/device_model_storage_sqlite.cpp

-30
Original file line numberDiff line numberDiff line change
@@ -456,36 +456,6 @@ int32_t DeviceModelStorageSqlite::clear_custom_variable_monitors() {
456456
}
457457

458458
void DeviceModelStorageSqlite::check_integrity() {
459-
460-
// Check for required variables without actual values
461-
std::stringstream query_stream;
462-
query_stream << "SELECT c.NAME as 'COMPONENT_NAME', "
463-
"c.EVSE_ID as 'EVSE_ID', "
464-
"c.CONNECTOR_ID as 'CONNECTOR_ID', "
465-
"v.NAME as 'VARIABLE_NAME', "
466-
"v.INSTANCE as 'VARIABLE_INSTANCE' "
467-
"FROM VARIABLE_ATTRIBUTE va "
468-
"JOIN VARIABLE v ON v.ID = va.VARIABLE_ID "
469-
"JOIN COMPONENT c ON v.COMPONENT_ID = c.ID "
470-
"WHERE va.TYPE_ID = "
471-
<< static_cast<int>(AttributeEnum::Actual)
472-
<< " AND va.VALUE IS NULL"
473-
" AND v.REQUIRED = 1";
474-
auto select_stmt = this->db->new_statement(query_stream.str());
475-
476-
if (select_stmt->step() != SQLITE_DONE) {
477-
std::stringstream error;
478-
error << "Corrupted device model: Missing the following required values for 'Actual' Variable Attributes:"
479-
<< std::endl;
480-
do {
481-
error << "(Component/EvseId/ConnectorId/Variable/Instance: " << select_stmt->column_text(0) << "/"
482-
<< select_stmt->column_text_nullable(1).value_or("<null>") << "/"
483-
<< select_stmt->column_text_nullable(2).value_or("<null>") << "/" << select_stmt->column_text(3)
484-
<< "/" << select_stmt->column_text_nullable(4).value_or("<null>") << ")" << std::endl;
485-
} while (select_stmt->step() == SQLITE_ROW);
486-
487-
throw DeviceModelError(error.str());
488-
}
489459
}
490460

491461
} // namespace v201

tests/lib/ocpp/v201/functional_blocks/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ target_include_directories(libocpp_test_security PUBLIC
3838

3939
set(TEST_AUTHORIZATION_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/../device_model_test_helper.cpp
4040
${LIBOCPP_LIB_PATH}/ocpp/v201/functional_blocks/authorization.cpp
41+
${LIBOCPP_LIB_PATH}/ocpp/v201/ctrlr_component_variables.cpp
4142
${LIBOCPP_LIB_PATH}/ocpp/v201/messages/Authorize.cpp
4243
${LIBOCPP_LIB_PATH}/ocpp/v201/messages/ClearCache.cpp
4344
${LIBOCPP_LIB_PATH}/ocpp/v201/messages/GetLocalListVersion.cpp

0 commit comments

Comments
 (0)