Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions config/v16/profile_schemas/Internal.json
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,14 @@
"type": "boolean",
"readOnly": false,
"default": false
},
"MeterPublicKeys": {
"$comment": "The public key of the meter in formatted according the Signed Meter Values Whitepaper of OCPP. The first element represents the public key for connector id 1, the second for connector id 2, and so on.",
"type": "array",
"readOnly": true,
"items": {
"type": "string"
}
}
},
"additionalProperties": false
Expand Down
6 changes: 6 additions & 0 deletions include/ocpp/v16/charge_point.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,12 @@ class ChargePoint {
/// \brief Delay draining the message queue after reconnecting, so the CSMS can perform post-reconnect checks first
/// \param delay The delay period (seconds)
void set_message_queue_resume_delay(std::chrono::seconds delay);

/// \brief Sets the public key of the powermeter for the given connector
/// \param connector The connector for which the public key is set
/// \param public_key_pem The public key in PEM format
/// \return true if the public key was set successfully, false otherwise
bool set_powermeter_public_key(const int32_t connector, const std::string& public_key_pem);
};

} // namespace v16
Expand Down
5 changes: 5 additions & 0 deletions include/ocpp/v16/charge_point_configuration.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,11 @@ class ChargePointConfiguration {
void setWaitForSetUserPriceTimeout(const int32_t wait_for_set_user_price_timeout);
std::optional<KeyValue> getWaitForSetUserPriceTimeoutKeyValue();

// Signed Meter Values
std::optional<KeyValue> getPublicKeyKeyValue(const uint32_t connector_id);
std::optional<std::vector<KeyValue>> getAllMeterPublicKeyKeyValues();
bool setMeterPublicKey(const int32_t connector_id, const std::string& public_key_pem);

// custom
std::optional<KeyValue> getCustomKeyValue(CiString<50> key);
ConfigurationStatus setCustomKey(CiString<50> key, CiString<500> value, bool force);
Expand Down
6 changes: 6 additions & 0 deletions include/ocpp/v16/charge_point_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -943,6 +943,12 @@ class ChargePointImpl : ocpp::ChargingStationBase {
void set_message_queue_resume_delay(std::chrono::seconds delay) {
this->message_queue_resume_delay = delay;
}

/// \brief Sets the public key of the powermeter for the given connector
/// \param connector The connector for which the public key is set
/// \param public_key_pem The public key in PEM format
/// \return true if the public key was set successfully, false otherwise
bool set_powermeter_public_key(const int32_t connector, const std::string& public_key_pem);
};

} // namespace v16
Expand Down
4 changes: 4 additions & 0 deletions lib/ocpp/v16/charge_point.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -383,5 +383,9 @@ void ChargePoint::set_message_queue_resume_delay(std::chrono::seconds delay) {
this->charge_point->set_message_queue_resume_delay(delay);
}

bool ChargePoint::set_powermeter_public_key(const int32_t connector, const std::string& public_key_pem) {
return this->charge_point->set_powermeter_public_key(connector, public_key_pem);
}

} // namespace v16
} // namespace ocpp
144 changes: 144 additions & 0 deletions lib/ocpp/v16/charge_point_configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3172,6 +3172,79 @@ std::optional<KeyValue> ChargePointConfiguration::getWaitForSetUserPriceTimeoutK
return result;
}

std::optional<KeyValue> ChargePointConfiguration::getPublicKeyKeyValue(const uint32_t connector_id) {
if (!this->config["Internal"].contains("MeterPublicKeys")) {
return std::nullopt;
}

const auto& meter_public_keys = this->config["Internal"].at("MeterPublicKeys");

if (!meter_public_keys.is_array()) {
return std::nullopt;
}

const auto& keys_array = meter_public_keys;
if (keys_array.size() < connector_id or connector_id < 1) {
return std::nullopt;
}

KeyValue kv;
kv.key = "MeterPublicKey[" + std::to_string(connector_id) + "]";
kv.readonly = true;
kv.value = keys_array.at(connector_id - 1).get<std::string>();
return kv;
}

std::optional<std::vector<KeyValue>> ChargePointConfiguration::getAllMeterPublicKeyKeyValues() {
if (!this->config["Internal"].contains("MeterPublicKeys")) {
return std::nullopt;
}

const auto& meter_public_keys = this->config["Internal"].at("MeterPublicKeys");

if (!meter_public_keys.is_array()) {
return std::nullopt;
}

std::vector<KeyValue> key_values;
const auto& keys_array = meter_public_keys;
for (size_t i = 0; i < keys_array.size(); i++) {
KeyValue kv;
kv.key = "MeterPublicKey[" + std::to_string(i + 1) + "]";
kv.readonly = true;
kv.value = keys_array.at(i).get<std::string>();
key_values.push_back(kv);
}

return key_values;
}

