.. error_framework: Error Framework #################### **************************** Purpose **************************** The error framework is used to handle errors in the EVerest framework. As not every module can "decide" by itself how to react to an error and how to handle it, the error framework provides functionality that allows modules to react to errors that are raised in required other modules. The other main purpose of the error framework is to provide a way to monitor and report errors in the system. This can be used for example for displaying or reporting to an OCPP backend. **************************** Usage **************************** General ============ Raise an error -------------------- Each implementation of an interface can raise errors that are defined in the interface. There is one function `raise_error` that takes an error object as argument. The error object is an instance of the class `Error`. To create the initial error object, the `ErrorFactory` is used. Clear an error -------------------- An error can be cleared by the same implementation that raised the error. There are multiple functions to clear an error. The function `clear_error` provides two different signatures. The first signature takes the `ErrorType` and a boolean `clear_all` as arguments. `clear_all` is optional and defaults to `False`. If `clear_all` is `True`, all errors of the given `ErrorType` are cleared. If `clear_all` is `False`, only the Error with `ErrorType` = "" is cleared. The second signature takes `ErrorType` and `ErrorSubType` as arguments. In this case, only the error with the given `ErrorType` and `ErrorSubType` is cleared. The function `clear_all_errors_of_impl` clears all errors of the current implementation. Subscribe to an error -------------------- A module can subscribe to errors of its requirements. This way the module can react to those errors. There are two functions to subscribe to errors. The function `subscribe_error` takes the `ErrorType` and two callback functions as arguments. The `ErrorType` is the type of the error that the module wants to subscribe to. The first callback is called when the error is raised. The second callback is called when the error is cleared. The function `subscribe_all_errors` takes again two callback functions as arguments. The first callback is called when an error of any type is raised by the requirement. The second callback is called when an error is cleared. Subscribe globally to all errors ------------------------------ This feature is currently only available for C++ modules. It allows a module to susbcribe to globally all errors of all modules. This can be used for example for logging purposes or error reporting. To enable this functionality, the flag `enable_global_errors` in the module's manifest file must be set to `true`. With this, the function `subscribe_global_all_errors` is added to the autogenerated code. This way the function can be used as the other subscribe functions, with two callback functions as arguments. The ErrorFactory ----------------------- Since a module does not have direct access to some information that is required to create an error object, as for example the `module_id`, the class `ErrorFactory` is used, which is provided for each implementation of an interface, with correct default values. The `ErrorFactory` is used to create the initial error object. This error object can be raised as it is, or can be modified before raising. The `ErrorFactory` provides multiple signatures for the function `create_error`: The first signature takes no arguments and creates an error object with default values. The second signature takes the `ErrorType`, `ErrorSubType` and `message` as arguments. The third signature takes the `ErrorType`, `ErrorSubType`, `message` and `severity` as arguments. The fourth signature takes the `ErrorType`, `ErrorSubType`, `message` and `state` as arguments. The fifth signature takes the `ErrorType`, `ErrorSubType`, `message`, `severity` and `state` as arguments. The ErrorStateMonitor ----------------------- The `ErrorStateMonitor` is a class that is used to monitor the error state of implementations and requirements. It is provided for each implementation of an interface and for each requirement of an module. To check if an error is currently active, the function `is_error_active` is used. This function takes the `ErrorType` and `ErrorSubType` as arguments and returns a boolean value. If the error is active, the function returns `True`, otherwise `False`. To check if a specific set of errors is in a specific state, the struct `StateCondition` is defined. This struct has the members `ErrorType`, `ErrorSubType` and `active: boolean`. The function `is_condition_satisfied` can either take a single `StateCondition` or a list of `StateCondition` as argument. If a single `StateCondition` is passed, the function returns `True` if the error is in the state as defined in the `StateCondition`. If a list of `StateCondition` is passed, the function returns `True` if all conditions are satisfied. Syntax in a C++ module ==================== You can find two example modules written in C++ in the `examples` folder: `ExampleErrorRaiser` and `ExampleErrorSubscriber`. Raise an error -------------------- Can be done in the implementation of an interface. .. code-block:: cpp // Create an error object Error error_object = this->error_factory->create_error( "example/ExampleErrorA", // ErrorType "", // ErrorSubType "This is an example error" // message ); // Raise the error raise_error(error_object); Clear an error -------------------- Can be done in the implementation of an interface. .. code-block:: cpp // Clear all errors of the ErrorType "example/ExampleErrorA" clear_error( "example/ExampleErrorA", // ErrorType true // clear_all ); // Clear the error with ErrorType "example/ExampleErrorA" and ErrorSubType "" clear_error( "example/ExampleErrorA", // ErrorType "" // ErrorSubType ); clear_error( "example/ExampleErrorA", // ErrorType false // clear_all ); clear_error( "example/ExampleErrorA" // ErrorType ); // clear_all defaults to false // Clear all errors of the current implementation clear_all_errors_of_impl(); Subscribe to an error -------------------- May be done in the `init` function of the implementation. .. code-block:: cpp // Subscribe to an error of the ErrorType "example/ExampleErrorA" subscribe_error( "example/ExampleErrorA", // ErrorType [](Error error) { // callback // Do something when the error is raised }, [](Error error) { // clear_callback // Do something when the error is cleared } ); // Subscribe to all errors of the requirement subscribe_all_errors( [](Error error) { // callback // Do something when an error is raised }, [](Error error) { // clear_callback // Do something when an error is cleared } ); Subscribe to global all errors ------------------------------ Needs to be enabled in the manifest file of the module. May be done in the `init` function of the implementation. .. code-block:: cpp // Subscribe to global all errors subscribe_global_all_errors( [](Error error) { // callback // Do something when an error is raised }, [](Error error) { // clear_callback // Do something when an error is cleared } ); The ErrorFactory ----------------------- Is used to create an error object. .. code-block:: cpp Error error_object_0 = this->error_factory->create_error(); Error error_object_1 = this->error_factory->create_error( "example/ExampleErrorA", // ErrorType "", // ErrorSubType "This is an example error" // message ); Error error_object_2 = this->error_factory->create_error( "example/ExampleErrorA", // ErrorType "", // ErrorSubType "This is an example error", // message Everest::error::Severity::High // severity ); Error error_object_3 = this->error_factory->create_error( "example/ExampleErrorA", // ErrorType "", // ErrorSubType "This is an example error", // message Everest::error::State::Active // state ); Error error_object_4 = this->error_factory->create_error( "example/ExampleErrorA", // ErrorType "", // ErrorSubType "This is an example error", // message Everest::error::Severity::High, // severity Everest::error::State::Active // state ); The ErrorStateMonitor ----------------------- Is used to monitor the error state of implementations and requirements. Can be accessed in the implementation of an interface / anytime for requirements. Get the `ErrorStateMonitor`: .. code-block:: cpp // Get the ErrorStateMonitor of an implementation std::shared_ptr& monitor = this->error_state_monitor; // Get the ErrorStateMonitor of a requirement std::shared_ptr& monitor = this->mod->r_example_raiser->error_state_monitor; Check if an error is active: .. code-block:: cpp // Check if an error of the ErrorType "example/ExampleErrorA" is active bool is_active = monitor->is_error_active( "example/ExampleErrorA", // ErrorType "" // ErrorSubType ); Check if a specific set of errors is in a specific state: .. code-block:: cpp // Check if an error of the ErrorType "example/ExampleErrorA" is active StateCondition condition = { "example/ExampleErrorA", // ErrorType "", // ErrorSubType true // active }; bool is_satisfied = monitor->is_condition_satisfied(condition); // Check if multiple errors are active std::list conditions = { { "example/ExampleErrorA", // ErrorType "", // ErrorSubType true // active }, { "example/ExampleErrorB", // ErrorType "", // ErrorSubType true // active } }; bool are_satisfied = monitor->is_condition_satisfied(conditions); Syntax in a Python module ==================== You can find two example modules written in Python in the `examples` folder: `PyExampleErrorRaiser` and `PyExampleErrorSubscriber`. The error related classes need to be imported from the `everest` module. .. code-block:: python from everest.framework import error Raise an error -------------------- Can be done in the implementation of an interface after initializing. In opposite to the C++ implementation, the raise function is called on the module object and takes additionally the `implementation_id` as argument. .. code-block:: python # Create an error object error_object = self._mod.get_error_factory("example_raiser").create_error( "example/ExampleErrorA", # ErrorType "", # ErrorSubType "This is an example error" # message ) # Raise the error self._mod.raise_error( "example_raiser", # implementation_id error_object # error ) Clear an error -------------------- Can be done in the implementation of an interface after raising. In opposite to the C++ implementation, the clear function is called on the module object and takes additionally the `implementation_id` as argument. .. code-block:: python # Clear all errors of the ErrorType "example/ExampleErrorA" self._mod.clear_error( "example_raiser", # implementation_id "example/ExampleErrorA", # ErrorType True # clear_all ) # Clear the error with ErrorType "example/ExampleErrorA" and ErrorSubType "" self._mod.clear_error( "example_raiser", # implementation_id "example/ExampleErrorA", # ErrorType "" # ErrorSubType ) self._mod.clear_error( "example_raiser", # implementation_id "example/ExampleErrorA", # ErrorType False # clear_all ) self._mod.clear_error( "example_raiser", # implementation_id "example/ExampleErrorA" # ErrorType ) # clear_all defaults to false # Clear all errors of the current implementation self._mod.clear_all_errors_of_impl( "example_raiser" # implementation_id ) Subscribe to an error -------------------- Can be done in the `init` function of the implementation. In opposite to the C++ implementation, the subscribe function is called on the module object and takes additionally the `requirement` as argument. .. code-block:: python # Subscribe to an error of the ErrorType "example/ExampleErrorA" self._mod.subscribe_error( self._setup.connections["example_raiser"][0], # requirement "example/ExampleErrorA", # ErrorType lambda error: print("Error raised: ", error), # callback lambda error: print("Error cleared: ", error) # clear_callback ) # Subscribe to all errors of the requirement self._mod.subscribe_all_errors( self._setup.connections["example_raiser"][0], # implementation_id lambda error: print("Error raised: ", error), # callback lambda error: print("Error cleared: ", error) # clear_callback ) Subscribe to global all errors ------------------------------ This feature is currently only available for C++ modules. The ErrorFactory ----------------------- Is used to create an error object. .. code-block:: python error_object_0 = self._mod.get_error_factory("example_raiser").create_error() error_object_1 = self._mod.get_error_factory("example_raiser").create_error( "example/ExampleErrorA", # ErrorType "", # ErrorSubType "This is an example error" # message ) error_object_2 = self._mod.get_error_factory("example_raiser").create_error( "example/ExampleErrorA", # ErrorType "", # ErrorSubType "This is an example error", # message error.Severity.High # severity ) error_object_3 = self._mod.get_error_factory("example_raiser").create_error( "example/ExampleErrorA", # ErrorType "", # ErrorSubType "This is an example error", # message error.State.Active # state ) error_object_4 = self._mod.get_error_factory("example_raiser").create_error( "example/ExampleErrorA", # ErrorType "", # ErrorSubType "This is an example error", # message error.Severity.High, # severity error.State.Active # state ) The ErrorStateMonitor ----------------------- Get the `ErrorStateMonitor`: .. code-block:: python # Get the ErrorStateMonitor of an implementation monitor = self._mod.get_error_state_monitor_impl( "example_raiser" # implementation_id ) # Get the ErrorStateMonitor of a requirement monitor = self._mod.get_error_state_monitor_req( self._setup.connections["example_raiser"][0] # requirement ) Check if an error is active: .. code-block:: python # Check if an error of the ErrorType "example/ExampleErrorA" is active is_active = monitor.is_error_active( "example/ExampleErrorA", # ErrorType "" # ErrorSubType ) Check if a specific set of errors is in a specific state: .. code-block:: python # Check if an error of the ErrorType "example/ExampleErrorA" is active condition = error.StateCondition( "example/ExampleErrorA", # ErrorType "", # ErrorSubType True # active ) is_satisfied = monitor.is_condition_satisfied(condition) # Check if multiple errors are active conditions = [ error.StateCondition( "example/ExampleErrorA", # ErrorType "", # ErrorSubType True # active ), error.StateCondition( "example/ExampleErrorB", # ErrorType "", # ErrorSubType True # active ) ] are_satisfied = monitor.is_condition_satisfied(conditions) Syntax in a Javascript module ======================== You can find two example modules written in Javascript in the `examples` folder: `JsExampleErrorRaiser` and `JsExampleErrorSubscriber`. Raise an error -------------------- Can be done in the implementation of an interface after initializing. .. code-block:: javascript // Create an error object let error_object = mod.provides.example_raiser.error_factory.create_error( "example/ExampleErrorA", // ErrorType "", // ErrorSubType "This is an example error" // message ); // Raise the error mod.provides.example_raiser.raise_error(error_object); Clear an error -------------------- Can be done in the implementation of an interface after raising. .. code-block:: javascript // Clear all errors of the ErrorType "example/ExampleErrorA" mod.provides.example_raiser.clear_error( "example/ExampleErrorA", // ErrorType true // clear_all ); // Clear the error with ErrorType "example/ExampleErrorA" and ErrorSubType "" mod.provides.example_raiser.clear_error( "example/ExampleErrorA", // ErrorType "" // ErrorSubType ); mod.provides.example_raiser.clear_error( "example/ExampleErrorA", // ErrorType false // clear_all ); mod.provides.example_raiser.clear_error( "example/ExampleErrorA" // ErrorType ); // clear_all defaults to false // Clear all errors of the current implementation mod.provides.example_raiser.clear_all_errors_of_impl(); Subscribe to an error ----------------------- May be done in the `init` function of the implementation. In Javascript, the subscription is limited to only one callback per subscription. So the example below wouldn't be possible since it would subscribe two times to `example/ExampleErrorA`. .. code-block:: javascript // Subscribe to an error of the ErrorType "example/ExampleErrorA" setup.uses.example_raiser.subscribe_error( "example/ExampleErrorA", // ErrorType (error) => { // callback // Do something when the error is raised }, (error) => { // clear_callback // Do something when the error is cleared } ); // Subscribe to all errors of the requirement setup.uses.example_raiser.subscribe_all_errors( (error) => { // callback // Do something when an error is raised }, (error) => { // clear_callback // Do something when an error is cleared } ); Subscribe to global all errors ------------------------------ This feature is currently only available for C++ modules. The ErrorFactory ----------------------- Is used to create new error objects. The function signature of `create_error` with arguments `ErrorType`, `ErrorSubType`, `message`, and `state` is not available in Javascript. .. code-block:: javascript let error_object_0 = mod.provides.example_raiser.error_factory.create_error(); let error_object_1 = mod.provides.example_raiser.error_factory.create_error( "example/ExampleErrorA", // ErrorType "", // ErrorSubType "This is an example error" // message ); let error_object_2 = mod.provides.example_raiser.error_factory.create_error( "example/ExampleErrorA", // ErrorType "", // ErrorSubType "This is an example error", // message error.Severity.High // severity ); let error_object_4 = mod.provides.example_raiser.error_factory.create_error( "example/ExampleErrorA", // ErrorType "", // ErrorSubType "This is an example error", // message error.Severity.High, // severity error.State.Active // state ); The ErrorStateMonitor ----------------------- Get the `ErrorStateMonitor`: .. code-block:: javascript // Get the ErrorStateMonitor of an implementation let monitor = mod.provides.example_raiser.error_state_monitor; // Get the ErrorStateMonitor of a requirement let monitor = setup.uses.example_raiser.error_state_monitor; Check if an error is active: .. code-block:: javascript // Check if an error of the ErrorType "example/ExampleErrorA" is active let is_active = monitor.is_error_active( "example/ExampleErrorA", // ErrorType "" // ErrorSubType ); Check if a specific set of errors is in a specific state: .. code-block:: javascript // Check if an error of the ErrorType "example/ExampleErrorA" is active let condition = { ErrorType: "example/ExampleErrorA", // ErrorType ErrorSubType: "", // ErrorSubType active: true // active }; let is_satisfied = monitor.is_condition_satisfied(condition); // Check if multiple errors are active let conditions = [ { ErrorType: "example/ExampleErrorA", // ErrorType ErrorSubType: "", // ErrorSubType active: true // active }, { ErrorType: "example/ExampleErrorB", // ErrorType ErrorSubType: "", // ErrorSubType active: true // active } ]; let are_satisfied = monitor.is_condition_satisfied(conditions); *************************** Usage Guide *************************** Creating Error objects ======================= Error objects may always be created using the `ErrorFactory` of the implementation. Error objects can be edited after creation, before raising them. The following attributes may not be changed after creation: - `timestamp` - `origin.module_id` - `origin.implementation_id` - `uuid` The global subscription ======================= If a module is subscribed to global all errors, it may do only "reporting" actions, but no "handling" actions. This means that the module does not change its behavior based on the error, but only reports the error for example to a log file. Side effects of raising errors ============================== The error framework allow module implementations to get notified about an error from one of their requirements by subscribing to the error. This can be used for reporting purposes (e.g. via OCPP) or it can be used to adjust the control flow of the module based on the raised error. It is important to note that raising errors can therefore lead to side effects within other modules. The side effects shall be documented as part of the module documentation (see e.g. EvseManager or OCPP). **************************** Architecture **************************** t.b.d. This section is still under construction. The most is implemented in the directories `include/utils/error/` and `lib/error/` in the `everest-framework` repository.