Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

network connection profiles #410

Closed
wants to merge 36 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
f5eae6b
[wip] add a websocked connected and disconnected callback
SNSubramanya Jan 19, 2024
b94f03c
[wip] experiment with promise and future
SNSubramanya Jan 19, 2024
5f03834
make websocket callbacks optional
SNSubramanya Jan 22, 2024
cc6d0a3
[wip] Add a future return for the configure network connection callback
SNSubramanya Jan 23, 2024
9169440
[wip] Add a variable to configure the timeout duration
SNSubramanya Jan 24, 2024
0658697
[wip] Add a check for the timeout value and deferred status
SNSubramanya Jan 24, 2024
aeb45d2
[wip] Added an on_try_switch function to switch to a specific network…
SNSubramanya Jan 26, 2024
5436951
[wip] Add definition for on_try_switch_network_profile
SNSubramanya Jan 29, 2024
2a71918
[wip] some more PR changes
SNSubramanya Feb 1, 2024
b6d22c8
websocket_(dis)connected_callback: Connected callback: NetworkConnect…
Feb 12, 2024
f365eb0
Add network profile slot to config network result.
Feb 14, 2024
00362ff
Change callback interface and add extra function for when network int…
Feb 15, 2024
b6e08cd
Changes on websocket interface to use a new defined enum.
maaikez Feb 19, 2024
9e81cb8
Fixed compiling for new interface
AssemblyJohn Feb 20, 2024
2eee3ed
Change configuration slot to be an int32_t instead of string. Add doc…
maaikez Feb 21, 2024
0dcf551
Implement a bit more of on_try_switch_network_connection_profile.
maaikez Feb 21, 2024
677c2d0
Add connectivity manager.
maaikez Feb 22, 2024
39269f4
Add some callbacks in connectivity manager
maaikez Mar 1, 2024
6cc22b3
Add main thread to connectivity manager.
maaikez Mar 2, 2024
8a5353f
Remove things from ChargePoint that are moved to the ConnectivityMana…
maaikez Mar 4, 2024
6230d9f
Change charge_point so it does not use websocket member anymore. Late…
maaikez Mar 5, 2024
5d5e7ef
On network disconnect: only disconnect if the network that should be …
maaikez Mar 5, 2024
1fa3d07
Fix bug where disconnected network slot was always disconnecting the …
maaikez Mar 7, 2024
28b242d
Add logging for testing and try to fix some bugs.
maaikez Mar 8, 2024
1dad026
Move reconnect timer to connectivity manager. formatting.
maaikez Mar 13, 2024
f47488e
Add websocket failed callback. Add functionality in connectivity mana…
maaikez Mar 14, 2024
42fb35a
Strip websocket classes. Add (a lot of!) logging for debugging purposes.
maaikez Mar 15, 2024
062367b
Add documentation. Fix some crashes when websocket is reconnecting.
maaikez Mar 18, 2024
fe28ee8
Cleanup. Introduce new mutex to prevent pending, active and requested…
maaikez Mar 19, 2024
65e4f34
Fix after rebase
maaikez Mar 19, 2024
e005b18
Removed include for websocket_plain and websocket_tls .hpp from webso…
maaikez Mar 19, 2024
83383e8
Formatting.
maaikez Mar 19, 2024
25c301a
'Fix' some lint issues and clang format issues.
maaikez Mar 19, 2024
10b5ff5
Add some removed code (oops). Move (duplicate) code from chargepoint …
maaikez Mar 25, 2024
9d2e723
Make libwebsockets also work with security profile 1 (so that the url…
maaikez Mar 27, 2024
c2f8e29
Add some documentation.
maaikez Mar 29, 2024
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
18 changes: 18 additions & 0 deletions config/v201/component_schemas/standardized/InternalCtrlr.json
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,24 @@
"description": "If enabled the metervalues configured with the AlignedDataCtrlr will be rounded to the exact time intervals",
"default": false,
"type": "boolean"
},
"NetworkConfigTimeout": {
"variable_name": "NetworkConfigTimeout",
"characteristics": {
"supportsMonitoring": true,
"dataType": "integer",
"minLimit": 1
},
"attributes": [
{
"type": "Actual",
"mutability": "ReadWrite"
}
],
"description": "Timeout value in seconds to wait for a response from a network configuration request",
"minimum": 1,
"default": "160",
"type": "integer"
}
},
"required": [
Expand Down
8 changes: 7 additions & 1 deletion config/v201/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,13 @@
"SupportedCriteria": {
"variable_name": "SupportedCriteria",
"attributes": {
"Actual": "Enabled,Active,Available,Problem"
"Actual": "Enabled,Active,Available"
}
},
"NetworkConfigTimeout": {
"variable_name": "NetworkConfigTimeout",
"attributes": {
"Actual": "160"
}
},
"UpdateCertificateSymlinks": {
Expand Down
1 change: 1 addition & 0 deletions dependencies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ libwebsockets:
- LWS_WITHOUT_TEST_SERVER_EXTPOLL ON
- LWS_WITHOUT_TEST_PING ON
- LWS_WITHOUT_TEST_CLIENT ON
- LWS_WITH_NETWORK ON
gtest:
# GoogleTest now follows the Abseil Live at Head philosophy. We recommend updating to the latest commit in the main branch as often as possible.
git: https://github.com/google/googletest.git
Expand Down
2 changes: 2 additions & 0 deletions doc/build-with-fetchcontent/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ macro(find_package PACKAGE_NAME)
find_nlohmann_json_schema_validator()
elseif("${PACKAGE_NAME}" STREQUAL "websocketpp")
find_websocketpp()
elseif("${PACKAGE_NAME}" STREQUAL "libwebsockets")
find_libwebsockets()
elseif("${PACKAGE_NAME}" STREQUAL "fsm")
find_fsm()
elseif("${PACKAGE_NAME}" STREQUAL "everest-timer")
Expand Down
70 changes: 70 additions & 0 deletions doc/networkconnectivity/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Network connection profile interface

libocpp automatically tries to connect with the given network connection profiles. However, if you want to have more
control, you can use the callbacks that are designated for the network connectivity.

libocpp will connect automatically with the network profile of the highest priority. If that does not succeed, it will
move on to the network profile with the second highest priority, etc.

## Set up interface (optional)

A callback can be implemented to be able to set up the interface. For example if the interface is a modem, it must first
be enabled before it is possible to connect to that interface. To be able to do this, you can implement the callback
`std::future<ConfigNetworkResult>(configure_network_connection_profile_callback(configuration_slot, NetworkConnectionProfile))`

In the implementation of this callback, you have to create a promise and return the future to the promise:
```cpp
std::promise<ocpp::v201::ConfigNetworkResult> promise();
std::future<ocpp::v201::ConfigNetworkResult> future = promise.get_future();
return future;
```

If the network was setup successfully, you can set the values in the future with
```cpp
future.set_value(configNetworkResult);
```
This way libocpp knows it can connect with the given interface and try to do that. A timeout can be configured // TODO which timeout etc???
to be sure libocpp will not wait forever to connect.


### Bind to a specific interface

In some cases, there are multiple network interfaces available and you might want to connect to a specific interface.
In `ConfigNetworkResult` you can set the interface you want the websocket to bind to. Sometimes an interface has more
than one IP address (in the case of a link local / auto IP address for example). In that case you want the websocket
to bind to a specific IP address. The `interface_address` in ConfigNetworkResult supports both. It will bind using the
given network interface (a string containing the name of the interface) or the given ip address (a string containing
the ip address in human readable format).


## Connect to higher network connection profile priority (optional)

Normally, if libocpp is connected with a network connection profile, it will not disconnect. But a situation might
occur that libocpp is connected with a profile with priority 2 or lower and you find out on system level that an
interface (with a higher priority) changed and is now up. In that case you might want to inform libocpp that the higher
priority interface is up and that it can try to connect with that interface. For example when the modem has 2nd priority,
but you want to avoid high costs because of the data rates and switch back to wired network as soon as it is available.
A call is implemented exactly for this reason:
`bool on_try_switch_network_connection_profile(const int32_t configuration_slot)`.
When you call this function, you inform libocpp that there is a network connection profile available and it might try
to connect to that network connection profile (when indeed the priority of that profile is higher and there might
be some more checks).

Note: make sure to not ask for switching to another network profile too often, if it turns out that it can not connect
to the backoffice while there is a link available, there might be an 'unstable' connection to the backoffice.


## Disconnected / connected callbacks

libocpp provides two callbacks for when the websocket is connected and disconnected. It will provide the network slot
in those callbacks, so you can keep the used network connection up and running (for example not disabling the modem) or
disable the network connection (example again: disable the modem).


## Sequence diagram

'core' can be read as any application that implements libocpp

For step 9, ping is one way to check if a CSMS is up, but you of course can implement a way to check this yourself.

![Sequence diagram](networkconnectivity_libocpp.png)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
54 changes: 54 additions & 0 deletions doc/networkconnectivity/networkconnectivity_libocpp.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
@startuml
'https://plantuml.com/sequence-diagram
!pragma teoz true
participant csms
autonumber "<b><font color=red>"
skinparam sequenceArrowThickness 2

== libocpp wants to connect to network connection profile ==

{start} libocpp -> core: std::future<ConfigNetworkResult>(configure_network_connection_profile_callback(\nconfiguration_slot, NetworkConnectionProfile))
activate core #DarkSalmon
core -> core: Setup network, e.g. setup modem
{end} core -> libocpp: promise.set_value(status,\nip_address, etc)

deactivate core
{start} <-> {end}: ... possible delay ...

alt within timeout

' core -> libocpp: on_network_update (ip address)
libocpp -> csms: connect websocket (ip address)
csms -> libocpp: ACK
libocpp -> core: websocket_connected_callback(configuration_slot, NetworkConnectionProfile)
core -> core: disable unneeded interfaces, \ne.g. disable modem
else timeout reached, next network connection profile selected
libocpp --> core: std::future<ConfigNetworkResult>(configure_network_connection_profile_callback(\nconfiguration_slot, NetworkConnectionProfile)) (see 1)
end


== CSMS is connected via connection profile prio 2 (for example modem) but prio 1 (for example eth0) comes up ==

loop until prio 1 csms is found
core -> csms: ping
end

core -> libocpp: on_try_switch_networkconnectionprofile(configuration_slot)
libocpp --> core: std::future<ConfigNetworkResult>(configure_network_connection_profile_callback(\nconfiguration_slot, NetworkConnectionProfile)) (see 1)


== Network is disconnected (for example networkcable removed) ==

core -> libocpp: disconnect csms (on_network_disconnected(configuration_slot, OCPPInterfaceEnum)
libocpp -> core: websocket_disconnected_callback(configuration_slot, NetworkConnectionProfile)
libocpp --> core: std::future<ConfigNetworkResult>(configure_network_connection_profile_callback(\nconfiguration_slot, NetworkConnectionProfile)) (see 1)


== Network is disconnected (websocket timeout) ==

libocpp -> libocpp: disconnect csms
libocpp -> core: websocket_disconnected_callback(configuration_slot, NetworkConnectionProfile)
libocpp --> core: std::future<ConfigNetworkResult>(configure_network_connection_profile_callback(\nconfiguration_slot, NetworkConnectionProfile)) (see 1)


@enduml
31 changes: 31 additions & 0 deletions include/ocpp/common/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,37 @@ enum class MessageDirection {
ChargingStationToCSMS
};

///
/// \brief Reason why a websocket closes its connection
///
enum class WebsocketCloseReason : uint8_t {
/// Normal closure, meaning that the purpose for which the connection was
/// established has been fulfilled.
Normal = 1,
/// Close the connection with a forced TCP drop.
/**
* This special value requests that the WebSocket connection be closed by
* forcibly dropping the TCP connection. This will leave the other side of
* the connection with a broken connection and some expensive timeouts. this
* should not be done except in extreme cases or in cases of malicious
* remote endpoints.
*/
ForceTcpDrop,
/// The endpoint was "going away", such as a server going down or a browser
/// navigating away from a page.
GoingAway,
/// A dummy value to indicate that the connection was closed abnormally.
/**
* In such a case there was no close frame to extract a value from. This
* value is illegal on the wire.
*/
AbnormalClose,
/// Indicates that the service is restarted. A client may reconnect and if
/// if it chooses to do so, should reconnect using a randomized delay of
/// 5-30s
ServiceRestart
};

} // namespace ocpp

#endif
27 changes: 17 additions & 10 deletions include/ocpp/common/websocket/websocket.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@

#include <ocpp/common/evse_security.hpp>
#include <ocpp/common/ocpp_logging.hpp>
#include <ocpp/common/websocket/websocket_plain.hpp>
#include <ocpp/common/websocket/websocket_tls.hpp>
#include <ocpp/common/websocket/websocket_base.hpp>

namespace ocpp {

class WebsocketBase;
struct WebsocketConnectionOptions;

///
/// \brief contains a websocket abstraction that can connect to TLS and non-TLS websocket endpoints
///
Expand All @@ -17,8 +20,8 @@ class Websocket {
// unique_ptr holds address of base - requires WebSocketBase to have a virtual destructor
std::unique_ptr<WebsocketBase> websocket;
std::function<void(const int security_profile)> connected_callback;
std::function<void()> disconnected_callback;
std::function<void(const websocketpp::close::status::value reason)> closed_callback;
std::function<void(const WebsocketCloseReason reason)> closed_callback;
std::function<void(const WebsocketCloseReason reason)> failed_callback;
std::function<void(const std::string& message)> message_callback;
std::shared_ptr<MessageLogging> logging;

Expand All @@ -34,23 +37,27 @@ class Websocket {
void set_connection_options(const WebsocketConnectionOptions& connection_options);

/// \brief disconnect the websocket
void disconnect(websocketpp::close::status::value code);
void disconnect(const WebsocketCloseReason code);

// \brief reconnects the websocket after the delay
void reconnect(std::error_code reason, long delay);
void reconnect();

/// \brief indicates if the websocket is connected
bool is_connected();

/// \brief register a \p callback that is called when the websocket is connected successfully
void register_connected_callback(const std::function<void(const int security_profile)>& callback);

/// \brief register a \p callback that is called when the websocket connection is disconnected
void register_disconnected_callback(const std::function<void()>& callback);

/// \brief register a \p callback that is called when the websocket connection has been closed and will not attempt
/// to reconnect
void register_closed_callback(const std::function<void(const websocketpp::close::status::value reason)>& callback);
void register_closed_callback(const std::function<void(const WebsocketCloseReason reason)>& callback);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can keep the closed_callback and disconnected_callback for now because they are implicitly used to manage the reconnect behavior, but to reduce complexity of the interface I would prefer to not have the seperated after this refactor is done


///
/// \brief Register a callback that is called when the websocket tried to connect, but could not make a connection
/// or was already connected and a failure occured.
/// \param callback The callback.
///
void register_failed_callback(const std::function<void(const WebsocketCloseReason reason)>& callback);

/// \brief register a \p callback that is called when the websocket receives a message
void register_message_callback(const std::function<void(const std::string& message)>& callback);
Expand Down
43 changes: 19 additions & 24 deletions include/ocpp/common/websocket/websocket_base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
bool verify_csms_common_name;
bool use_tpm_tls;
bool verify_csms_allow_wildcards;
std::optional<std::string> iface_or_ip; ///< The interface of the connection or the ip address of the interface
};

enum class ConnectionFailedReason {
Expand All @@ -51,20 +52,14 @@
std::atomic_bool m_is_connected;
WebsocketConnectionOptions connection_options;
std::function<void(const int security_profile)> connected_callback;
std::function<void()> disconnected_callback;
std::function<void(const websocketpp::close::status::value reason)> closed_callback;
std::function<void(const WebsocketCloseReason reason)> closed_callback;
std::function<void(const WebsocketCloseReason reason)> failed_callback;
std::function<void(const std::string& message)> message_callback;
std::function<void(ConnectionFailedReason)> connection_failed_callback;
websocketpp::lib::shared_ptr<boost::asio::steady_timer> reconnect_timer;
std::unique_ptr<Everest::SteadyTimer> ping_timer;
websocketpp::connection_hdl handle;
std::mutex reconnect_mutex;
std::mutex connection_mutex;
std::atomic_int reconnect_backoff_ms;
websocketpp::transport::timer_handler reconnect_callback;
std::atomic_int connection_attempts;
std::atomic_bool shutting_down;
std::atomic_bool reconnecting;
bool shutting_down;

Check notice on line 62 in include/ocpp/common/websocket/websocket_base.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

include/ocpp/common/websocket/websocket_base.hpp#L62

class member 'WebsocketBase::shutting_down' is never used.

/// \brief Indicates if the required callbacks are registered
/// \returns true if the websocket is properly initialized
Expand All @@ -76,13 +71,6 @@
/// \brief Logs websocket connection error
void log_on_fail(const std::error_code& ec, const boost::system::error_code& transport_ec, const int http_status);

/// \brief Calculates and returns the reconnect interval based on int retry_backoff_random_range_s,
/// retry_backoff_repeat_times, int retry_backoff_wait_minimum_s of the WebsocketConnectionOptions
long get_reconnect_interval();

// \brief cancels the reconnect timer
void cancel_reconnect_timer();

/// \brief send a websocket ping
virtual void ping() = 0;

Expand All @@ -103,27 +91,34 @@
virtual void set_connection_options(const WebsocketConnectionOptions& connection_options) = 0;
void set_connection_options_base(const WebsocketConnectionOptions& connection_options);

/// \brief reconnect the websocket after the delay
virtual void reconnect(std::error_code reason, long delay) = 0;
/// \brief reconnect the websocket.
///
/// This is needed because of websocketpp: we can not just call 'connect' because the websocket might be in a wrong
/// state here.
virtual void reconnect() = 0;

/// \brief disconnect the websocket
void disconnect(websocketpp::close::status::value code);
void disconnect(WebsocketCloseReason code);

/// \brief indicates if the websocket is connected
bool is_connected();

/// \brief closes the websocket
virtual void close(websocketpp::close::status::value code, const std::string& reason) = 0;
virtual void close(WebsocketCloseReason code, const std::string& reason, const bool stop_perpetual) = 0;

/// \brief register a \p callback that is called when the websocket is connected successfully
void register_connected_callback(const std::function<void(const int security_profile)>& callback);

/// \brief register a \p callback that is called when the websocket connection is disconnected
void register_disconnected_callback(const std::function<void()>& callback);

/// \brief register a \p callback that is called when the websocket connection has been closed and will not attempt
/// to reconnect
void register_closed_callback(const std::function<void(const websocketpp::close::status::value reason)>& callback);
void register_closed_callback(const std::function<void(const WebsocketCloseReason reason)>& callback);

///
/// \brief Register a callback that is called when the websocket tried to connect, but could not make a connection
/// or was already connected and a failure occured.
/// \param callback The callback.
///
void register_failed_callback(const std::function<void(const WebsocketCloseReason reason)>& callback);

/// \brief register a \p callback that is called when the websocket receives a message
void register_message_callback(const std::function<void(const std::string& message)>& callback);
Expand Down
Loading
Loading