bool ChargePointConfiguration::setMeterPublicKey(const int32_t connector_id, const std::string& public_key_pem) {
if (connector_id > this->getNumberOfConnectors() or connector_id < 1) {
EVLOG_warning << "Cannot set MeterPublicKey for connector " << connector_id
<< ", because the connector id does not exist.";
return false;
}

if (!this->config["Internal"].contains("MeterPublicKeys") or this->config["Internal"]["MeterPublicKeys"].empty()) {
this->config["Internal"]["MeterPublicKeys"] = json::array();
for (size_t i = 0; i < this->getNumberOfConnectors(); i++) {
this->config["Internal"]["MeterPublicKeys"].push_back("");
}
}

auto& meter_public_keys = this->config["Internal"]["MeterPublicKeys"];
if (!meter_public_keys.is_array() or meter_public_keys.size() < static_cast<size_t>(connector_id)) {
EVLOG_warning << "Cannot set MeterPublicKey for connector " << connector_id
<< ", because the MeterPublicKeys array is not valid.";
return false;
}
meter_public_keys[connector_id - 1] = public_key_pem;

this->setInUserConfig("Internal", "MeterPublicKeys", this->config["Internal"]["MeterPublicKeys"]);
return true;
}

void ChargePointConfiguration::setLanguage(const std::string& language) {
this->config["CostAndPrice"]["Language"] = language;
this->setInUserConfig("CostAndPrice", "Language", language);
Expand Down Expand Up @@ -3254,6 +3327,49 @@ void ChargePointConfiguration::setCentralSystemURI(std::string centralSystemUri)
this->setInUserConfig("Internal", "CentralSystemURI", centralSystemUri);
}

namespace {
std::optional<uint32_t> parse_meter_public_key_index(const std::string& input) {
const std::string prefix = "MeterPublicKey[";
const std::string suffix = "]";

if (input.size() <= prefix.size() + suffix.size()) {
return std::nullopt;
}

if (input.rfind(prefix, 0) != 0) {
return std::nullopt;
}

if (input.substr(input.size() - suffix.size()) != suffix) {
return std::nullopt;
}

std::string number_str = input.substr(prefix.size(), input.size() - prefix.size() - suffix.size());

if (number_str.empty()) {
return std::nullopt;
}

for (char c : number_str) {
if (!std::isdigit(static_cast<unsigned char>(c))) {
return std::nullopt;
}
}

unsigned long long temp = 0;
try {
temp = std::stoull(number_str);
} catch (...) {
return std::nullopt;
}

if (temp > std::numeric_limits<uint32_t>::max()) {
return std::nullopt;
}
return static_cast<uint32_t>(temp);
}
} // namespace

std::optional<KeyValue> ChargePointConfiguration::get(CiString<50> key) {
std::lock_guard<std::recursive_mutex> lock(this->configuration_mutex);
// Internal Profile
Expand Down Expand Up @@ -3502,6 +3618,24 @@ std::optional<KeyValue> ChargePointConfiguration::get(CiString<50> key) {
if (key == "WebSocketPingInterval") {
return this->getWebsocketPingIntervalKeyValue();
}
if (key.get().rfind("MeterPublicKey[", 0) == 0) {
const std::string& s = key.get();
const auto connector_id_opt = parse_meter_public_key_index(s);

if (!connector_id_opt.has_value()) {
EVLOG_error << "Invalid MeterPublicKey format for key '" << s << "'";
return std::nullopt;
}

const auto connector_id = connector_id_opt.value();

if (connector_id == 0) {
EVLOG_error << "MeterPublicKey key '" << s << "' contains connectorId=0 which is invalid.";
return std::nullopt;
}

return this->getPublicKeyKeyValue(connector_id);
}

// Firmware Management
if (this->supported_feature_profiles.count(SupportedFeatureProfiles::FirmwareManagement)) {
Expand Down Expand Up @@ -3659,6 +3793,16 @@ std::vector<KeyValue> ChargePointConfiguration::get_all_key_value() {
all.push_back(kv);
}
}
// MeterPublicKey is a special here, as it has multiple possible connector ids which are all
// separate key value pairs.
} else if (config_key.get() == "MeterPublicKeys") {
const auto meter_public_key_kvs = getAllMeterPublicKeyKeyValues();
if (meter_public_key_kvs.has_value()) {
for (const auto& kv : meter_public_key_kvs.value()) {
all.push_back(kv);
}
}

} else {
auto config_value = this->get(config_key);
if (config_value != std::nullopt) {
Expand Down
4 changes: 4 additions & 0 deletions lib/ocpp/v16/charge_point_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1975,6 +1975,10 @@ ChargePointImpl::set_configuration_key_internal(CiString<50> key, CiString<500>
return {result, response};
}

bool ChargePointImpl::set_powermeter_public_key(const int32_t connector, const std::string& public_key_pem) {
return this->configuration->setMeterPublicKey(connector, public_key_pem);
}

void ChargePointImpl::handleChangeAvailabilityRequest(ocpp::Call<ChangeAvailabilityRequest> call) {
EVLOG_debug << "Received ChangeAvailabilityRequest: " << call.msg << "\nwith messageId: " << call.uniqueId;

Expand Down