diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index 11e971403..64631c388 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -99,14 +99,20 @@ class ChargePointInterface { virtual ~ChargePointInterface() = default; /// \brief Starts the ChargePoint, initializes and connects to the Websocket endpoint - /// \param bootreason Optional bootreason (default: PowerUp). - virtual void start(BootReasonEnum bootreason = BootReasonEnum::PowerUp) = 0; + /// \param bootreason Optional bootreason (default: PowerUp). + /// \param start_connecting Optional, set to false to initialize but not start connecting. Otherwise will connect to + /// the first network profile. (default: true) + virtual void start(BootReasonEnum bootreason = BootReasonEnum::PowerUp, bool start_connecting = true) = 0; /// \brief Stops the ChargePoint. Disconnects the websocket connection and stops MessageQueue and all timers virtual void stop() = 0; - /// \brief Initializes the websocket and connects to CSMS if it is not yet connected - virtual void connect_websocket() = 0; + /// \brief Initializes the websocket and connects to a CSMS. Provide a network_profile_slot to connect to that + /// specific slot. + /// + /// \param network_profile_slot Optional slot to use when connecting. std::nullopt means the slot will be determined + /// automatically. + virtual void connect_websocket(std::optional network_profile_slot = std::nullopt) = 0; /// \brief Disconnects the the websocket connection to the CSMS if it is connected virtual void disconnect_websocket() = 0; @@ -119,16 +125,6 @@ class ChargePointInterface { /// The handlers /// @{ - /// - /// \brief Can be called when a network is disconnected, for example when an ethernet cable is removed. - /// - /// This is introduced because the websocket can take several minutes to timeout when a network interface becomes - /// unavailable, whereas the system can detect this sooner. - /// - /// \param configuration_slot The slot of the network connection profile that is disconnected. - /// - virtual void on_network_disconnected(int32_t configuration_slot) = 0; - /// /// \brief Can be called when a network is disconnected, for example when an ethernet cable is removed. /// @@ -139,14 +135,6 @@ class ChargePointInterface { /// virtual void on_network_disconnected(OCPPInterfaceEnum ocpp_interface) = 0; - /// \brief Switch to a specific network connection profile given the configuration slot. - /// - /// Switch will only be done when the configuration slot has a higher priority. - /// - /// \param configuration_slot Slot in which the configuration is stored - /// \return true if the switch is possible. - virtual bool on_try_switch_network_connection_profile(const int32_t configuration_slot) = 0; - /// \brief Chargepoint notifies about new firmware update status firmware_update_status. This function should be /// called during a Firmware Update to indicate the current firmware_update_status. /// \param request_id The request_id. When it is -1, it will not be included in the request. @@ -364,20 +352,20 @@ class ChargePointInterface { /// present. This returns the value from the cached network connection profiles. \param /// network_configuration_priority \return virtual std::optional - get_network_connection_profile(const int32_t configuration_slot) = 0; + get_network_connection_profile(const int32_t configuration_slot) const = 0; /// \brief Get the priority of the given configuration slot. /// \param configuration_slot The configuration slot to get the priority from. /// \return The priority if the configuration slot exists. /// - virtual std::optional get_configuration_slot_priority(const int configuration_slot) = 0; + virtual std::optional get_priority_from_configuration_slot(const int configuration_slot) const = 0; - /// @brief Get the network connection priorities. + /// @brief Get the network connection slots sorted by priority. /// Each item in the vector contains the configured configuration slots, where the slot with index 0 has the highest /// priority. - /// @return The network connection priorities + /// @return The network connection slots /// - virtual const std::vector& get_network_connection_priorities() const = 0; + virtual const std::vector& get_network_connection_slots() const = 0; }; /// \brief Class implements OCPP2.0.1 Charging Station @@ -491,10 +479,6 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa get_composite_schedule_internal(const GetCompositeScheduleRequest& request, const std::set& profiles_to_ignore = {}); - /// \brief Removes all network connection profiles below the actual security profile and stores the new list in the - /// device model - void remove_network_connection_profiles_below_actual_security_profile(); - void message_callback(const std::string& message); void update_aligned_data_interval(); @@ -863,19 +847,15 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa ~ChargePoint(); - void start(BootReasonEnum bootreason = BootReasonEnum::PowerUp) override; + void start(BootReasonEnum bootreason = BootReasonEnum::PowerUp, bool start_connecting = true) override; void stop() override; - virtual void connect_websocket() override; + void connect_websocket(std::optional network_profile_slot = std::nullopt) override; virtual void disconnect_websocket() override; - void on_network_disconnected(int32_t configuration_slot) override; - void on_network_disconnected(OCPPInterfaceEnum ocpp_interface) override; - bool on_try_switch_network_connection_profile(const int32_t configuration_slot) override; - void on_firmware_update_status_notification(int32_t request_id, const FirmwareStatusEnum& firmware_update_status) override; @@ -960,11 +940,12 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa std::vector get_all_composite_schedules(const int32_t duration, const ChargingRateUnitEnum& unit) override; - std::optional get_network_connection_profile(const int32_t configuration_slot) override; + std::optional + get_network_connection_profile(const int32_t configuration_slot) const override; - std::optional get_configuration_slot_priority(const int configuration_slot) override; + std::optional get_priority_from_configuration_slot(const int configuration_slot) const override; - const std::vector& get_network_connection_priorities() const override; + const std::vector& get_network_connection_slots() const override; /// \brief Requests a value of a VariableAttribute specified by combination of \p component_id and \p variable_id /// from the device model diff --git a/include/ocpp/v201/connectivity_manager.hpp b/include/ocpp/v201/connectivity_manager.hpp index 03c45bc6f..93b7f4924 100644 --- a/include/ocpp/v201/connectivity_manager.hpp +++ b/include/ocpp/v201/connectivity_manager.hpp @@ -43,13 +43,14 @@ class ConnectivityManager { std::optional configure_network_connection_profile_callback; Everest::SteadyTimer websocket_timer; - bool disable_automatic_websocket_reconnects; - int network_configuration_priority; + std::optional pending_configuration_slot; + bool wants_to_be_connected; + int32_t active_network_configuration_priority; + int last_known_security_level; /// @brief Local cached network connection profiles - std::vector network_connection_profiles; + std::vector cached_network_connection_profiles; /// @brief local cached network connection priorities - std::vector network_connection_priorities; - WebsocketConnectionOptions current_connection_options{}; + std::vector network_connection_slots; public: ConnectivityManager(DeviceModel& device_model, std::shared_ptr evse_security, @@ -84,45 +85,38 @@ class ConnectivityManager { /// void set_configure_network_connection_profile_callback(ConfigureNetworkConnectionProfileCallback callback); - /// \brief Gets the configured NetworkConnectionProfile based on the given \p configuration_slot . The - /// central system uri of the connection options will not contain ws:// or wss:// because this method removes it if - /// present. This returns the value from the cached network connection profiles. \param - /// network_configuration_priority \return - std::optional get_network_connection_profile(const int32_t configuration_slot); + /// \brief Gets the cached NetworkConnectionProfile based on the given \p configuration_slot. + /// This returns the value from the cached network connection profiles. + /// \return Returns a profile if the slot is found + std::optional get_network_connection_profile(const int32_t configuration_slot) const; /// \brief Get the priority of the given configuration slot. /// \param configuration_slot The configuration slot to get the priority from. /// \return The priority if the configuration slot exists. /// - std::optional get_configuration_slot_priority(const int configuration_slot); + std::optional get_priority_from_configuration_slot(const int configuration_slot) const; - /// @brief Get the network connection priorities. + /// @brief Get the network connection slots sorted by priority. /// Each item in the vector contains the configured configuration slots, where the slot with index 0 has the highest /// priority. - /// @return The network connection priorities + /// @return The network connection slots /// - const std::vector& get_network_connection_priorities() const; + const std::vector& get_network_connection_slots() const; /// \brief Check if the websocket is connected /// \return True is the websocket is connected, else false /// bool is_websocket_connected(); - /// \brief Start the connectivity manager - /// - void start(); - - /// \brief Stop the connectivity manager - /// - void stop(); - /// \brief Connect to the websocket + /// \param configuration_slot Optional the network_profile_slot to connect to. std::nullopt will select the slot + /// internally. /// - void connect(); + void connect(std::optional network_profile_slot = std::nullopt); - /// \brief Disconnect the websocket with a specific \p reason + /// \brief Disconnect the websocket /// - void disconnect_websocket(WebsocketCloseReason code = WebsocketCloseReason::Normal); + void disconnect(); /// \brief send a \p message over the websocket /// \returns true if the message was sent successfully @@ -135,37 +129,35 @@ class ConnectivityManager { /// This is introduced because the websocket can take several minutes to timeout when a network interface becomes /// unavailable, whereas the system can detect this sooner. /// - /// \param configuration_slot The slot of the network connection profile that is disconnected. - /// - void on_network_disconnected(int32_t configuration_slot); - - /// - /// \brief Can be called when a network is disconnected, for example when an ethernet cable is removed. - /// - /// This is introduced because the websocket can take several minutes to timeout when a network interface becomes - /// unavailable, whereas the system can detect this sooner. - /// - /// \param ocpp_interface The interface that is disconnected. + /// \param ocpp_interface The interface that is disconnected. /// void on_network_disconnected(OCPPInterfaceEnum ocpp_interface); - /// \brief Switch to a specific network connection profile given the configuration slot. + /// \brief Called when the charging station certificate is changed /// - /// Switch will only be done when the configuration slot has a higher priority. - /// - /// \param configuration_slot Slot in which the configuration is stored - /// \return true if the switch is possible. - bool on_try_switch_network_connection_profile(const int32_t configuration_slot); + void on_charging_station_certificate_changed(); + + /// \brief Confirms the connection is successful so the security profile requirements can be handled + void confirm_successful_connection(); private: - /// \brief Init the websocket + /// \brief Initializes the websocket and tries to connect /// - void init_websocket(); + void try_connect_websocket(); /// \brief Get the current websocket connection options - /// \returns the current websocket connection options + /// \return the current websocket connection options + /// + std::optional get_ws_connection_options(const int32_t configuration_slot); + + /// \brief Calls the configuration callback to get the interface to use, if there is a callback + /// \param slot The configuration slot to get the interface for + /// \param profile The network connection profile to get the interface for /// - WebsocketConnectionOptions get_ws_connection_options(const int32_t configuration_slot); + /// \return The network configuration containing the network interface to use, nullptr if the request failed or the + /// callback is not configured + std::optional + handle_configure_network_connection_profile_callback(int slot, const NetworkConnectionProfile& profile); /// \brief Function invoked when the web socket connected with the \p security_profile /// @@ -179,31 +171,36 @@ class ConnectivityManager { /// void on_websocket_closed(ocpp::WebsocketCloseReason reason); - /// \brief Reconnect with the give websocket \p reason /// - void reconnect(WebsocketCloseReason reason, std::optional next_priority = std::nullopt); + /// \brief Get the active network configuration slot in use. + /// \return The active slot the network is connected to or the pending slot. + /// + int get_active_network_configuration_slot() const; /// - /// \brief Returns true if the provided configuration slot is of higher priority compared to the one currently - /// in use. - /// \param new_configuration_slot The configuration slot to check. - /// \return True when given slot is of higher priority. + /// \brief Get the network configuration slot of the given priority. + /// \param priority The priority to get the configuration slot. + /// \return The configuration slot. /// - bool is_higher_priority_profile(const int new_configuration_slot); + int get_configuration_slot_from_priority(const int priority); /// - /// \brief Get the active network configuration slot in use. - /// \return The active slot the network is connected to or the pending slot. + /// \brief Get the next prioritized network configuration slot of the given configuration slot. + /// \param configuration_slot The current configuration slot. + /// \return The next prioritized configuration slot. /// - int get_active_network_configuration_slot(); + int get_next_configuration_slot(int32_t configuration_slot); - /// \brief Moves websocket network_configuration_priority to next profile - /// - void next_network_configuration_priority(); + /// \brief Cache all the network connection profiles + void cache_network_connection_profiles(); + + /// \brief Removes all connection profiles from the cache that have a security profile lower than the currently + /// connected security profile + void check_cache_for_invalid_security_profiles(); - /// @brief Cache all the network connection profiles. Must be called once during initialization - /// \return True if the network connection profiles could be cached, else False. - bool cache_network_connection_profiles(); + /// \brief Removes all connection profiles from the database that have a security profile lower than the currently + /// connected security profile + void remove_network_connection_profiles_below_actual_security_profile(); }; } // namespace v201 diff --git a/include/ocpp/v201/ocpp_types.hpp b/include/ocpp/v201/ocpp_types.hpp index 5c76097e8..0676cb74a 100644 --- a/include/ocpp/v201/ocpp_types.hpp +++ b/include/ocpp/v201/ocpp_types.hpp @@ -891,7 +891,6 @@ std::ostream& operator<<(std::ostream& os, const SetMonitoringResult& k); /// @brief The result of a configuration of a network profile. struct ConfigNetworkResult { - uint8_t network_profile_slot; ///< @brief Network profile slot. std::optional interface_address; ///< ip address or interface string bool success; ///< true if the configuration was successful }; diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 16c2465fc..a1852bc65 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -111,7 +111,7 @@ ChargePoint::~ChargePoint() { this->auth_cache_cleanup_thread.join(); } -void ChargePoint::start(BootReasonEnum bootreason) { +void ChargePoint::start(BootReasonEnum bootreason, bool start_connecting) { this->message_queue->start(); this->bootreason = bootreason; @@ -123,7 +123,9 @@ void ChargePoint::start(BootReasonEnum bootreason) { this->boot_notification_req(bootreason); // call clear_invalid_charging_profiles when system boots this->clear_invalid_charging_profiles(); - this->connectivity_manager->start(); + if (start_connecting) { + this->connectivity_manager->connect(); + } const std::string firmware_version = this->device_model->get_value(ControllerComponentVariables::FirmwareVersion); @@ -157,31 +159,23 @@ void ChargePoint::stop() { this->heartbeat_timer.stop(); this->boot_notification_timer.stop(); this->certificate_signed_timer.stop(); - this->connectivity_manager->stop(); + this->connectivity_manager->disconnect(); this->client_certificate_expiration_check_timer.stop(); this->v2g_certificate_expiration_check_timer.stop(); this->monitoring_updater.stop_monitoring(); this->message_queue->stop(); } -void ChargePoint::connect_websocket() { - this->connectivity_manager->connect(); -} - void ChargePoint::disconnect_websocket() { - this->connectivity_manager->disconnect_websocket(); -} - -void ChargePoint::on_network_disconnected(int32_t configuration_slot) { - this->connectivity_manager->on_network_disconnected(configuration_slot); + this->connectivity_manager->disconnect(); } void ChargePoint::on_network_disconnected(OCPPInterfaceEnum ocpp_interface) { this->connectivity_manager->on_network_disconnected(ocpp_interface); } -bool ChargePoint::on_try_switch_network_connection_profile(const int32_t configuration_slot) { - return this->connectivity_manager->on_try_switch_network_connection_profile(configuration_slot); +void ChargePoint::connect_websocket(std::optional network_profile_slot) { + this->connectivity_manager->connect(network_profile_slot); } void ChargePoint::on_firmware_update_status_notification(int32_t request_id, @@ -1207,53 +1201,6 @@ void ChargePoint::init_certificate_expiration_check_timers() { .value_or(60))); } -void ChargePoint::remove_network_connection_profiles_below_actual_security_profile() { - // Remove all the profiles that are a lower security level than security_level - const auto security_level = this->device_model->get_value(ControllerComponentVariables::SecurityProfile); - - auto network_connection_profiles = json::parse( - this->device_model->get_value(ControllerComponentVariables::NetworkConnectionProfiles)); - - auto is_lower_security_level = [security_level](const SetNetworkProfileRequest& item) { - return item.connectionData.securityProfile < security_level; - }; - - network_connection_profiles.erase( - std::remove_if(network_connection_profiles.begin(), network_connection_profiles.end(), is_lower_security_level), - network_connection_profiles.end()); - - this->device_model->set_value(ControllerComponentVariables::NetworkConnectionProfiles.component, - ControllerComponentVariables::NetworkConnectionProfiles.variable.value(), - AttributeEnum::Actual, network_connection_profiles.dump(), - VARIABLE_ATTRIBUTE_VALUE_SOURCE_INTERNAL); - - // Update the NetworkConfigurationPriority so only remaining profiles are in there - const auto network_priority = ocpp::split_string( - this->device_model->get_value(ControllerComponentVariables::NetworkConfigurationPriority), ','); - - auto in_network_profiles = [&network_connection_profiles](const std::string& item) { - auto is_same_slot = [&item](const SetNetworkProfileRequest& profile) { - return std::to_string(profile.configurationSlot) == item; - }; - return std::any_of(network_connection_profiles.begin(), network_connection_profiles.end(), is_same_slot); - }; - - std::string new_network_priority; - for (const auto& item : network_priority) { - if (in_network_profiles(item)) { - if (!new_network_priority.empty()) { - new_network_priority += ','; - } - new_network_priority += item; - } - } - - this->device_model->set_value(ControllerComponentVariables::NetworkConfigurationPriority.component, - ControllerComponentVariables::NetworkConfigurationPriority.variable.value(), - AttributeEnum::Actual, new_network_priority, - VARIABLE_ATTRIBUTE_VALUE_SOURCE_INTERNAL); -} - void ChargePoint::handle_message(const EnhancedMessage& message) { const auto& json_message = message.message; try { @@ -1795,7 +1742,6 @@ void ChargePoint::handle_variable_changed(const SetVariableData& set_variable_da if (this->device_model->get_value(ControllerComponentVariables::SecurityProfile) < 3) { // TODO: A01.FR.11 log the change of BasicAuth in Security Log this->connectivity_manager->set_websocket_authorization_key(set_variable_data.attributeValue.get()); - this->connectivity_manager->disconnect_websocket(WebsocketCloseReason::ServiceRestart); } } if (component_variable == ControllerComponentVariables::HeartbeatInterval and @@ -2377,7 +2323,7 @@ void ChargePoint::handle_certificate_signed_req(Call c if (response.status == CertificateSignedStatusEnum::Accepted and cert_signing_use == ocpp::CertificateSigningUseEnum::ChargingStationCertificate and this->device_model->get_value(ControllerComponentVariables::SecurityProfile) == 3) { - this->connectivity_manager->disconnect_websocket(WebsocketCloseReason::ServiceRestart); + this->connectivity_manager->on_charging_station_certificate_changed(); const auto& security_event = ocpp::security_events::RECONFIGURATIONOFSECURITYPARAMETERS; std::string tech_info = "Changed charging station certificate"; @@ -2459,7 +2405,7 @@ void ChargePoint::handle_boot_notification_response(CallResultcallbacks.time_sync_callback.value()(msg.currentTime); } - this->remove_network_connection_profiles_below_actual_security_profile(); + this->connectivity_manager->confirm_successful_connection(); // set timers if (msg.interval > 0) { @@ -4173,33 +4119,29 @@ void ChargePoint::websocket_connected_callback(const int configuration_slot, const NetworkConnectionProfile& network_connection_profile) { this->message_queue->resume(this->message_queue_resume_delay); - const auto& security_profile_cv = ControllerComponentVariables::SecurityProfile; - if (security_profile_cv.variable.has_value()) { - this->device_model->set_read_only_value( - security_profile_cv.component, security_profile_cv.variable.value(), AttributeEnum::Actual, - std::to_string(network_connection_profile.securityProfile), VARIABLE_ATTRIBUTE_VALUE_SOURCE_INTERNAL); - } - - if (this->registration_status == RegistrationStatusEnum::Accepted and - this->time_disconnected.time_since_epoch() != 0s) { - // handle offline threshold - // Get the current time point using steady_clock - auto offline_duration = std::chrono::steady_clock::now() - this->time_disconnected; - - // B04.FR.01 - // If offline period exceeds offline threshold then send the status notification for all connectors - if (offline_duration > - std::chrono::seconds(this->device_model->get_value(ControllerComponentVariables::OfflineThreshold))) { - EVLOG_debug << "offline for more than offline threshold "; - this->component_state_manager->send_status_notification_all_connectors(); - } else { - // B04.FR.02 - // If offline period doesn't exceed offline threshold then send the status notification for all - // connectors that changed state - EVLOG_debug << "offline for less than offline threshold "; - this->component_state_manager->send_status_notification_changed_connectors(); + if (this->registration_status == RegistrationStatusEnum::Accepted) { + this->connectivity_manager->confirm_successful_connection(); + + if (this->time_disconnected.time_since_epoch() != 0s) { + // handle offline threshold + // Get the current time point using steady_clock + auto offline_duration = std::chrono::steady_clock::now() - this->time_disconnected; + + // B04.FR.01 + // If offline period exceeds offline threshold then send the status notification for all connectors + if (offline_duration > std::chrono::seconds(this->device_model->get_value( + ControllerComponentVariables::OfflineThreshold))) { + EVLOG_debug << "offline for more than offline threshold "; + this->component_state_manager->send_status_notification_all_connectors(); + } else { + // B04.FR.02 + // If offline period doesn't exceed offline threshold then send the status notification for all + // connectors that changed state + EVLOG_debug << "offline for less than offline threshold "; + this->component_state_manager->send_status_notification_changed_connectors(); + } + this->init_certificate_expiration_check_timers(); // re-init as timers are stopped on disconnect } - this->init_certificate_expiration_check_timers(); // re-init as timers are stopped on disconnect } this->time_disconnected = std::chrono::time_point(); @@ -4511,16 +4453,17 @@ std::vector ChargePoint::get_all_composite_schedules(const in return composite_schedules; } -std::optional ChargePoint::get_network_connection_profile(const int32_t configuration_slot) { +std::optional +ChargePoint::get_network_connection_profile(const int32_t configuration_slot) const { return this->connectivity_manager->get_network_connection_profile(configuration_slot); } -std::optional ChargePoint::get_configuration_slot_priority(const int configuration_slot) { - return this->connectivity_manager->get_configuration_slot_priority(configuration_slot); +std::optional ChargePoint::get_priority_from_configuration_slot(const int configuration_slot) const { + return this->connectivity_manager->get_priority_from_configuration_slot(configuration_slot); } -const std::vector& ChargePoint::get_network_connection_priorities() const { - return this->connectivity_manager->get_network_connection_priorities(); +const std::vector& ChargePoint::get_network_connection_slots() const { + return this->connectivity_manager->get_network_connection_slots(); } // Static functions diff --git a/lib/ocpp/v201/connectivity_manager.cpp b/lib/ocpp/v201/connectivity_manager.cpp index a35f7965f..d06c2de14 100644 --- a/lib/ocpp/v201/connectivity_manager.cpp +++ b/lib/ocpp/v201/connectivity_manager.cpp @@ -26,18 +26,20 @@ ConnectivityManager::ConnectivityManager(DeviceModel& device_model, std::shared_ logging{logging}, websocket{nullptr}, message_callback{message_callback}, - disable_automatic_websocket_reconnects{false}, - network_configuration_priority{0} { + wants_to_be_connected{false}, + active_network_configuration_priority{0}, + last_known_security_level{0} { + cache_network_connection_profiles(); } void ConnectivityManager::set_websocket_authorization_key(const std::string& authorization_key) { if (this->websocket != nullptr) { this->websocket->set_authorization_key(authorization_key); + this->websocket->disconnect(WebsocketCloseReason::ServiceRestart); } } void ConnectivityManager::set_websocket_connection_options(const WebsocketConnectionOptions& connection_options) { - this->current_connection_options = connection_options; if (this->websocket != nullptr) { this->websocket->set_connection_options(connection_options); } @@ -46,7 +48,9 @@ void ConnectivityManager::set_websocket_connection_options(const WebsocketConnec void ConnectivityManager::set_websocket_connection_options_without_reconnect() { const int configuration_slot = get_active_network_configuration_slot(); const auto connection_options = this->get_ws_connection_options(configuration_slot); - this->set_websocket_connection_options(connection_options); + if (connection_options.has_value()) { + this->set_websocket_connection_options(connection_options.value()); + } } void ConnectivityManager::set_websocket_connected_callback(WebsocketConnectionCallback callback) { @@ -67,16 +71,15 @@ void ConnectivityManager::set_configure_network_connection_profile_callback( } std::optional -ConnectivityManager::get_network_connection_profile(const int32_t configuration_slot) { +ConnectivityManager::get_network_connection_profile(const int32_t configuration_slot) const { - for (const auto& network_profile : this->network_connection_profiles) { + for (const auto& network_profile : this->cached_network_connection_profiles) { if (network_profile.configurationSlot == configuration_slot) { - switch (auto security_profile = network_profile.connectionData.securityProfile) { - case security::OCPP_1_6_ONLY_UNSECURED_TRANSPORT_WITHOUT_BASIC_AUTHENTICATION: - throw std::invalid_argument("security_profile = " + std::to_string(security_profile) + - " not officially allowed in OCPP 2.0.1"); - default: - break; + if (network_profile.connectionData.securityProfile == + security::OCPP_1_6_ONLY_UNSECURED_TRANSPORT_WITHOUT_BASIC_AUTHENTICATION) { + throw std::invalid_argument( + "security_profile = " + std::to_string(network_profile.connectionData.securityProfile) + + " not officially allowed in OCPP 2.0.1"); } return network_profile.connectionData; @@ -85,198 +88,142 @@ ConnectivityManager::get_network_connection_profile(const int32_t configuration_ return std::nullopt; } -std::optional ConnectivityManager::get_configuration_slot_priority(const int configuration_slot) { - auto it = std::find(this->network_connection_priorities.begin(), this->network_connection_priorities.end(), - configuration_slot); - if (it != network_connection_priorities.end()) { +std::optional ConnectivityManager::get_priority_from_configuration_slot(const int configuration_slot) const { + auto it = + std::find(this->network_connection_slots.begin(), this->network_connection_slots.end(), configuration_slot); + if (it != this->network_connection_slots.end()) { // Index is iterator - begin iterator - return it - network_connection_priorities.begin(); + return it - this->network_connection_slots.begin(); } return std::nullopt; } -const std::vector& ConnectivityManager::get_network_connection_priorities() const { - return this->network_connection_priorities; +int ConnectivityManager::get_active_network_configuration_slot() const { + return this->network_connection_slots.at(this->active_network_configuration_priority); } -bool ConnectivityManager::is_websocket_connected() { - return this->websocket != nullptr && this->websocket->is_connected(); +int ConnectivityManager::get_configuration_slot_from_priority(const int priority) { + return this->network_connection_slots.at(priority); } -void ConnectivityManager::start() { - init_websocket(); - if (websocket != nullptr) { - this->disable_automatic_websocket_reconnects = false; - websocket->connect(); - } +const std::vector& ConnectivityManager::get_network_connection_slots() const { + return this->network_connection_slots; } -void ConnectivityManager::stop() { - this->websocket_timer.stop(); - disconnect_websocket(WebsocketCloseReason::Normal); +bool ConnectivityManager::is_websocket_connected() { + return this->websocket != nullptr && this->websocket->is_connected(); } -void ConnectivityManager::connect() { - if (this->websocket != nullptr and !this->websocket->is_connected()) { - this->disable_automatic_websocket_reconnects = false; - this->init_websocket(); - this->websocket->connect(); +void ConnectivityManager::connect(std::optional configuration_slot_opt) { + if (this->network_connection_slots.empty()) { + EVLOG_warning << "No network connection profiles configured, aborting websocket connection."; + return; } -} -void ConnectivityManager::disconnect_websocket(WebsocketCloseReason code) { - if (this->websocket != nullptr) { - if (code != WebsocketCloseReason::ServiceRestart) { - this->disable_automatic_websocket_reconnects = true; - } - this->websocket->disconnect(code); + const int32_t configuration_slot = configuration_slot_opt.value_or(this->get_active_network_configuration_slot()); + if (!this->get_network_connection_profile(configuration_slot).has_value()) { + EVLOG_warning << "Could not find network connection profile belonging to configuration slot " + << configuration_slot; + return; } -} -bool ConnectivityManager::send_to_websocket(const std::string& message) { - if (this->websocket == nullptr) { - return false; + this->wants_to_be_connected = true; + this->pending_configuration_slot = configuration_slot; + if (this->is_websocket_connected()) { + // After the websocket gets closed a reconnect will be triggered + this->websocket->disconnect(WebsocketCloseReason::ServiceRestart); + } else { + this->try_connect_websocket(); } - - return this->websocket->send(message); } -void ConnectivityManager::on_network_disconnected(int32_t configuration_slot) { - const int actual_configuration_slot = get_active_network_configuration_slot(); - std::optional network_connection_profile = - this->get_network_connection_profile(actual_configuration_slot); - - if (!network_connection_profile.has_value()) { - EVLOG_warning << "Network disconnected. No network connection profile configured"; - } else if (configuration_slot == actual_configuration_slot) { - // Since there is no connection anymore: disconnect the websocket, the manager will try to connect with the next - // available network connection profile as we enable reconnects. - this->disconnect_websocket(ocpp::WebsocketCloseReason::GoingAway); - this->disable_automatic_websocket_reconnects = false; +void ConnectivityManager::disconnect() { + this->wants_to_be_connected = false; + this->websocket_timer.stop(); + if (this->websocket != nullptr) { + this->websocket->disconnect(WebsocketCloseReason::Normal); } } -void ConnectivityManager::on_network_disconnected(OCPPInterfaceEnum ocpp_interface) { - - const int actual_configuration_slot = get_active_network_configuration_slot(); - std::optional network_connection_profile = - this->get_network_connection_profile(actual_configuration_slot); +void ConnectivityManager::confirm_successful_connection() { + const int config_slot_int = this->get_active_network_configuration_slot(); - if (!network_connection_profile.has_value()) { - EVLOG_warning << "Network disconnected. No network connection profile configured"; - } else if (ocpp_interface == network_connection_profile.value().ocppInterface) { - // Since there is no connection anymore: disconnect the websocket, the manager will try to connect with the next - // available network connection profile as we enable reconnects. - this->disconnect_websocket(ocpp::WebsocketCloseReason::GoingAway); - this->disable_automatic_websocket_reconnects = false; - } -} + const auto network_connection_profile = this->get_network_connection_profile(config_slot_int); -bool ConnectivityManager::on_try_switch_network_connection_profile(const int32_t configuration_slot) { - if (!is_higher_priority_profile(configuration_slot)) { - return false; + if (const auto& security_profile_cv = ControllerComponentVariables::SecurityProfile; + security_profile_cv.variable.has_value()) { + this->device_model.set_read_only_value(security_profile_cv.component, security_profile_cv.variable.value(), + AttributeEnum::Actual, + std::to_string(network_connection_profile.value().securityProfile), + VARIABLE_ATTRIBUTE_VALUE_SOURCE_INTERNAL); } - EVLOG_info << "Trying to connect with higher priority network connection profile (configuration slots: " - << this->get_active_network_configuration_slot() << " --> " << configuration_slot << ")."; - - const std::optional network_connection_profile_opt = - this->get_network_connection_profile(configuration_slot); - if (!network_connection_profile_opt.has_value()) { - EVLOG_warning << "Could not find network connection profile belonging to configuration slot " - << configuration_slot; - return false; - } - this->disconnect_websocket(WebsocketCloseReason::Normal); - reconnect(WebsocketCloseReason::Normal, get_configuration_slot_priority(configuration_slot)); - return true; + this->remove_network_connection_profiles_below_actual_security_profile(); + this->check_cache_for_invalid_security_profiles(); } -void ConnectivityManager::init_websocket() { +void ConnectivityManager::try_connect_websocket() { if (this->device_model.get_value(ControllerComponentVariables::ChargePointId).find(':') != std::string::npos) { EVLOG_AND_THROW(std::runtime_error("ChargePointId must not contain \':\'")); } - // cache the network profiles on initialization - if (!cache_network_connection_profiles()) { - EVLOG_warning << "No network connection profiles configured, aborting websocket connection."; - return; - } - - const int config_slot_int = this->get_active_network_configuration_slot(); + // Check the cache runtime since security profile might change async + this->check_cache_for_invalid_security_profiles(); - const auto network_connection_profile = this->get_network_connection_profile(config_slot_int); + const int configuration_slot_to_set = + this->pending_configuration_slot.value_or(this->get_active_network_configuration_slot()); + const auto network_connection_profile = this->get_network_connection_profile(configuration_slot_to_set); // Not const as the iface member can be set by the configure network connection profile callback - auto connection_options = this->get_ws_connection_options(config_slot_int); + auto connection_options = this->get_ws_connection_options(configuration_slot_to_set); bool can_use_connection_profile = true; if (!network_connection_profile.has_value()) { - EVLOG_warning << "No network connection profile configured for " << config_slot_int; + EVLOG_warning << "No network connection profile configured for " << configuration_slot_to_set; + can_use_connection_profile = false; + } else if (!connection_options.has_value()) { + EVLOG_warning << "Connection profile configured for " << configuration_slot_to_set << " failed: not valid URL"; can_use_connection_profile = false; } else if (this->configure_network_connection_profile_callback.has_value()) { - EVLOG_debug << "Request to configure network connection profile " << config_slot_int; - - std::future config_status = this->configure_network_connection_profile_callback.value()( - config_slot_int, network_connection_profile.value()); - const int32_t config_timeout = - this->device_model.get_optional_value(ControllerComponentVariables::NetworkConfigTimeout) - .value_or(default_network_config_timeout_seconds); - - std::future_status status = config_status.wait_for(std::chrono::seconds(config_timeout)); - - switch (status) { - case std::future_status::deferred: - case std::future_status::timeout: { - EVLOG_warning << "Timeout configuring config slot: " << config_slot_int; + std::optional config = handle_configure_network_connection_profile_callback( + configuration_slot_to_set, network_connection_profile.value()); + if (config.has_value() && config->success) { + connection_options->iface = config->interface_address; + } else { + EVLOG_warning << "Could not use config slot " << configuration_slot_to_set; can_use_connection_profile = false; - break; - } - case std::future_status::ready: { - ConfigNetworkResult result = config_status.get(); - if (result.success and result.network_profile_slot == config_slot_int) { - EVLOG_debug << "Config slot " << config_slot_int << " is configured"; - // Set interface or ip to connection options. - connection_options.iface = result.interface_address; - } else { - EVLOG_warning << "Could not configure config slot " << config_slot_int; - can_use_connection_profile = false; - } - break; - } } } if (!can_use_connection_profile) { - this->websocket_timer.timeout( - [this]() { - this->next_network_configuration_priority(); - this->start(); - }, - WEBSOCKET_INIT_DELAY); + if (this->wants_to_be_connected) { + this->websocket_timer.timeout( + [this, configuration_slot_to_set] { + this->pending_configuration_slot = get_next_configuration_slot(configuration_slot_to_set); + this->try_connect_websocket(); + }, + WEBSOCKET_INIT_DELAY); + } return; } - EVLOG_info << "Open websocket with NetworkConfigurationPriority: " << this->network_configuration_priority + 1 - << " which is configurationSlot " << config_slot_int; + this->pending_configuration_slot.reset(); + this->active_network_configuration_priority = + get_priority_from_configuration_slot(configuration_slot_to_set).value(); + + EVLOG_info << "Open websocket with NetworkConfigurationPriority: " + << this->active_network_configuration_priority + 1 << " which is configurationSlot " + << configuration_slot_to_set; if (const auto& active_network_profile_cv = ControllerComponentVariables::ActiveNetworkProfile; active_network_profile_cv.variable.has_value()) { this->device_model.set_read_only_value( active_network_profile_cv.component, active_network_profile_cv.variable.value(), AttributeEnum::Actual, - std::to_string(config_slot_int), VARIABLE_ATTRIBUTE_VALUE_SOURCE_INTERNAL); - } - - if (const auto& security_profile_cv = ControllerComponentVariables::SecurityProfile; - security_profile_cv.variable.has_value()) { - this->device_model.set_read_only_value(security_profile_cv.component, security_profile_cv.variable.value(), - AttributeEnum::Actual, - std::to_string(network_connection_profile.value().securityProfile), - VARIABLE_ATTRIBUTE_VALUE_SOURCE_INTERNAL); + std::to_string(configuration_slot_to_set), VARIABLE_ATTRIBUTE_VALUE_SOURCE_INTERNAL); } if (this->websocket == nullptr) { - this->websocket = std::make_unique(connection_options, this->evse_security, this->logging); + this->websocket = std::make_unique(connection_options.value(), this->evse_security, this->logging); this->websocket->register_connected_callback( std::bind(&ConnectivityManager::on_websocket_connected, this, std::placeholders::_1)); @@ -285,7 +232,7 @@ void ConnectivityManager::init_websocket() { this->websocket->register_closed_callback( std::bind(&ConnectivityManager::on_websocket_closed, this, std::placeholders::_1)); } else { - this->websocket->set_connection_options(connection_options); + this->websocket->set_connection_options(connection_options.value()); } // Attach external callbacks everytime since they might have changed @@ -294,9 +241,79 @@ void ConnectivityManager::init_websocket() { } this->websocket->register_message_callback([this](const std::string& message) { this->message_callback(message); }); + + this->websocket->connect(); +} + +std::optional +ConnectivityManager::handle_configure_network_connection_profile_callback(int slot, + const NetworkConnectionProfile& profile) { + if (!this->configure_network_connection_profile_callback.has_value()) { + return std::nullopt; + } + + std::future config_status = + this->configure_network_connection_profile_callback.value()(slot, profile); + const int32_t config_timeout = + this->device_model.get_optional_value(ControllerComponentVariables::NetworkConfigTimeout) + .value_or(default_network_config_timeout_seconds); + + if (config_status.wait_for(std::chrono::seconds(config_timeout)) == std::future_status::ready) { + return config_status.get(); + } + + EVLOG_warning << "Timeout configuring config slot: " << slot; + return std::nullopt; +} + +int ConnectivityManager::get_next_configuration_slot(int32_t configuration_slot) { + + if (this->network_connection_slots.size() > 1) { + EVLOG_info << "Switching to next network configuration priority"; + } + const auto network_configuration_priority_opt = get_priority_from_configuration_slot(configuration_slot); + + const int network_configuration_priority = + network_configuration_priority_opt.has_value() + ? (network_configuration_priority_opt.value() + 1) % (this->network_connection_slots.size()) + : 0; + + return get_configuration_slot_from_priority(network_configuration_priority); +} + +bool ConnectivityManager::send_to_websocket(const std::string& message) { + if (this->websocket == nullptr) { + return false; + } + + return this->websocket->send(message); +} + +void ConnectivityManager::on_network_disconnected(OCPPInterfaceEnum ocpp_interface) { + + const int actual_configuration_slot = get_active_network_configuration_slot(); + std::optional network_connection_profile = + this->get_network_connection_profile(actual_configuration_slot); + + if (!network_connection_profile.has_value()) { + EVLOG_warning << "Network disconnected. No network connection profile configured"; + } else if (ocpp_interface == network_connection_profile.value().ocppInterface) { + // Since there is no connection anymore: disconnect the websocket, the manager will try to connect with the next + // available network connection profile as we enable reconnects. + EVLOG_info << "ConnectivityManager::on_network_disconnected"; + this->websocket->disconnect(ocpp::WebsocketCloseReason::GoingAway); + } +} + +void ConnectivityManager::on_charging_station_certificate_changed() { + if (this->websocket != nullptr) { + // After the websocket gets closed a reconnect will be triggered + this->websocket->disconnect(WebsocketCloseReason::ServiceRestart); + } } -WebsocketConnectionOptions ConnectivityManager::get_ws_connection_options(const int32_t configuration_slot) { +std::optional +ConnectivityManager::get_ws_connection_options(const int32_t configuration_slot) { const auto network_connection_profile_opt = this->get_network_connection_profile(configuration_slot); if (!network_connection_profile_opt.has_value()) { @@ -306,40 +323,48 @@ WebsocketConnectionOptions ConnectivityManager::get_ws_connection_options(const const auto network_connection_profile = network_connection_profile_opt.value(); - auto uri = Uri::parse_and_validate( - network_connection_profile.ocppCsmsUrl.get(), - this->device_model.get_value(ControllerComponentVariables::SecurityCtrlrIdentity), - network_connection_profile.securityProfile); - - WebsocketConnectionOptions connection_options{ - OcppProtocolVersion::v201, - uri, - network_connection_profile.securityProfile, - this->device_model.get_optional_value(ControllerComponentVariables::BasicAuthPassword), - this->device_model.get_value(ControllerComponentVariables::RetryBackOffRandomRange), - this->device_model.get_value(ControllerComponentVariables::RetryBackOffRepeatTimes), - this->device_model.get_value(ControllerComponentVariables::RetryBackOffWaitMinimum), - this->device_model.get_value(ControllerComponentVariables::NetworkProfileConnectionAttempts), - this->device_model.get_value(ControllerComponentVariables::SupportedCiphers12), - this->device_model.get_value(ControllerComponentVariables::SupportedCiphers13), - this->device_model.get_value(ControllerComponentVariables::WebSocketPingInterval), - this->device_model.get_optional_value(ControllerComponentVariables::WebsocketPingPayload) - .value_or("payload"), - this->device_model.get_optional_value(ControllerComponentVariables::WebsocketPongTimeout).value_or(5), - this->device_model.get_optional_value(ControllerComponentVariables::UseSslDefaultVerifyPaths) - .value_or(true), - this->device_model.get_optional_value(ControllerComponentVariables::AdditionalRootCertificateCheck) - .value_or(false), - std::nullopt, // hostName - this->device_model.get_optional_value(ControllerComponentVariables::VerifyCsmsCommonName).value_or(true), - this->device_model.get_optional_value(ControllerComponentVariables::UseTPM).value_or(false), - this->device_model.get_optional_value(ControllerComponentVariables::VerifyCsmsAllowWildcards) - .value_or(false), - this->device_model.get_optional_value(ControllerComponentVariables::IFace), - this->device_model.get_optional_value(ControllerComponentVariables::EnableTLSKeylog).value_or(false), - this->device_model.get_optional_value(ControllerComponentVariables::TLSKeylogFile)}; - - return connection_options; + try { + auto uri = Uri::parse_and_validate( + network_connection_profile.ocppCsmsUrl.get(), + this->device_model.get_value(ControllerComponentVariables::SecurityCtrlrIdentity), + network_connection_profile.securityProfile); + + WebsocketConnectionOptions connection_options{ + OcppProtocolVersion::v201, + uri, + network_connection_profile.securityProfile, + this->device_model.get_optional_value(ControllerComponentVariables::BasicAuthPassword), + this->device_model.get_value(ControllerComponentVariables::RetryBackOffRandomRange), + this->device_model.get_value(ControllerComponentVariables::RetryBackOffRepeatTimes), + this->device_model.get_value(ControllerComponentVariables::RetryBackOffWaitMinimum), + this->device_model.get_value(ControllerComponentVariables::NetworkProfileConnectionAttempts), + this->device_model.get_value(ControllerComponentVariables::SupportedCiphers12), + this->device_model.get_value(ControllerComponentVariables::SupportedCiphers13), + this->device_model.get_value(ControllerComponentVariables::WebSocketPingInterval), + this->device_model.get_optional_value(ControllerComponentVariables::WebsocketPingPayload) + .value_or("payload"), + this->device_model.get_optional_value(ControllerComponentVariables::WebsocketPongTimeout).value_or(5), + this->device_model.get_optional_value(ControllerComponentVariables::UseSslDefaultVerifyPaths) + .value_or(true), + this->device_model.get_optional_value(ControllerComponentVariables::AdditionalRootCertificateCheck) + .value_or(false), + std::nullopt, // hostName + this->device_model.get_optional_value(ControllerComponentVariables::VerifyCsmsCommonName) + .value_or(true), + this->device_model.get_optional_value(ControllerComponentVariables::UseTPM).value_or(false), + this->device_model.get_optional_value(ControllerComponentVariables::VerifyCsmsAllowWildcards) + .value_or(false), + this->device_model.get_optional_value(ControllerComponentVariables::IFace), + this->device_model.get_optional_value(ControllerComponentVariables::EnableTLSKeylog).value_or(false), + this->device_model.get_optional_value(ControllerComponentVariables::TLSKeylogFile)}; + + return connection_options; + + } catch (const std::invalid_argument& e) { + EVLOG_error << "Could not configure the connection options: " << e.what(); + } + + return std::nullopt; } void ConnectivityManager::on_websocket_connected([[maybe_unused]] int security_profile) { @@ -363,95 +388,110 @@ void ConnectivityManager::on_websocket_disconnected() { } void ConnectivityManager::on_websocket_closed(ocpp::WebsocketCloseReason reason) { - EVLOG_warning << "Closed websocket of NetworkConfigurationPriority: " << this->network_configuration_priority + 1 - << " which is configurationSlot " << this->get_active_network_configuration_slot(); + EVLOG_warning << "Closed websocket of NetworkConfigurationPriority: " + << this->active_network_configuration_priority + 1 << " which is configurationSlot " + << this->get_active_network_configuration_slot(); - if (!this->disable_automatic_websocket_reconnects) { - reconnect(reason); - } -} - -void ConnectivityManager::reconnect(WebsocketCloseReason reason, std::optional next_priority) { - this->websocket_timer.timeout( - [this, reason, next_priority]() { - if (reason != WebsocketCloseReason::ServiceRestart) { - if (!next_priority.has_value()) { - this->next_network_configuration_priority(); - } else { - this->network_configuration_priority = next_priority.value(); + if (this->wants_to_be_connected) { + this->websocket_timer.timeout( + [this, reason] { + if (reason != WebsocketCloseReason::ServiceRestart) { + this->pending_configuration_slot = + get_next_configuration_slot(get_active_network_configuration_slot()); } - } - this->start(); - }, - WEBSOCKET_INIT_DELAY); -} - -bool ConnectivityManager::is_higher_priority_profile(const int new_configuration_slot) { - - const int current_slot = get_active_network_configuration_slot(); - if (current_slot == 0) { - // No slot in use, new is always higher priority. - return true; + this->try_connect_websocket(); + }, + WEBSOCKET_INIT_DELAY); } +} - if (current_slot == new_configuration_slot) { - // Slot is the same, probably already connected - return false; - } +void ConnectivityManager::cache_network_connection_profiles() { + // get all the network connection profiles from the device model and cache them + this->cached_network_connection_profiles = + json::parse(this->device_model.get_value(ControllerComponentVariables::NetworkConnectionProfiles)); - const std::optional new_priority = get_configuration_slot_priority(new_configuration_slot); - if (!new_priority.has_value()) { - // Slot not found. - return false; + for (const std::string& str : ocpp::split_string( + this->device_model.get_value(ControllerComponentVariables::NetworkConfigurationPriority), + ',')) { + int num = std::stoi(str); + this->network_connection_slots.push_back(num); } +} - const std::optional current_priority = get_configuration_slot_priority(current_slot); - if (!current_priority.has_value()) { - // Slot not found. - return false; - } +void ConnectivityManager::check_cache_for_invalid_security_profiles() { + const auto security_level = this->device_model.get_value(ControllerComponentVariables::SecurityProfile); - if (new_priority.value() < current_priority.value()) { - // Priority is indeed higher (lower index means higher priority) - return true; + if (this->last_known_security_level == security_level) { + return; } + this->last_known_security_level = security_level; - return false; -} + // Use active slot + auto before_slot = this->pending_configuration_slot.value_or(this->get_active_network_configuration_slot()); -int ConnectivityManager::get_active_network_configuration_slot() { - return this->network_connection_priorities.at(this->network_configuration_priority); -} + auto is_lower_security_level = [this, security_level](const int slot) { + const auto opt_profile = this->get_network_connection_profile(slot); + return !opt_profile.has_value() || opt_profile->securityProfile < security_level; + }; -void ConnectivityManager::next_network_configuration_priority() { + this->network_connection_slots.erase(std::remove_if(this->network_connection_slots.begin(), + this->network_connection_slots.end(), is_lower_security_level), + this->network_connection_slots.end()); - // retrieve priorities from cache - if (this->network_connection_priorities.size() > 1) { - EVLOG_info << "Switching to next network configuration priority"; + // Use the active slot and if not valid any longer use the next available one + auto opt_priority = this->get_priority_from_configuration_slot(before_slot); + if (opt_priority) { + this->pending_configuration_slot = before_slot; + } else { + this->pending_configuration_slot = this->get_next_configuration_slot(before_slot); } - this->network_configuration_priority = - (this->network_configuration_priority + 1) % (this->network_connection_priorities.size()); } -bool ConnectivityManager::cache_network_connection_profiles() { +void ConnectivityManager::remove_network_connection_profiles_below_actual_security_profile() { + // Remove all the profiles that are a lower security level than security_level + const auto security_level = this->device_model.get_value(ControllerComponentVariables::SecurityProfile); - if (!this->network_connection_profiles.empty()) { - EVLOG_debug << " Network connection profiles already cached"; - return true; - } - - // get all the network connection profiles from the device model and cache them - this->network_connection_profiles = + auto network_connection_profiles = json::parse(this->device_model.get_value(ControllerComponentVariables::NetworkConnectionProfiles)); - for (const std::string& str : ocpp::split_string( - this->device_model.get_value(ControllerComponentVariables::NetworkConfigurationPriority), - ',')) { - int num = std::stoi(str); - this->network_connection_priorities.push_back(num); + auto is_lower_security_level = [security_level](const SetNetworkProfileRequest& item) { + return item.connectionData.securityProfile < security_level; + }; + + network_connection_profiles.erase( + std::remove_if(network_connection_profiles.begin(), network_connection_profiles.end(), is_lower_security_level), + network_connection_profiles.end()); + + this->device_model.set_value(ControllerComponentVariables::NetworkConnectionProfiles.component, + ControllerComponentVariables::NetworkConnectionProfiles.variable.value(), + AttributeEnum::Actual, network_connection_profiles.dump(), + VARIABLE_ATTRIBUTE_VALUE_SOURCE_INTERNAL); + + // Update the NetworkConfigurationPriority so only remaining profiles are in there + const auto network_priority = ocpp::split_string( + this->device_model.get_value(ControllerComponentVariables::NetworkConfigurationPriority), ','); + + auto in_network_profiles = [&network_connection_profiles](const std::string& item) { + auto is_same_slot = [&item](const SetNetworkProfileRequest& profile) { + return std::to_string(profile.configurationSlot) == item; + }; + return std::any_of(network_connection_profiles.begin(), network_connection_profiles.end(), is_same_slot); + }; + + std::string new_network_priority; + for (const auto& item : network_priority) { + if (in_network_profiles(item)) { + if (!new_network_priority.empty()) { + new_network_priority += ','; + } + new_network_priority += item; + } } - return !this->network_connection_priorities.empty(); + this->device_model.set_value(ControllerComponentVariables::NetworkConfigurationPriority.component, + ControllerComponentVariables::NetworkConfigurationPriority.variable.value(), + AttributeEnum::Actual, new_network_priority, VARIABLE_ATTRIBUTE_VALUE_SOURCE_INTERNAL); } + } // namespace v201 } // namespace ocpp