diff --git a/config/v16/profile_schemas/Internal.json b/config/v16/profile_schemas/Internal.json index 25043f8b0..fabb40de7 100644 --- a/config/v16/profile_schemas/Internal.json +++ b/config/v16/profile_schemas/Internal.json @@ -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 diff --git a/include/ocpp/v16/charge_point.hpp b/include/ocpp/v16/charge_point.hpp index 1a072a737..3ffad258f 100644 --- a/include/ocpp/v16/charge_point.hpp +++ b/include/ocpp/v16/charge_point.hpp @@ -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 diff --git a/include/ocpp/v16/charge_point_configuration.hpp b/include/ocpp/v16/charge_point_configuration.hpp index 932800210..c9c3d8ca4 100644 --- a/include/ocpp/v16/charge_point_configuration.hpp +++ b/include/ocpp/v16/charge_point_configuration.hpp @@ -520,6 +520,11 @@ class ChargePointConfiguration { void setWaitForSetUserPriceTimeout(const int32_t wait_for_set_user_price_timeout); std::optional getWaitForSetUserPriceTimeoutKeyValue(); + // Signed Meter Values + std::optional getPublicKeyKeyValue(const uint32_t connector_id); + std::optional> getAllMeterPublicKeyKeyValues(); + bool setMeterPublicKey(const int32_t connector_id, const std::string& public_key_pem); + // custom std::optional getCustomKeyValue(CiString<50> key); ConfigurationStatus setCustomKey(CiString<50> key, CiString<500> value, bool force); diff --git a/include/ocpp/v16/charge_point_impl.hpp b/include/ocpp/v16/charge_point_impl.hpp index 0370a87cd..a894ed397 100644 --- a/include/ocpp/v16/charge_point_impl.hpp +++ b/include/ocpp/v16/charge_point_impl.hpp @@ -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 diff --git a/lib/ocpp/v16/charge_point.cpp b/lib/ocpp/v16/charge_point.cpp index 8b02a89c6..595321db5 100644 --- a/lib/ocpp/v16/charge_point.cpp +++ b/lib/ocpp/v16/charge_point.cpp @@ -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 diff --git a/lib/ocpp/v16/charge_point_configuration.cpp b/lib/ocpp/v16/charge_point_configuration.cpp index 7861489c5..341b018c3 100644 --- a/lib/ocpp/v16/charge_point_configuration.cpp +++ b/lib/ocpp/v16/charge_point_configuration.cpp @@ -3172,6 +3172,79 @@ std::optional ChargePointConfiguration::getWaitForSetUserPriceTimeoutK return result; } +std::optional 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(); + return kv; +} + +std::optional> 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 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(); + 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(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); @@ -3254,6 +3327,49 @@ void ChargePointConfiguration::setCentralSystemURI(std::string centralSystemUri) this->setInUserConfig("Internal", "CentralSystemURI", centralSystemUri); } +namespace { +std::optional 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(c))) { + return std::nullopt; + } + } + + unsigned long long temp = 0; + try { + temp = std::stoull(number_str); + } catch (...) { + return std::nullopt; + } + + if (temp > std::numeric_limits::max()) { + return std::nullopt; + } + return static_cast(temp); +} +} // namespace + std::optional ChargePointConfiguration::get(CiString<50> key) { std::lock_guard lock(this->configuration_mutex); // Internal Profile @@ -3502,6 +3618,24 @@ std::optional 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)) { @@ -3659,6 +3793,16 @@ std::vector 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) { diff --git a/lib/ocpp/v16/charge_point_impl.cpp b/lib/ocpp/v16/charge_point_impl.cpp index 6f7a549dd..e23b296e8 100644 --- a/lib/ocpp/v16/charge_point_impl.cpp +++ b/lib/ocpp/v16/charge_point_impl.cpp @@ -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 call) { EVLOG_debug << "Received ChangeAvailabilityRequest: " << call.msg << "\nwith messageId: " << call.uniqueId;