diff --git a/CODEOWNERS b/CODEOWNERS index 04f7c19f02bee..150867a177c4d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -628,6 +628,7 @@ /samples/drivers/ht16k33/ @henrikbrixandersen /samples/drivers/lora/ @Mani-Sadhasivam /samples/subsys/lorawan/ @Mani-Sadhasivam +/samples/subsys/event_manager/ @nordic-krch /samples/modules/canopennode/ @henrikbrixandersen /samples/net/ @rlubos @tbursztyka @pfalcon /samples/net/cloud/tagoio_http_post/ @nandojve @@ -705,6 +706,7 @@ scripts/gen_image_info.py @tejlmand /subsys/debug/asan_hacks.c @vanwinkeljan @aescolar @daor-oti /subsys/demand_paging/ @dcpleung @nashif /subsys/emul/ @sjg20 +/subsys/event_manager/ @nordic-krch /subsys/fb/ @jfischer-no /subsys/fs/ @nashif /subsys/fs/fcb/ @nvlsianpu diff --git a/boards/riscv/tlsr9518adk80d/doc/index.rst b/boards/riscv/tlsr9518adk80d/doc/index.rst index ad0d72712c13b..03c377e96310e 100644 --- a/boards/riscv/tlsr9518adk80d/doc/index.rst +++ b/boards/riscv/tlsr9518adk80d/doc/index.rst @@ -81,6 +81,7 @@ The following example projects are supported: - samples/hello_world - samples/synchronization - samples/philosophers +- samples/application_development/event_manager - samples/basic/threads - samples/basic/blinky - samples/basic/blinky_pwm diff --git a/doc/reference/app_event_manager/em_overview.svg b/doc/reference/app_event_manager/em_overview.svg new file mode 100644 index 0000000000000..27ae5485b9e5e --- /dev/null +++ b/doc/reference/app_event_manager/em_overview.svg @@ -0,0 +1,205 @@ + + + + + + + + + + + + + + + + + + + + + + Page-1 + + Nordic Blue + Application Event Manager + + Application Event Manager + + Nordic Blue.7 + Module 0 + + Module 0 + + Nordic Blue.8 + Module 1 + + Module 1 + + Nordic Blue.9 + Module 2 + + Module 2 + + Nordic Blue.10 + Module 4 + + Module 4 + + Nordic Blue.11 + Driver 0 + + Driver 0 + + Nordic Blue.12 + Driver 1 + + Driver 1 + + Nordic Blue.13 + Hardware + + Hardware + + Dynamic connector.24 + + + + Dynamic connector.25 + + + + Dynamic connector.26 + + + + Dynamic connector.27 + + + + Dynamic connector.28 + + + + Dynamic connector.29 + + + + Dynamic connector.32 + + + + Dynamic connector.33 + + + + Dynamic connector.39 + + + + Nordic Blue.45 + Module 3 + + Module 3 + + Dynamic connector.48 + + + + Dynamic connector.50 + + + + Dynamic connector.54 + + + + Dynamic connector.57 + + + + Dynamic connector.58 + + + + Dynamic connector.59 + + + + Dynamic connector.60 + + + + Dynamic connector.61 + + + + Dynamic connector + + + + Dynamic connector.63 + + + + Nordic Blue.72 + Library 0 + + Library 0 + + Dynamic connector.73 + + + + Sheet.74 + + + + Nordic Blue.75 + + + + Nordic Blue.76 + + + + Sheet.79 + NCS components + + NCS components + + Dynamic connector.82 + + + + Sheet.83 + Modules and events + + Modules and events + + Sheet.84 + Events + + Events + + diff --git a/doc/reference/app_event_manager/index.rst b/doc/reference/app_event_manager/index.rst new file mode 100644 index 0000000000000..bf2626d17215d --- /dev/null +++ b/doc/reference/app_event_manager/index.rst @@ -0,0 +1,393 @@ +.. _app_event_manager: + +Application Event Manager +######################### + +.. contents:: + :local: + :depth: 2 + +The Application Event Manager is a piece of software that supports development of consistent, modular, event-based applications. +In an event-based application, parts of the application functionality are separated into isolated modules that communicate with each other using events. +Events are submitted by modules and other modules can subscribe and react to them. +The Application Event Manager acts as coordinator of the event-based communication. + +.. figure:: em_overview.svg + :alt: Architecture of an application based on Application Event Manager + +See the :ref:`app_event_manager_sample` sample for a simple example of how to use the Application Event Manager. + +Events +****** + +Events are structured data types that are defined by the application and can contain additional data. + +The Application Event Manager handles the events by processing and propagating all of them to the modules (listeners) that subscribe to a specific event. +Multiple modules can subscribe to the same event. +As part of this communication, listeners can process events differently based on their type. + +The Application Event Manager provides API for defining, creating, and subscribing events. +See `Implementing an event type`_ for details about how to create custom event types. + +Modules +******* + +Modules are separate source files that can subscribe to every defined event. +You can use events for communication between modules. + +There is no limitation as to how many events each module can subscribe to. +An application can have as many modules as required. + +The Application Event Manager provides an API for subscribing modules to specific events defined in the application. +When a module subscribes to a specific event, it is called a listener module. +Every listener is identified by a unique name. + +.. _app_event_manager_configuration: + +Configuration +************* + +To use the Application Event Manager, enable it using the :kconfig:option:`CONFIG_APP_EVENT_MANAGER` Kconfig option and initialize it in your :file:`main.c` file. +Initializing the Application Event Manager allows it to handle submitted events and deliver them to modules that subscribe to the specified event type. + +Complete the following steps: + +1. Enable the :kconfig:option:`CONFIG_APP_EVENT_MANAGER` Kconfig option. +#. Include :file:`app_event_manager.h` in your :file:`main.c` file. +#. Call :c:func:`app_event_manager_init()`. + +.. _app_event_manager_implementing_events: + +Implementing events and modules +******************************* + +If an application module is supposed to react to an event, your application must implement an event type, submit the event, and register the module as listener. +Read the following sections for details. + +Implementing an event type +========================== + +If you want to easily create and implement custom event types, the Application Event Manager provides macros to add a new event type in your application. +Complete the following steps: + +* `Create a header file`_ for the event type you want to define +* `Create a source file`_ for the event type + +Create a header file +-------------------- + +To create a header file for the event type you want to define: + +1. Make sure the header file includes the Application Event Manager header file: + + .. code-block:: c + + #include + +#. Define the new event type by creating a structure that contains an :c:struct:`app_event_header` named ``header`` as the first field. +#. Optionally, add additional custom data fields to the structure. +#. Declare the event type with the :c:macro:`APP_EVENT_TYPE_DECLARE` macro, passing the name of the created structure as an argument. + +The following code example shows a header file for the event type :c:struct:`sample_event`: + +.. code-block:: c + + #include + + struct sample_event { + struct app_event_header header; + + /* Custom data fields. */ + int8_t value1; + int16_t value2; + int32_t value3; + }; + + APP_EVENT_TYPE_DECLARE(sample_event); + +In some use cases, the length of the data associated with an event may vary. +You can use the :c:macro:`APP_EVENT_TYPE_DYNDATA_DECLARE` macro instead of :c:macro:`APP_EVENT_TYPE_DECLARE` to declare an event type with variable data size. +In such case, add the data with the variable size as the last member of the event structure. +For example, you can add variable sized data to the previously defined event by applying the following change to the code: + +.. code-block:: c + + #include + + struct sample_event { + struct app_event_header header; + + /* Custom data fields. */ + int8_t value1; + int16_t value2; + int32_t value3; + struct event_dyndata dyndata; + }; + + APP_EVENT_TYPE_DYNDATA_DECLARE(sample_event); + +In this example, the :c:struct:`event_dyndata` structure contains the following information: + +* A zero-length array that is used as a buffer with variable size (:c:member:`event_dyndata.data`). +* A number representing the size of the buffer (:c:member:`event_dyndata.size`). + +Create a source file +-------------------- + +To create a source file for the event type you defined in the header file: + +1. Include the header file for the new event type in your source file. +#. Define the event type with the :c:macro:`APP_EVENT_TYPE_DEFINE` macro. + Pass the name of the event type as declared in the header along with additional parameters. + For example, you can provide a function that fills a buffer with a string version of the event data (used for logging). + The :c:macro:`APP_EVENT_TYPE_DEFINE` macro adds flags as a last parameter. + These flags are constant and can only be set using :c:macro:`APP_EVENT_FLAGS_CREATE` on :c:macro:`APP_EVENT_TYPE_DEFINE` macro. + To not set any flag, use :c:macro:`APP_EVENT_FLAGS_CREATE` without any argument as shown in the below example. + To get value of specific flag, use :c:func:`app_event_get_type_flag` function. + +The following code example shows a source file for the event type ``sample_event``: + +.. code-block:: c + + #include "sample_event.h" + + static void log_sample_event(const struct app_event_header *aeh) + { + struct sample_event *event = cast_sample_event(aeh); + + APP_EVENT_MANAGER_LOG(aeh, "val1=%d val2=%d val3=%d", event->value1, + event->value2, event->value3); + } + + APP_EVENT_TYPE_DEFINE(sample_event, /* Unique event name. */ + log_sample_event, /* Function logging event data. */ + NULL, /* No event info provided. */ + APP_EVENT_FLAGS_CREATE()); /* Flags managing event type. */ + +.. note:: + There is a deprecated way of logging Application Event Manager events by writing a string to the provided buffer. + To use the deprecated way, you need to set the :kconfig:option:`CONFIG_APP_EVENT_MANAGER_USE_DEPRECATED_LOG_FUN` option. + You can then use both ways of logging events. + Application Event Manager figures out which way to be used based on the type of the logging function passed. + +Submitting an event +=================== + +To submit an event of a given type, for example ``sample_event``: + +1. Allocate the event by calling the function with the name *new_event_type_name*. + For example, ``new_sample_event()``. +#. Write values to the data fields. +#. Use :c:macro:`APP_EVENT_SUBMIT` to submit the event. + +The following code example shows how to create and submit an event of type ``sample_event`` that has three data fields: + +.. code-block:: c + + /* Allocate event. */ + struct sample_event *event = new_sample_event(); + + /* Write data to datafields. */ + event->value1 = value1; + event->value2 = value2; + event->value3 = value3; + + /* Submit event. */ + APP_EVENT_SUBMIT(event); + +If an event type also defines data with variable size, you must pass also the size of the data as an argument to the function that allocates the event. +For example, if the ``sample_event`` also contains data with variable size, you must apply the following changes to the code: + +.. code-block:: c + + /* Allocate event. */ + struct sample_event *event = new_sample_event(my_data_size); + + /* Write data to datafields. */ + event->value1 = value1; + event->value2 = value2; + event->value3 = value3; + + /* Write data with variable size. */ + memcpy(event->dyndata.data, my_buf, my_data_size); + + /* Submit event. */ + APP_EVENT_SUBMIT(event); + +After the event is submitted, the Application Event Manager adds it to the processing queue. +When the event is processed, the Application Event Manager notifies all modules that subscribe to this event type. + +.. note:: + Events are dynamically allocated and must be submitted. + If an event is not submitted, it will not be handled and the memory will not be freed. + +.. _app_event_manager_register_module_as_listener: + +Registering a module as listener +================================ + +If you want a module to receive events managed by the Application Event Manager, you must register it as a listener and you must subscribe it to a given event type. + +To turn a module into a listener for specific event types, complete the following steps: + +1. Include the header files for the respective event types, for example, ``#include "sample_event.h"``. +#. :ref:`Implement an Event handler function ` and define the module as a listener with the :c:macro:`APP_EVENT_LISTENER` macro, passing both the name of the module and the event handler function as arguments. +#. Subscribe the listener to specific event types. + +For subscribing to an event type, the Application Event Manager provides three types of subscriptions, differing in priority. +They can be registered with the following macros: + +* :c:macro:`APP_EVENT_SUBSCRIBE_FIRST` - notification as the first subscriber +* :c:macro:`APP_EVENT_SUBSCRIBE_EARLY` - notification before other listeners +* :c:macro:`APP_EVENT_SUBSCRIBE` - standard notification +* :c:macro:`APP_EVENT_SUBSCRIBE_FINAL` - notification as the last, final subscriber + +There is no defined order in which subscribers of the same priority are notified. + +The module will receive events for the subscribed event types only. +The listener name passed to the subscribe macro must be the same one used in the macro :c:macro:`APP_EVENT_LISTENER`. + +.. _app_event_manager_register_module_as_listener_handler: + +Implementing an event handler function +-------------------------------------- + +The event handler function is called when any of the subscribed event types are being processed. +Only one event handler function can be registered per listener. +Therefore, if a listener subscribes to multiple event types, the function must handle all of them. + +The event handler gets a pointer to the :c:struct:`app_event_header` structure as the function argument. +The function should return ``true`` to consume the event, which means that the event is not propagated to further listeners, or ``false``, otherwise. + +To check if an event has a given type, call the function with the name *is*\_\ *event_type_name* (for example, ``is_sample_event()``), passing the pointer to the application event header as the argument. +This function returns ``true`` if the event matches the given type, or ``false`` otherwise. + +To access the event data, cast the :c:struct:`app_event_header` structure to a proper event type, using the function with the name *cast*\_\ *event_type_name* (for example, ``cast_sample_event()``), passing the pointer to the application event header as the argument. + +Code example +------------ + +The following code example shows how to register an event listener with an event handler function and subscribe to the event type ``sample_event``: + +.. code-block:: c + + #include "sample_event.h" + + static bool app_event_handler(const struct app_event_header *aeh) + { + if (is_sample_event(aeh)) { + + /* Accessing event data. */ + struct sample_event *event = cast_sample_event(aeh); + + int8_t v1 = event->value1; + int16_t v2 = event->value2; + int32_t v3 = event->value3; + + /* Actions when received given event type. */ + foo(v1, v2, v3); + + return false; + } + + return false; + } + + APP_EVENT_LISTENER(sample_module, app_event_handler); + APP_EVENT_SUBSCRIBE(sample_module, sample_event); + +The variable size data is accessed in the same way as the other members of the structure defining an event. + +Application Event Manager extensions +************************************ + +The Application Event Manager provides additional features that could be helpful when debugging event-based applications. + +.. _app_event_manager_profiling_init_hooks: + +Initialization hook +=================== + +.. em_initialization_hook_start + +The Application Event Manager provides an initialization hook for any module that relies on the Application Event Manager initialization before the first event is processed. +The hook function should be declared in the ``int hook(void)`` format. +If the hook function returns a non-zero value, the initialization process is interrupted and a related error is returned. + +To register the initialization hook, use the macro :c:macro:`APP_EVENT_MANAGER_HOOK_POSTINIT_REGISTER`. +For details, refer to :ref:`app_event_manager_api`. + +.. em_initialization_hook_end + +.. _app_event_manager_profiling_tracing_hooks: + +Tracing hooks +============= + +.. em_tracing_hooks_start + +The Application Event Manager uses flexible mechanism to implement hooks when an event is submitted, before it is processed, and after its processing. +Oryginally designed to implement event tracing, the tracing hooks can be used for other purposes as well. +The registered hook function should be declared in the ``void hook(const struct app_event_header *aeh)`` format. + +The following macros are implemented to register event tracing hooks: + +* :c:macro:`APP_EVENT_HOOK_ON_SUBMIT_REGISTER_FIRST`, :c:macro:`APP_EVENT_HOOK_ON_SUBMIT_REGISTER`, :c:macro:`APP_EVENT_HOOK_ON_SUBMIT_REGISTER_LAST` +* :c:macro:`APP_EVENT_HOOK_PREPROCESS_REGISTER_FIRST`, :c:macro:`APP_EVENT_HOOK_PREPROCESS_REGISTER`, :c:macro:`APP_EVENT_HOOK_PREPROCESS_REGISTER_LAST` +* :c:macro:`APP_EVENT_HOOK_POSTPROCESS_REGISTER_FIRST`, :c:macro:`APP_EVENT_HOOK_POSTPROCESS_REGISTER`, :c:macro:`APP_EVENT_HOOK_POSTPROCESS_REGISTER_LAST` + +For details, refer to :ref:`app_event_manager_api`. + +.. em_tracing_hooks_end + +.. _app_event_manager_profiling_mem_hooks: + +Memory management hooks +======================= + +The Application Event Manager implements default memory management functions using weak implementation. +You can override this implementation to implement other types of memory allocation. + +The following weak functions are provided by the Application Event Manager as the memory management hooks: + +* :c:func:`app_event_manager_alloc` +* :c:func:`app_event_manager_free` + +For details, refer to :ref:`app_event_manager_api`. + +Shell integration +================= + +Shell integration is available to display additional information and to dynamically enable or disable logging for given event types. + +The Event Manager is integrated with Zephyr's :ref:`shell_api` module. +When the shell is turned on, an additional subcommand set (:command:`app_event_manager`) is added. + +This subcommand set contains the following commands: + +:command:`show_listeners` + Show all registered listeners. + +:command:`show_subscribers` + Show all registered subscribers. + +:command:`show_events` + Show all registered event types. + The letters "E" or "D" indicate if logging is currently enabled or disabled for a given event type. + +:command:`enable` or :command:`disable` + Enable or disable logging. + If called without additional arguments, the command applies to all event types. + To enable or disable logging for specific event types, pass the event type indexes, as displayed by :command:`show_events`, as arguments. + +.. _app_event_manager_api: + +API documentation +***************** + +| Header file: :file:`include/app_event_manager.h` +| Source files: :file:`subsys/app_event_manager/` + +.. doxygengroup:: app_event_manager + :project: nrf + :members: diff --git a/doc/reference/index.rst b/doc/reference/index.rst index d132c578064f7..97d818f85f2a9 100644 --- a/doc/reference/index.rst +++ b/doc/reference/index.rst @@ -8,6 +8,7 @@ API Reference :maxdepth: 2 api/index.rst + app_event_manager/index.rst audio/index.rst misc/notify.rst bluetooth/index.rst diff --git a/include/app_event_manager/app_event_manager.h b/include/app_event_manager/app_event_manager.h new file mode 100644 index 0000000000000..901902148c67b --- /dev/null +++ b/include/app_event_manager/app_event_manager.h @@ -0,0 +1,424 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** @file + * @brief Application Event Manager header. + */ + +#ifndef ZEPHYR_INCLUDE_APP_EVENT_MANAGER_EVENT_MANAGER_H_ +#define ZEPHYR_INCLUDE_APP_EVENT_MANAGER_EVENT_MANAGER_H_ + +/** + * @defgroup app_event_manager Application Event Manager + * @brief Application Event Manager + * + * @{ + */ + +#include +#include +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Pointer to the event handler function. + * + * @param aeh Pointer to the application event header of the event that is + * processed by app_event_manager. + * @retval True if event was consumed and should not be propagated to other listeners, + * false otherwise. + */ +typedef bool (*cb_fn)(const struct app_event_header *aeh); +/** + * @brief List of bits in event type flags. + */ +enum app_event_type_flags { + /* Flags set internally by Application Event Manager. */ + APP_EVENT_TYPE_FLAGS_SYSTEM_START, + APP_EVENT_TYPE_FLAGS_HAS_DYNDATA = APP_EVENT_TYPE_FLAGS_SYSTEM_START, + + /* Flags available for user. */ + APP_EVENT_TYPE_FLAGS_USER_SETTABLE_START, + APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE = + APP_EVENT_TYPE_FLAGS_USER_SETTABLE_START, + + /* Number of predefined flags. */ + APP_EVENT_TYPE_FLAGS_COUNT, + + /* Value greater or equal are user-specific. */ + APP_EVENT_TYPE_FLAGS_USER_DEFINED_START = APP_EVENT_TYPE_FLAGS_COUNT, +}; + +/** @brief Get event type flag's value. + * + * @param flag Selected event type flag. + * @param et Pointer to the event type. + * @retval Boolean value of requested flag. + */ +static inline bool app_event_get_type_flag(const struct event_type *et, + enum app_event_type_flags flag) +{ + return (et->flags & BIT(flag)) != 0; +} + +/** @brief Create an event listener object. + * + * @param lname Listener name. + * @param cb_fn Pointer to the event handler function. + */ +#define APP_EVENT_LISTENER(lname, cb_fn) Z_APP_EVENT_LISTENER(lname, cb_fn) + +/** @brief Subscribe a listener to an event type as first module that is + * being notified. + * + * @param lname Name of the listener. + * @param ename Name of the event. + */ +#define APP_EVENT_SUBSCRIBE_FIRST(lname, ename) \ + Z_APP_EVENT_SUBSCRIBE(lname, ename, Z_APP_EM_MARKER_FIRST_ELEMENT); \ + const struct {} _CONCAT(_CONCAT(__event_subscriber_, ename), first_sub_redefined) = {} + +/** @brief Subscribe a listener to the early notification list for an + * event type. + * + * @param lname Name of the listener. + * @param ename Name of the event. + */ +#define APP_EVENT_SUBSCRIBE_EARLY(lname, ename) \ + Z_APP_EVENT_SUBSCRIBE(lname, ename, Z_APP_EM_SUBS_PRIO_ID(Z_APP_EM_SUBS_PRIO_EARLY)) + +/** @brief Subscribe a listener to the normal notification list for an event + * type. + * + * @param lname Name of the listener. + * @param ename Name of the event. + */ +#define APP_EVENT_SUBSCRIBE(lname, ename) \ + Z_APP_EVENT_SUBSCRIBE(lname, ename, Z_APP_EM_SUBS_PRIO_ID(Z_APP_EM_SUBS_PRIO_NORMAL)) + +/** @brief Subscribe a listener to an event type as final module that is + * being notified. + * + * @param lname Name of the listener. + * @param ename Name of the event. + */ +#define APP_EVENT_SUBSCRIBE_FINAL(lname, ename) \ + Z_APP_EVENT_SUBSCRIBE(lname, ename, Z_APP_EM_MARKER_FINAL_ELEMENT); \ + const struct {} _CONCAT(_CONCAT(__event_subscriber_, ename), final_sub_redefined) = {} + +/** @brief Declare an event type. + * + * This macro provides declarations required for an event to be used + * by other modules. + * + * @param ename Name of the event. + */ +#define APP_EVENT_TYPE_DECLARE(ename) Z_APP_EVENT_TYPE_DECLARE(ename) + +/** @brief Declare an event type with dynamic data size. + * + * This macro provides declarations required for an event to be used + * by other modules. + * Declared event will use dynamic data. + * + * @param ename Name of the event. + */ +#define APP_EVENT_TYPE_DYNDATA_DECLARE(ename) Z_APP_EVENT_TYPE_DYNDATA_DECLARE(ename) + +/** @brief Define an event type. + * + * This macro defines an event type. In addition, it defines functions + * specific to the event type and the event type structure. + * + * For every defined event, the following functions are created, where + * %event_type is replaced with the given event type name @p ename + * (for example, button_event): + * - new_%event_type - Allocates an event of a given type. + * - is_%event_type - Checks if the application event header that is provided + * as argument represents the given event type. + * - cast_%event_type - Casts the application event header that is provided + * as argument to an event of the given type. + * + * @param ename Name of the event. + * @param log_fn Function to stringify an event of this type. + * @param ev_info_struct Data structure describing the event type. + * @param app_event_type_flags Event type flags. + * You should use APP_EVENT_FLAGS_CREATE to define them. + */ +#define APP_EVENT_TYPE_DEFINE(ename, log_fn, ev_info_struct, app_event_type_flags) \ + Z_APP_EVENT_TYPE_DEFINE(ename, log_fn, ev_info_struct, app_event_type_flags) + +/** @brief Verify if an event ID is valid. + * + * The pointer to an event type structure is used as its ID. This macro + * validates that the provided pointer is within the range where event + * type structures are defined. + * + * @param id ID. + */ +#define APP_EVENT_ASSERT_ID(id) \ + __ASSERT_NO_MSG((id >= _event_type_list_start) && (id < _event_type_list_end)) + +/** @brief Submit an event. + * + * This helper macro simplifies the event submission. + * + * @param event Pointer to the event object. + */ +#define APP_EVENT_SUBMIT(event) _event_submit(&event->header) + +/** + * @brief Register event hook after the Application Event Manager is initialized. + * + * The event hook called after the app_event_manager is initialized to provide some additional + * initialization of the modules that depends on it. + * The hook function should have a form `int hook(void)`. + * If the initialization hook returns non-zero value the initialization process is interrupted. + * + * @param hook_fn Hook function. + */ +#define APP_EVENT_MANAGER_HOOK_POSTINIT_REGISTER(hook_fn) \ + Z_APP_EVENT_MANAGER_HOOK_POSTINIT_REGISTER(hook_fn, \ + Z_APP_EM_SUBS_PRIO_ID(Z_APP_EM_SUBS_PRIO_NORMAL)) + +/** + * @brief Get the event size + * + * Function that calculates the event size using its header. + * @note + * For this function to be available the + * @kconfig{CONFIG_APP_EVENT_MANAGER_PROVIDE_EVENT_SIZE} option needs to be enabled. + * + * @param aeh Pointer to the application event header. + * + * @return Event size in bytes. + */ +static inline size_t app_event_manager_event_size(const struct app_event_header *aeh) +{ +#if IS_ENABLED(CONFIG_APP_EVENT_MANAGER_PROVIDE_EVENT_SIZE) + size_t size = aeh->type_id->struct_size; + + if (app_event_get_type_flag(aeh->type_id, APP_EVENT_TYPE_FLAGS_HAS_DYNDATA)) { + size += ((const struct event_dyndata *) + (((const uint8_t *)aeh) + size - sizeof(struct event_dyndata)))->size; + } + return size; +#else + __ASSERT_NO_MSG(false); + return 0; +#endif +} + + +/** + * @brief Register hook called on event submission. The hook would be called first. + * + * The event hook called when the event is submitted. + * The hook function should have a form `void hook(const struct app_event_header *aeh)`. + * The macro makes sure that the hook provided here is called first. + * Only one hook can be registered with this macro. + * + * @note + * The registered hook may be called from many contexts. + * To ensure that order of events in the queue matches the order of the registered callbacks calls, + * the callbacks are called under the same spinlock as adding events to the queue. + * + * @param hook_fn Hook function. + */ +#define APP_EVENT_HOOK_ON_SUBMIT_REGISTER_FIRST(hook_fn) \ + const struct {} __event_hook_on_submit_first_sub_redefined = {}; \ + Z_APP_EVENT_HOOK_ON_SUBMIT_REGISTER(hook_fn, Z_APP_EM_MARKER_FIRST_ELEMENT) + +/** + * @brief Register event hook on submission. + * + * The event hook called when the event is submitted. + * The hook function should have a form `void hook(const struct app_event_header *aeh)`. + * + * @note + * The registered hook may be called from many contexts. + * To ensure that order of events in the queue matches the order of the registered callbacks calls, + * the callbacks are called under the same spinlock as adding events to the queue. + * + * @param hook_fn Hook function. + */ +#define APP_EVENT_HOOK_ON_SUBMIT_REGISTER(hook_fn) \ + Z_APP_EVENT_HOOK_ON_SUBMIT_REGISTER(hook_fn, Z_APP_EM_SUBS_PRIO_ID(Z_APP_EM_SUBS_PRIO_NORMAL)) + +/** + * @brief Register event hook on submission. The hook would be called last. + * + * The event hook called when the event is submitted. + * The hook function should have a form `void hook(const struct app_event_header *aeh)`. + * The macro makes sure that the hook provided here is called last. + * Only one hook can be registered with this macro. + * + * @note + * The registered hook may be called from many contexts. + * To ensure that order of events in the queue matches the order of the registered callbacks calls, + * the callbacks are called under the same spinlock as adding events to the queue. + * + * @param hook_fn Hook function. + */ +#define APP_EVENT_HOOK_ON_SUBMIT_REGISTER_LAST(hook_fn) \ + const struct {} __event_hook_on_submit_last_sub_redefined = {}; \ + Z_APP_EVENT_HOOK_ON_SUBMIT_REGISTER(hook_fn, Z_APP_EM_MARKER_FINAL_ELEMENT) + +/** + * @brief Register event hook on the start of event processing. The hook would be called first. + * + * The hook function should have a form `void hook(const struct app_event_header *aeh)`. + * The macro makes sure that the hook provided here is called first. + * Only one hook can be registered with this macro. + * + * @param hook_fn Hook function. + */ +#define APP_EVENT_HOOK_PREPROCESS_REGISTER_FIRST(hook_fn) \ + const struct {} __event_hook_preprocess_first_sub_redefined = {}; \ + Z_APP_EVENT_HOOK_PREPROCESS_REGISTER(hook_fn, Z_APP_EM_MARKER_FIRST_ELEMENT) + +/** + * @brief Register event hook on the start of event processing. + * + * The event hook called when the event is being processed. + * The hook function should have a form `void hook(const struct app_event_header *aeh)`. + * + * @param hook_fn Hook function. + */ +#define APP_EVENT_HOOK_PREPROCESS_REGISTER(hook_fn) \ + Z_APP_EVENT_HOOK_PREPROCESS_REGISTER(hook_fn, Z_APP_EM_SUBS_PRIO_ID(Z_APP_EM_SUBS_PRIO_NORMAL)) + +/** + * @brief Register event hook on the start of event processing. The hook would be called last. + * + * The event hook called when the event is being processed. + * The hook function should have a form `void hook(const struct app_event_header *aeh)`. + * The macro makes sure that the hook provided here is called last. + * Only one hook can be registered with this macro. + * + * @param hook_fn Hook function. + */ +#define APP_EVENT_HOOK_PREPROCESS_REGISTER_LAST(hook_fn) \ + const struct {} __event_hook_preprocess_last_sub_redefined = {}; \ + Z_APP_EVENT_HOOK_PREPROCESS_REGISTER(hook_fn, Z_APP_EM_MARKER_FINAL_ELEMENT) + +/** + * @brief Register event hook on the end of event processing. The hook would be called first. + * + * The event hook called after the event is processed. + * The hook function should have a form `void hook(const struct app_event_header *aeh)`. + * The macro makes sure that the hook provided here is called first. + * Only one hook can be registered with this macro. + * + * @param hook_fn Hook function. + */ +#define APP_EVENT_HOOK_POSTPROCESS_REGISTER_FIRST(hook_fn) \ + const struct {} __event_hook_postprocess_first_sub_redefined = {}; \ + Z_APP_EVENT_HOOK_POSTPROCESS_REGISTER(hook_fn, Z_APP_EM_MARKER_FIRST_ELEMENT) + +/** + * @brief Register event hook on the end of event processing. + * + * The event hook called after the event is processed. + * The hook function should have a form `void hook(const struct app_event_header *aeh)`. + * + * @param hook_fn Hook function. + */ +#define APP_EVENT_HOOK_POSTPROCESS_REGISTER(hook_fn) \ + Z_APP_EVENT_HOOK_POSTPROCESS_REGISTER(hook_fn, \ + Z_APP_EM_SUBS_PRIO_ID(Z_APP_EM_SUBS_PRIO_NORMAL)) + +/** + * @brief Register event hook on the end of event processing. The hook would be called last. + * + * The event hook called after the event is processed. + * The hook function should have a form `void hook(const struct app_event_header *aeh)`. + * The macro makes sure that the hook provided here is called last. + * Only one hook can be registered with this macro. + * + * @param hook_fn Hook function. + */ +#define APP_EVENT_HOOK_POSTPROCESS_REGISTER_LAST(hook_fn) \ + const struct {} __event_hook_postprocess_last_sub_redefined = {}; \ + Z_APP_EVENT_HOOK_POSTPROCESS_REGISTER(hook_fn, Z_APP_EM_MARKER_FINAL_ELEMENT) + + +/** @brief Initialize the Application Event Manager. + * + * @retval 0 If the operation was successful. Error values can be added by the hooks registered + * by @ref APP_EVENT_MANAGER_HOOK_POSTINIT_REGISTER macro. + */ +int app_event_manager_init(void); + +/** @brief Allocate event. + * + * The behavior of this function depends on the actual implementation. + * The default implementation of this function is same as k_malloc. + * It is annotated as weak and can be overridden by user. + * + * @param size Amount of memory requested (in bytes). + * @retval Address of the allocated memory if successful, otherwise NULL. + **/ +void *app_event_manager_alloc(size_t size); + + +/** @brief Free memory occupied by the event. + * + * The behavior of this function depends on the actual implementation. + * The default implementation of this function is same as k_free. + * It is annotated as weak and can be overridden by user. + * + * @param addr Pointer to previously allocated memory. + **/ +void app_event_manager_free(void *addr); + +/** @brief Log event. + * + * This helper macro simplifies event logging. + * + * @param aeh Pointer to the application event header of the event that is + * processed by app_event_manager. + * @param ... `printf`- like format string and variadic list of arguments corresponding to + * the format string. + */ +#define APP_EVENT_MANAGER_LOG(aeh, ...) do { \ + LOG_MODULE_DECLARE(app_event_manager, CONFIG_APP_EVENT_MANAGER_LOG_LEVEL); \ + if (IS_ENABLED(CONFIG_APP_EVENT_MANAGER_LOG_EVENT_TYPE)) { \ + LOG_INF("e:%s " GET_ARG_N(1, __VA_ARGS__), aeh->type_id->name \ + COND_CODE_0(NUM_VA_ARGS_LESS_1(__VA_ARGS__), \ + (), \ + (, GET_ARGS_LESS_N(1, __VA_ARGS__)) \ + )); \ + } else { \ + LOG_INF(__VA_ARGS__); \ + } \ +} while (0) + + +/** + * @brief Define flags for event type. + * + * @param ... Comma-separated list of flags which should be set. + * In case no flags should be set leave it empty. + */ + #define APP_EVENT_FLAGS_CREATE(...) \ + (FOR_EACH_NONEMPTY_TERM(Z_APP_EVENT_FLAGS_JOIN, (|), __VA_ARGS__) 0) + + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_APP_EVENT_MANAGER_EVENT_MANAGER_H_ */ diff --git a/include/app_event_manager/app_event_manager_priv.h b/include/app_event_manager/app_event_manager_priv.h new file mode 100644 index 0000000000000..2b7cf5065be08 --- /dev/null +++ b/include/app_event_manager/app_event_manager_priv.h @@ -0,0 +1,421 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Application Event Manager private header. + * + * Although these defines are globally visible they must not be used directly. + */ + +#ifndef ZEPHYR_INCLUDE_APP_EVENT_MANAGER_EVENT_MANAGER_PRIV_H_ +#define ZEPHYR_INCLUDE_APP_EVENT_MANAGER_EVENT_MANAGER_PRIV_H_ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Determine if _Generic is supported. + * In general it's a C11 feature but it was added also in: + * - GCC 4.9.0 https://gcc.gnu.org/gcc-4.9/changes.html + * - Clang 3.0 https://releases.llvm.org/3.0/docs/ClangReleaseNotes.html + * + * @note Z_C_GENERIC is also set for C++ where functionality is implemented + * using overloading and templates. + */ +#ifndef Z_C_GENERIC +#if defined(__cplusplus) || (((__STDC_VERSION__ >= 201112L) || \ + ((__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) >= 40900) || \ + ((__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) >= 30000))) +#define Z_C_GENERIC 1 +#else +#define Z_C_GENERIC 0 +#endif +#endif + +/* Macros related to sorting of elements in the event subscribers section. */ + +/* Markers used for ordering elements in subscribers array for each event type. + * To ensure ordering markers should go in alphabetical order. + */ + +#define Z_APP_EM_MARKER_ARRAY_START _a +#define Z_APP_EM_MARKER_FIRST_ELEMENT _b +#define Z_APP_EM_MARKER_PRIO_ELEMENTS _p +#define Z_APP_EM_MARKER_FINAL_ELEMENT _y +#define Z_APP_EM_MARKER_ARRAY_END _z + +/* Macro expanding ordering level into string. + * The level must be between 00 and 99. Leading zero is required to ensure + * proper sorting. + */ +#define Z_APP_EM_SUBS_PRIO_ID(level) _CONCAT(_CONCAT(Z_APP_EM_MARKER_PRIO_ELEMENTS, level), _) + +/* There are 2 default ordering levels of event subscribers. */ +#define Z_APP_EM_SUBS_PRIO_EARLY 05 +#define Z_APP_EM_SUBS_PRIO_NORMAL 10 + +/* Convenience macros generating section names. */ + +#define Z_APP_EVENT_SUBSCRIBERS_SECTION_PREFIX(ename, marker) \ + _CONCAT(_CONCAT(event_subscribers_, ename), marker) + +#define Z_APP_EVENT_SUBSCRIBERS_SECTION_NAME(ename, marker) \ + STRINGIFY(Z_APP_EVENT_SUBSCRIBERS_SECTION_PREFIX(ename, marker)) + + +/* Macros related to subscriber array tags + * Each tag is a zero-length element that which is placed by linker at the start + * and the end of the subscriber array respectively. + */ + +/* Convenience macro generating tag name. */ +#define Z_APP_EM_TAG_NAME(prefix) _CONCAT(prefix, _tag) + +/* Zero-length subscriber to be used as a tag. */ +#define Z_APP_EVENT_SUBSCRIBERS_TAG(ename, marker) \ + const struct {} Z_APP_EM_TAG_NAME(Z_APP_EVENT_SUBSCRIBERS_SECTION_PREFIX \ + (ename, marker)) \ + __used __aligned(__alignof(struct event_subscriber)) \ + __attribute__((__section__(Z_APP_EVENT_SUBSCRIBERS_SECTION_NAME \ + (ename, marker)))) = {}; + +/* Macro defining subscriber array boundary tags. */ +#define Z_APP_EVENT_SUBSCRIBERS_ARRAY_TAGS(ename) \ + Z_APP_EVENT_SUBSCRIBERS_TAG(ename, Z_APP_EM_MARKER_ARRAY_START) \ + Z_APP_EVENT_SUBSCRIBERS_TAG(ename, Z_APP_EM_MARKER_ARRAY_END) + +/* Pointer to the first element of subscriber array for a given event type. */ +#define Z_APP_EVENT_SUBSCRIBERS_START_TAG(ename) \ + ((const struct event_subscriber *) \ + &Z_APP_EM_TAG_NAME(Z_APP_EVENT_SUBSCRIBERS_SECTION_PREFIX \ + (ename, Z_APP_EM_MARKER_ARRAY_START)) \ + ) + +/* Pointer to the element past the last element of subscriber array for a given event type. */ +#define Z_APP_EVENT_SUBSCRIBERS_END_TAG(ename) \ + ((const struct event_subscriber *) &Z_APP_EM_TAG_NAME( \ + Z_APP_EVENT_SUBSCRIBERS_SECTION_PREFIX(ename, Z_APP_EM_MARKER_ARRAY_END))\ + ) + +/* Subscribe a listener to an event. */ +#define Z_APP_EVENT_SUBSCRIBE(lname, ename, prio) \ + const struct event_subscriber _CONCAT(_CONCAT(__event_subscriber_, ename), lname)\ + __used __aligned(__alignof(struct event_subscriber)) \ + __attribute__((__section__(Z_APP_EVENT_SUBSCRIBERS_SECTION_NAME(ename, prio)))) = {\ + .listener = &_CONCAT(__event_listener_, lname), \ + } + +/* Pointer to event type definition is used as event type identifier. */ +#define Z_APP_EVENT_ID(ename) (&_CONCAT(__event_type_, ename)) + +/* Macro generates a function of name new_ename where ename is provided as + * an argument. Allocator function is used to create an event of the given + * ename type. + */ +#define Z_APP_EVENT_ALLOCATOR_FN(ename) \ + static inline struct ename *_CONCAT(new_, ename)(void) \ + { \ + struct ename *event = \ + (struct ename *)app_event_manager_alloc(sizeof(*event));\ + BUILD_ASSERT(offsetof(struct ename, header) == 0, \ + ""); \ + if (event != NULL) { \ + event->header.type_id = Z_APP_EVENT_ID(ename); \ + } \ + return event; \ + } + +/* Macro generates a function of name new_ename where ename is provided as + * an argument. Allocator function is used to create an event of the given + * ename type. + */ +#define Z_APP_EVENT_ALLOCATOR_DYNDATA_FN(ename) \ + static inline struct ename *_CONCAT(new_, ename)(size_t size) \ + { \ + struct ename *event = \ + (struct ename *)app_event_manager_alloc(sizeof(*event) + size); \ + BUILD_ASSERT((offsetof(struct ename, dyndata) + \ + sizeof(event->dyndata.size)) == \ + sizeof(*event), ""); \ + BUILD_ASSERT(offsetof(struct ename, header) == 0, \ + ""); \ + if (event != NULL) { \ + event->header.type_id = Z_APP_EVENT_ID(ename); \ + event->dyndata.size = size; \ + } \ + return event; \ + } + +/* Macro generates a function of name cast_ename where ename is provided as + * an argument. Casting function is used to convert app_event_header pointer + * into pointer to event matching the given ename type. + */ +#define Z_APP_EVENT_CASTER_FN(ename) \ + static inline struct ename *_CONCAT(cast_, ename)(const struct app_event_header *aeh) \ + { \ + struct ename *event = NULL; \ + if (aeh->type_id == Z_APP_EVENT_ID(ename)) { \ + event = CONTAINER_OF(aeh, struct ename, header); \ + } \ + return event; \ + } + + +/* Macro generates a function of name is_ename where ename is provided as + * an argument. Typecheck function is used to check if pointer to app_event_header + * belongs to the event matching the given ename type. + */ +#define Z_APP_EVENT_TYPECHECK_FN(ename) \ + static inline bool _CONCAT(is_, ename)(const struct app_event_header *aeh)\ + { \ + return (aeh->type_id == Z_APP_EVENT_ID(ename)); \ + } + +/* Declarations and definitions - for more details refer to public API. */ +#define Z_APP_EVENT_LISTENER(lname, notification_fn) \ + STRUCT_SECTION_ITERABLE(event_listener, _CONCAT(__event_listener_, lname)) = { \ + .name = STRINGIFY(lname), \ + .notification = (notification_fn), \ + } + +#define Z_APP_EVENT_TYPE_DECLARE_COMMON(ename) \ + extern Z_DECL_ALIGN(struct event_type) _CONCAT(__event_type_, ename); \ + Z_APP_EVENT_CASTER_FN(ename); \ + Z_APP_EVENT_TYPECHECK_FN(ename) + + +#define Z_APP_EVENT_TYPE_DECLARE(ename) \ + enum {_CONCAT(ename, Z_APP_EM_HAS_DYNDATA) = 0}; \ + Z_APP_EVENT_TYPE_DECLARE_COMMON(ename); \ + Z_APP_EVENT_ALLOCATOR_FN(ename) + +#define Z_APP_EVENT_TYPE_DYNDATA_DECLARE(ename) \ + enum {_CONCAT(ename, Z_APP_EM_HAS_DYNDATA) = 1}; \ + Z_APP_EVENT_TYPE_DECLARE_COMMON(ename); \ + Z_APP_EVENT_ALLOCATOR_DYNDATA_FN(ename) + +#if IS_ENABLED(CONFIG_APP_EVENT_MANAGER_PROVIDE_EVENT_SIZE) +#define Z_APP_EVENT_TYPE_DEFINE_SIZES(ename) \ + .struct_size = sizeof(struct ename), +#else +#define Z_APP_EVENT_TYPE_DEFINE_SIZES(ename) +#endif + +/** @brief Event header. + * + * When defining an event structure, the application event header + * must be placed as the first field. + */ +struct app_event_header { + /** Linked list node used to chain events. */ + sys_snode_t node; + + /** Pointer to the event type object. */ + const struct event_type *type_id; +}; + +/** Function to log data from this event. */ +typedef void (*log_event_data)(const struct app_event_header *aeh); + +/** Deprecated function to log data from this event. */ +typedef int (*log_event_data_dep)(const struct app_event_header *aeh, char *buf, size_t buf_len); + +#if IS_ENABLED(CONFIG_APP_EVENT_MANAGER_USE_DEPRECATED_LOG_FUN) +#define Z_APP_EVENT_TYPE_DEFINE_LOG_FUN(log_fn) \ + .log_event_func_dep = ((IS_ENABLED(CONFIG_LOG) && Z_C_GENERIC) ? \ + ((log_event_data_dep)_Generic((log_fn), \ + log_event_data : NULL, \ + log_event_data_dep : log_fn, \ + default : NULL)) \ + : (NULL)), \ + .log_event_func = (log_event_data) (IS_ENABLED(CONFIG_LOG) ? \ + (Z_C_GENERIC ? \ + (_Generic((log_fn), \ + log_event_data : log_fn, \ + log_event_data_dep : NULL, \ + default : NULL)) \ + : (log_fn)) \ + : (NULL)), +#else +#define Z_APP_EVENT_TYPE_DEFINE_LOG_FUN(log_fun) .log_event_func = log_fun, +#endif + +/** @brief Event type. + */ + +struct event_type { + /** Event name. */ + const char *name; + + /** Pointer to the array of subscribers. */ + const struct event_subscriber *subs_start; + + /** Pointer to the element directly after the array of subscribers. */ + const struct event_subscriber *subs_stop; + + /** Function to log data from this event. */ + log_event_data log_event_func; + +#if IS_ENABLED(CONFIG_APP_EVENT_MANAGER_USE_DEPRECATED_LOG_FUN) + /** Deprecated function to log data from this event. */ + log_event_data_dep log_event_func_dep; +#endif + + /** Custom data related to tracking. */ + const void *trace_data; + + /** Array of flags dedicated to event type. */ + const uint8_t flags; + +#if IS_ENABLED(CONFIG_APP_EVENT_MANAGER_PROVIDE_EVENT_SIZE) + /** The size of the event structure */ + uint16_t struct_size; +#endif +}; + +extern struct event_type _event_type_list_start[]; +extern struct event_type _event_type_list_end[]; + +#define Z_APP_EVENT_TYPE_DEFINE(ename, log_fn, trace_data_pointer, et_flags) \ + BUILD_ASSERT(((et_flags) & ((BIT_MASK(APP_EVENT_TYPE_FLAGS_USER_SETTABLE_START- \ + APP_EVENT_TYPE_FLAGS_SYSTEM_START))<< \ + APP_EVENT_TYPE_FLAGS_SYSTEM_START)) == 0); \ + Z_APP_EVENT_SUBSCRIBERS_ARRAY_TAGS(ename); \ + STRUCT_SECTION_ITERABLE(event_type, _CONCAT(__event_type_, ename)) = { \ + .name = STRINGIFY(ename), \ + .subs_start = Z_APP_EVENT_SUBSCRIBERS_START_TAG(ename), \ + .subs_stop = Z_APP_EVENT_SUBSCRIBERS_END_TAG(ename), \ + Z_APP_EVENT_TYPE_DEFINE_LOG_FUN(log_fn) /* No comma here intentionally */\ + .trace_data = (IS_ENABLED(CONFIG_APP_EVENT_MANAGER_TRACE_EVENT_DATA) ?\ + (trace_data_pointer) : (NULL)), \ + .flags = ((_CONCAT(ename, Z_APP_EM_HAS_DYNDATA)) ? \ + ((et_flags) | BIT(APP_EVENT_TYPE_FLAGS_HAS_DYNDATA)) : \ + ((et_flags) & (~BIT(APP_EVENT_TYPE_FLAGS_HAS_DYNDATA)))),\ + Z_APP_EVENT_TYPE_DEFINE_SIZES(ename) /* No comma here intentionally */ \ + } + +/** + * @brief Bitmask indicating event is displayed. + */ +struct app_event_manager_event_display_bm { + ATOMIC_DEFINE(flags, CONFIG_APP_EVENT_MANAGER_MAX_EVENT_CNT); +}; + +extern struct app_event_manager_event_display_bm _app_event_manager_event_display_bm; + + +/* Event hooks subscribers */ +#define Z_APP_EVENT_HOOK_REGISTER(section, hook_fn, prio) \ + BUILD_ASSERT((hook_fn) != NULL, "Registered hook cannot be NULL"); \ + STRUCT_SECTION_ITERABLE(section, _CONCAT(prio, hook_fn)) = { \ + .hook = (hook_fn) \ + } + +#define Z_APP_EVENT_MANAGER_HOOK_POSTINIT_REGISTER(hook_fn, prio) \ + BUILD_ASSERT(IS_ENABLED(CONFIG_APP_EVENT_MANAGER_POSTINIT_HOOK), \ + "Enable APP_EVENT_MANAGER_POSTINIT_HOOK before usage"); \ + Z_APP_EVENT_HOOK_REGISTER(app_event_manager_postinit_hook, hook_fn, prio) + +#define Z_APP_EVENT_HOOK_ON_SUBMIT_REGISTER(hook_fn, prio) \ + BUILD_ASSERT(IS_ENABLED(CONFIG_APP_EVENT_MANAGER_SUBMIT_HOOKS), \ + "Enable APP_EVENT_MANAGER_SUBMIT_HOOKS before usage"); \ + Z_APP_EVENT_HOOK_REGISTER(event_submit_hook, hook_fn, prio) + +#define Z_APP_EVENT_HOOK_PREPROCESS_REGISTER(hook_fn, prio) \ + BUILD_ASSERT(IS_ENABLED(CONFIG_APP_EVENT_MANAGER_PREPROCESS_HOOKS), \ + "Enable APP_EVENT_MANAGER_PREPROCESS_HOOKS before usage"); \ + Z_APP_EVENT_HOOK_REGISTER(event_preprocess_hook, hook_fn, prio) + +#define Z_APP_EVENT_HOOK_POSTPROCESS_REGISTER(hook_fn, prio) \ + BUILD_ASSERT(IS_ENABLED(CONFIG_APP_EVENT_MANAGER_POSTPROCESS_HOOKS), \ + "Enable APP_EVENT_MANAGER_POSTPROCESS_HOOKS before usage"); \ + Z_APP_EVENT_HOOK_REGISTER(event_postprocess_hook, hook_fn, prio) + +/** + * @brief Joining together event type flags. + */ +#define Z_APP_EVENT_FLAGS_JOIN(flag) BIT(flag) + + +/** @brief Dynamic event data. + * + * When defining an event structure, the dynamic event data + * must be placed as the last field. + */ +struct event_dyndata { + /** Size of the dynamic data. */ + size_t size; + + /** Dynamic data. */ + uint8_t data[0]; +}; + +/** @brief Event listener. + * + * All event listeners must be defined using @ref APP_EVENT_LISTENER. + */ +struct event_listener { + /** Name of this listener. */ + const char *name; + + /** Pointer to the function that is called when an event is handled. + * The function should return true to consume the event, which means that the event is + * not propagated to further listeners, or false, otherwise. + */ + bool (*notification)(const struct app_event_header *aeh); +}; + +/** @brief Event subscriber. + */ +struct event_subscriber { + /** Pointer to the listener. */ + const struct event_listener *listener; +}; + + +/** @brief Structure used to register Application Event Manager initialization hook + */ +struct app_event_manager_postinit_hook { + /** @brief Hook function */ + int (*hook)(void); +}; +/** @brief Structure used to register event submit hook + */ +struct event_submit_hook { + /** @brief Hook function */ + void (*hook)(const struct app_event_header *aeh); +}; + +/** @brief Structure used to register event preprocess hook + */ +struct event_preprocess_hook { + /** @brief Hook function */ + void (*hook)(const struct app_event_header *aeh); +}; + +/** @brief Structure used to register event postprocess hook + */ +struct event_postprocess_hook { + /** @brief Hook function */ + void (*hook)(const struct app_event_header *aeh); +}; + + +/** @brief Submit an event to the Application Event Manager. + * + * @param aeh Pointer to the application event header element in the event object. + */ +void _event_submit(struct app_event_header *aeh); + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_APP_EVENT_MANAGER_EVENT_MANAGER_PRIV_H_ */ diff --git a/samples/subsys/app_event_manager/CMakeLists.txt b/samples/subsys/app_event_manager/CMakeLists.txt new file mode 100644 index 0000000000000..6aa0088c206ad --- /dev/null +++ b/samples/subsys/app_event_manager/CMakeLists.txt @@ -0,0 +1,31 @@ +# +# Copyright (c) 2021 Nordic Semiconductor +# +# SPDX-License-Identifier: Apache-2.0 +# + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project("Application Event Manager sample") + +# Include application application event headers +zephyr_library_include_directories(src/events) + +# Application sources +# NORDIC SDK APP START +target_sources(app PRIVATE src/main.c) + +target_sources(app PRIVATE + src/events/ack_event.c + src/events/config_event.c + src/events/control_event.c + src/events/measurement_event.c +) + +target_sources(app PRIVATE + src/modules/controller.c + src/modules/sensor_simulated.c + src/modules/stats.c +) +# NORDIC SDK APP END diff --git a/samples/subsys/app_event_manager/README.rst b/samples/subsys/app_event_manager/README.rst new file mode 100644 index 0000000000000..faa1b48327666 --- /dev/null +++ b/samples/subsys/app_event_manager/README.rst @@ -0,0 +1,71 @@ +.. _app_event_manager_sample: + +Application Event Manager +######################### + +.. contents:: + :local: + :depth: 2 + +The Application Event Manager sample demonstrates the functionality of the :ref:`app_event_manager` subsystem. +It uses an event-driven architecture, where different modules communicate through sending and processing events. + + +Overview +******** + +The sample application consists of three modules that communicate using events: + +Sensor (``sensor_simulated.c``): + This module waits for a configuration event (which is sent by ``main.c``). + After receiving this event, it simulates measured data at constant intervals. + Every time the data is updated, the module sends the current values as measurement event. + When the module receives a control event from the Controller, it responds with an ACK event. + +Controller (``controller.c``): + This module waits for measurement events from the sensor. + Every time a measurement event is received, the module checks one of the measurement values that are transmitted as part of the event and, if the value exceeds a static threshold, sends a control event. + +Statistics (``stats.c``): + This module waits for measurement events from the sensor. + The module calculates and logs basic statistics about one of the measurement values that are transmitted as part of the event. + +Building and testing +******************** +.. |sample path| replace:: :file:`samples/app_event_manager` + +1.Using development board: + Build and flash Event Manager as follows, changing nrf52840dk_nrf52840 for your board: + west build -b nrf52840dk_nrf52840 samples/subsys/event_manager + west flash + Then connect to the kit with a terminal emulator (for example, PuTTY). +#. Using qemu + If you use qemu platform changing qemu_leon3 for your board: + west build -b quemu_leon3 samples/subsys/event_manager + west build -t run +#. Observe that output similar to the following is logged on UART in case of development kit and in terminal in case of qemu:: + + ***** Booting Zephyr OS v1.13.99-ncs1-4741-g1d6219f ***** + [00:00:00.000,854] app_event_manager: e: config_event init_val_1=3 + [00:00:00.001,068] app_event_manager: e: measurement_event val1=3 val2=3 val3=3 + [00:00:00.509,063] app_event_manager: e: measurement_event val1=3 val2=6 val3=9 + [00:00:01.018,005] app_event_manager: e: measurement_event val1=3 val2=9 val3=18 + [00:00:01.526,947] app_event_manager: e: measurement_event val1=3 val2=12 val3=30 + [00:00:02.035,888] app_event_manager: e: measurement_event val1=3 val2=15 val3=45 + [00:00:02.035,949] app_event_manager: e: control_event + [00:00:02.035,980] app_event_manager: e: ack_event + [00:00:02.544,830] app_event_manager: e: measurement_event val1=-3 val2=12 val3=57 + [00:00:03.053,771] app_event_manager: e: measurement_event val1=-3 val2=9 val3=66 + [00:00:03.562,713] app_event_manager: e: measurement_event val1=-3 val2=6 val3=72 + [00:00:04.071,655] app_event_manager: e: measurement_event val1=-3 val2=3 val3=75 + [00:00:04.580,596] app_event_manager: e: measurement_event val1=-3 val2=0 val3=75 + [00:00:04.580,596] stats: Average value3: 45 + + +Dependencies +************ + +This sample uses the following Zephyr subsystems: + +* :ref:`app_event_manager` +* :ref:`logging_api` diff --git a/samples/subsys/app_event_manager/prj.conf b/samples/subsys/app_event_manager/prj.conf new file mode 100644 index 0000000000000..b5f056174bd28 --- /dev/null +++ b/samples/subsys/app_event_manager/prj.conf @@ -0,0 +1,15 @@ +# +# Copyright (c) 2021 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# +# Enabling assert +CONFIG_ASSERT=y + +# Logger configuration +CONFIG_LOG=y +CONFIG_LOG_DEFAULT_LEVEL=2 + +# Configuration required by Application Event Manager +CONFIG_APP_EVENT_MANAGER=y +CONFIG_HEAP_MEM_POOL_SIZE=2048 diff --git a/samples/subsys/app_event_manager/sample.yaml b/samples/subsys/app_event_manager/sample.yaml new file mode 100644 index 0000000000000..dbdad7874ff06 --- /dev/null +++ b/samples/subsys/app_event_manager/sample.yaml @@ -0,0 +1,23 @@ +sample: + description: Sample showing Application Event Manager usage + name: Application Event Manager sample +common: + harness: console + integration_platforms: + - qemu_cortex_m3 + - nrf52dk_nrf52832 + - nrf52840dk_nrf52840 + - nrf9160dk_nrf9160_ns + - nrf21540dk_nrf52840 + harness_config: + type: multi_line + ordered: false + regex: + - "config_event" + - "measurement_event" + - "control_event" + - "ack_event" + - "Average value3: 45" +tests: + samples.app_event_manager: + build_only: false diff --git a/samples/subsys/app_event_manager/src/events/ack_event.c b/samples/subsys/app_event_manager/src/events/ack_event.c new file mode 100644 index 0000000000000..11db6f1e808d7 --- /dev/null +++ b/samples/subsys/app_event_manager/src/events/ack_event.c @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "ack_event.h" + + +APP_EVENT_TYPE_DEFINE(ack_event, + NULL, + NULL, + APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE)); diff --git a/samples/subsys/app_event_manager/src/events/ack_event.h b/samples/subsys/app_event_manager/src/events/ack_event.h new file mode 100644 index 0000000000000..6b0a9eb02f9d6 --- /dev/null +++ b/samples/subsys/app_event_manager/src/events/ack_event.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _ACK_EVENT_H_ +#define _ACK_EVENT_H_ + +/** + * @brief ACK Event + * @defgroup ack_event ACK Event + * @{ + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct ack_event { + struct app_event_header header; +}; + +APP_EVENT_TYPE_DECLARE(ack_event); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* _ACK_EVENT_H_ */ diff --git a/samples/subsys/app_event_manager/src/events/config_event.c b/samples/subsys/app_event_manager/src/events/config_event.c new file mode 100644 index 0000000000000..c6cba855116d8 --- /dev/null +++ b/samples/subsys/app_event_manager/src/events/config_event.c @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "config_event.h" + + +static void log_config_event(const struct app_event_header *aeh) +{ + struct config_event *event = cast_config_event(aeh); + + APP_EVENT_MANAGER_LOG(aeh, "init_val_1=%d", event->init_value1); +} + +APP_EVENT_TYPE_DEFINE(config_event, + log_config_event, + NULL, + APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE)); diff --git a/samples/subsys/app_event_manager/src/events/config_event.h b/samples/subsys/app_event_manager/src/events/config_event.h new file mode 100644 index 0000000000000..6c7de431d3a44 --- /dev/null +++ b/samples/subsys/app_event_manager/src/events/config_event.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _CONFIG_EVENT_H_ +#define _CONFIG_EVENT_H_ + +/** + * @brief Config Event + * @defgroup config_event Config Event + * @{ + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct config_event { + struct app_event_header header; + + int8_t init_value1; +}; + +APP_EVENT_TYPE_DECLARE(config_event); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* _CONFIG_EVENT_H_ */ diff --git a/samples/subsys/app_event_manager/src/events/control_event.c b/samples/subsys/app_event_manager/src/events/control_event.c new file mode 100644 index 0000000000000..04d6959d36e92 --- /dev/null +++ b/samples/subsys/app_event_manager/src/events/control_event.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "control_event.h" + + +APP_EVENT_TYPE_DEFINE(control_event, + NULL, + NULL, + APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE)); diff --git a/samples/subsys/app_event_manager/src/events/control_event.h b/samples/subsys/app_event_manager/src/events/control_event.h new file mode 100644 index 0000000000000..131f3c3c68494 --- /dev/null +++ b/samples/subsys/app_event_manager/src/events/control_event.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _CONTROL_EVENT_H_ +#define _CONTROL_EVENT_H_ + +/** + * @brief Control Event + * @defgroup control_event Control Event + * @{ + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct control_event { + struct app_event_header header; +}; + +APP_EVENT_TYPE_DECLARE(control_event); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* _CONTROL_EVENT_H_ */ diff --git a/samples/subsys/app_event_manager/src/events/measurement_event.c b/samples/subsys/app_event_manager/src/events/measurement_event.c new file mode 100644 index 0000000000000..ff573536eeadc --- /dev/null +++ b/samples/subsys/app_event_manager/src/events/measurement_event.c @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "measurement_event.h" + +static void log_measurement_event(const struct app_event_header *aeh) +{ + struct measurement_event *event = cast_measurement_event(aeh); + + APP_EVENT_MANAGER_LOG(aeh, "val1=%d val2=%d val3=%d", event->value1, + event->value2, event->value3); +} + + +APP_EVENT_TYPE_DEFINE(measurement_event, + log_measurement_event, + NULL, + APP_EVENT_FLAGS_CREATE(APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE)); diff --git a/samples/subsys/app_event_manager/src/events/measurement_event.h b/samples/subsys/app_event_manager/src/events/measurement_event.h new file mode 100644 index 0000000000000..ae8ae89efba60 --- /dev/null +++ b/samples/subsys/app_event_manager/src/events/measurement_event.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _MEASUREMENT_EVENT_H_ +#define _MEASUREMENT_EVENT_H_ + +/** + * @brief Measurement Event + * @defgroup measurement_event Measurement Event + * @{ + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct measurement_event { + struct app_event_header header; + + int8_t value1; + int16_t value2; + int32_t value3; +}; + +APP_EVENT_TYPE_DECLARE(measurement_event); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* _MEASUREMENT_EVENT_H_ */ diff --git a/samples/subsys/app_event_manager/src/main.c b/samples/subsys/app_event_manager/src/main.c new file mode 100644 index 0000000000000..eb2f7c40a6806 --- /dev/null +++ b/samples/subsys/app_event_manager/src/main.c @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#define MODULE main +LOG_MODULE_REGISTER(MODULE); + +#define INIT_VALUE1 3 + +void main(void) +{ + if (app_event_manager_init()) { + LOG_ERR("Application Event Manager not initialized"); + } else { + struct config_event *event = new_config_event(); + + event->init_value1 = INIT_VALUE1; + APP_EVENT_SUBMIT(event); + } +} diff --git a/samples/subsys/app_event_manager/src/modules/controller.c b/samples/subsys/app_event_manager/src/modules/controller.c new file mode 100644 index 0000000000000..60249cfbcda20 --- /dev/null +++ b/samples/subsys/app_event_manager/src/modules/controller.c @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "measurement_event.h" +#include "control_event.h" +#include "ack_event.h" + +#define VALUE2_THRESH 15 +#define MODULE controller + +static bool ack_req; + +void send_control_event(void) +{ + ack_req = true; + struct control_event *event = new_control_event(); + + APP_EVENT_SUBMIT(event); +} + +static bool app_event_handler(const struct app_event_header *aeh) +{ + if (is_measurement_event(aeh)) { + __ASSERT_NO_MSG(!ack_req); + struct measurement_event *me = cast_measurement_event(aeh); + + if ((me->value2 >= VALUE2_THRESH) || + (me->value2 <= -VALUE2_THRESH)) { + send_control_event(); + } + + return false; + } + + if (is_ack_event(aeh)) { + __ASSERT_NO_MSG(ack_req); + ack_req = false; + return false; + } + + /* If event is unhandled, unsubscribe. */ + __ASSERT_NO_MSG(false); + + return false; +} + +APP_EVENT_LISTENER(MODULE, app_event_handler); +APP_EVENT_SUBSCRIBE(MODULE, measurement_event); +APP_EVENT_SUBSCRIBE(MODULE, ack_event); diff --git a/samples/subsys/app_event_manager/src/modules/sensor_simulated.c b/samples/subsys/app_event_manager/src/modules/sensor_simulated.c new file mode 100644 index 0000000000000..7aaa4e48488d5 --- /dev/null +++ b/samples/subsys/app_event_manager/src/modules/sensor_simulated.c @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "measurement_event.h" +#include "control_event.h" +#include "ack_event.h" +#include "config_event.h" + +#define SENSOR_SIMULATED_THREAD_STACK_SIZE 800 +#define SENSOR_SIMULATED_THREAD_PRIORITY 1 +#define SENSOR_SIMULATED_THREAD_SLEEP 500 +#define MODULE sensor_sim + +static K_THREAD_STACK_DEFINE(sensor_simulated_thread_stack, + SENSOR_SIMULATED_THREAD_STACK_SIZE); +static struct k_thread sensor_simulated_thread; + +static int8_t value1; +static int16_t value2; +static int32_t value3; + +static void measure_update(void) +{ + value2 += value1; + value3 += value2; +} + +static void measure(void) +{ + measure_update(); + + struct measurement_event *event = new_measurement_event(); + + event->value1 = value1; + event->value2 = value2; + event->value3 = value3; + APP_EVENT_SUBMIT(event); +} + +static void sensor_simulated_thread_fn(void) +{ + while (true) { + measure(); + k_sleep(K_MSEC(SENSOR_SIMULATED_THREAD_SLEEP)); + } +} + +static void init(void) +{ + k_thread_create(&sensor_simulated_thread, + sensor_simulated_thread_stack, + SENSOR_SIMULATED_THREAD_STACK_SIZE, + (k_thread_entry_t)sensor_simulated_thread_fn, + NULL, NULL, NULL, + SENSOR_SIMULATED_THREAD_PRIORITY, + 0, K_NO_WAIT); +} + +static bool app_event_handler(const struct app_event_header *aeh) +{ + if (is_control_event(aeh)) { + value1 = -value1; + struct ack_event *ack = new_ack_event(); + + APP_EVENT_SUBMIT(ack); + return false; + } + + if (is_config_event(aeh)) { + struct config_event *ce = cast_config_event(aeh); + + value1 = ce->init_value1; + init(); + return false; + } + + /* If event is unhandled, unsubscribe. */ + __ASSERT_NO_MSG(false); + + return false; +} + +APP_EVENT_LISTENER(MODULE, app_event_handler); +APP_EVENT_SUBSCRIBE(MODULE, control_event); +APP_EVENT_SUBSCRIBE(MODULE, config_event); diff --git a/samples/subsys/app_event_manager/src/modules/stats.c b/samples/subsys/app_event_manager/src/modules/stats.c new file mode 100644 index 0000000000000..2385d0422d090 --- /dev/null +++ b/samples/subsys/app_event_manager/src/modules/stats.c @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#define MODULE stats + +#include "measurement_event.h" + +#include +#define STATS_LOG_LEVEL 4 +LOG_MODULE_REGISTER(MODULE, STATS_LOG_LEVEL); + +#define STATS_ARR_SIZE 10 + +static int32_t val_arr[STATS_ARR_SIZE]; + +static void add_to_stats(int32_t value) +{ + static size_t ind; + + /* Add new value */ + val_arr[ind] = value; + ind++; + + if (ind == ARRAY_SIZE(val_arr)) { + ind = 0; + long long int sum = 0; + + for (size_t i = 0; i < ARRAY_SIZE(val_arr); i++) { + sum += val_arr[i]; + } + LOG_INF("Average value3: %d", (int)(sum/ARRAY_SIZE(val_arr))); + } +} + +static bool app_event_handler(const struct app_event_header *aeh) +{ + if (is_measurement_event(aeh)) { + struct measurement_event *me = cast_measurement_event(aeh); + + add_to_stats(me->value3); + return false; + } + + /* If event is unhandled, unsubscribe. */ + __ASSERT_NO_MSG(false); + + return false; +} + +APP_EVENT_LISTENER(MODULE, app_event_handler); +APP_EVENT_SUBSCRIBE(MODULE, measurement_event); diff --git a/subsys/CMakeLists.txt b/subsys/CMakeLists.txt index 35f47bfb03fba..f57859569c983 100644 --- a/subsys/CMakeLists.txt +++ b/subsys/CMakeLists.txt @@ -13,6 +13,7 @@ add_subdirectory(fs) add_subdirectory(ipc) add_subdirectory(mgmt) add_subdirectory_ifdef(CONFIG_MCUBOOT_IMG_MANAGER dfu) +add_subdirectory_ifdef(CONFIG_APP_EVENT_MANAGER app_event_manager) add_subdirectory_ifdef(CONFIG_NET_BUF net) add_subdirectory_ifdef(CONFIG_USB_DEVICE_STACK usb) add_subdirectory(random) diff --git a/subsys/Kconfig b/subsys/Kconfig index fb2c7916edd43..377a7bffef36b 100644 --- a/subsys/Kconfig +++ b/subsys/Kconfig @@ -20,6 +20,8 @@ source "subsys/disk/Kconfig" source "subsys/emul/Kconfig" +source "subsys/app_event_manager/Kconfig" + source "subsys/fb/Kconfig" source "subsys/fs/Kconfig" diff --git a/subsys/app_event_manager/CMakeLists.txt b/subsys/app_event_manager/CMakeLists.txt new file mode 100644 index 0000000000000..bb3f3b4e36bbf --- /dev/null +++ b/subsys/app_event_manager/CMakeLists.txt @@ -0,0 +1,11 @@ +# +# Copyright (c) 2018 Nordic Semiconductor +# +# SPDX-License-Identifier: Apache-2.0 +# + +zephyr_include_directories(.) +zephyr_sources(app_event_manager.c) +zephyr_sources_ifdef(CONFIG_SHELL app_event_manager_shell.c) + +zephyr_linker_sources(SECTIONS aem.ld) diff --git a/subsys/app_event_manager/Kconfig b/subsys/app_event_manager/Kconfig new file mode 100644 index 0000000000000..0b90abe4ab709 --- /dev/null +++ b/subsys/app_event_manager/Kconfig @@ -0,0 +1,97 @@ +# +# Copyright (c) 2021 Nordic Semiconductor +# +# SPDX-License-Identifier: Apache-2.0 +# + +menuconfig APP_EVENT_MANAGER + bool + prompt "Application Event Manager" + help + Enable Application Event Manager. + Note that Application Event Manager uses orphan sections to handle its + data structures. + +if APP_EVENT_MANAGER + +config APP_EVENT_MANAGER_SHOW_EVENTS + bool "Show events" + depends on LOG + default y + help + This option controls if events are printed to console. + +config APP_EVENT_MANAGER_SHOW_EVENT_HANDLERS + bool "Show event handlers" + depends on APP_EVENT_MANAGER_SHOW_EVENTS + help + This option controls if event handlers are printed to console. + +config APP_EVENT_MANAGER_TRACE_EVENT_DATA + bool "Enables tracing information" + help + This option allows to gather information about events for tracing purposes. + +module = APP_EVENT_MANAGER +module-str = Application Event Manager +source "${ZEPHYR_BASE}/subsys/logging/Kconfig.template.log_config" + +config APP_EVENT_MANAGER_EVENT_LOG_BUF_LEN + int "Length of buffer for processing event message" + depends on APP_EVENT_MANAGER_USE_DEPRECATED_LOG_FUN + default 128 + range 2 1024 + help + This option is only needed for deprecated event logging approach. + +config APP_EVENT_MANAGER_USE_DEPRECATED_LOG_FUN + bool "Allow using both logging functions current and deprecated one." + help + This option is only needed for deprecated event logging approach. + +config APP_EVENT_MANAGER_LOG_EVENT_TYPE + bool "Include event type in the event log output" + default y + +config APP_EVENT_MANAGER_MAX_EVENT_CNT + int "Maximum number of event types" + default 32 + help + Maximum number of declared event types in Application Event Manager. + +config APP_EVENT_MANAGER_PROVIDE_EVENT_SIZE + bool "Provide information about the event size" + help + This option enables the information about event size. + This would require to store more information with event type + and should be enabled only if such an information is required. + +config APP_EVENT_MANAGER_POSTINIT_HOOK + bool "Enable postinit hook" + help + Enable postinit hooks support. + This option is here for optimisation purposes. + When postinit hook is not in use the related code may be removed. + +config APP_EVENT_MANAGER_SUBMIT_HOOKS + bool "Enable event submit hooks" + help + Enable event submit hooks support. + This option is here for optimisation purposes. + When submit hook is not in use the related code may be removed. + +config APP_EVENT_MANAGER_PREPROCESS_HOOKS + bool "Enable event preprocess hooks" + help + Enable event preprocess hooks support. + This option is here for optimisation purposes. + When preprocess hook is not in use the related code may be removed. + +config APP_EVENT_MANAGER_POSTPROCESS_HOOKS + bool "Enable event postprocess hooks" + help + Enable event postprocess hooks support. + This option is here for optimisation purposes. + When postprocess hook is not in use the related code may be removed. + +endif # APP_EVENT_MANAGER diff --git a/subsys/app_event_manager/aem.ld b/subsys/app_event_manager/aem.ld new file mode 100644 index 0000000000000..04c4103f68a19 --- /dev/null +++ b/subsys/app_event_manager/aem.ld @@ -0,0 +1,13 @@ +ITERABLE_SECTION_ROM(event_type, 4) +ITERABLE_SECTION_ROM(event_listener, 4) +ITERABLE_SECTION_ROM(app_event_manager_postinit_hook, 4) +ITERABLE_SECTION_ROM(event_submit_hook, 4) +ITERABLE_SECTION_ROM(event_preprocess_hook, 4) +ITERABLE_SECTION_ROM(event_postprocess_hook, 4) + +event_subscribers_all : ALIGN_WITH_INPUT +{ + __start_event_subscribers_all = .; + KEEP(*(SORT(event_subscribers_*))); + __stop_event_subscribers_all = .; +} GROUP_LINK_IN(ROMABLE_REGION) diff --git a/subsys/app_event_manager/app_event_manager.c b/subsys/app_event_manager/app_event_manager.c new file mode 100644 index 0000000000000..a570bd9b7bd27 --- /dev/null +++ b/subsys/app_event_manager/app_event_manager.c @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_REBOOT) +#include +#endif + +LOG_MODULE_REGISTER(app_event_manager, CONFIG_APP_EVENT_MANAGER_LOG_LEVEL); + +static void event_processor_fn(struct k_work *work); + +struct app_event_manager_event_display_bm _app_event_manager_event_display_bm; + +static K_WORK_DEFINE(event_processor, event_processor_fn); +static sys_slist_t eventq = SYS_SLIST_STATIC_INIT(&eventq); +static struct k_spinlock lock; + +static bool log_is_event_displayed(const struct event_type *et) +{ + size_t idx = et - _event_type_list_start; + + return atomic_test_bit(_app_event_manager_event_display_bm.flags, idx); +} + +#if IS_ENABLED(CONFIG_APP_EVENT_MANAGER_USE_DEPRECATED_LOG_FUN) +static void log_event_using_buffer(const struct app_event_header *aeh, + const struct event_type *et) +{ + char log_buf[CONFIG_APP_EVENT_MANAGER_EVENT_LOG_BUF_LEN]; + + int pos = et->log_event_func_dep(aeh, log_buf, sizeof(log_buf)); + + if (pos < 0) { + log_buf[0] = '\0'; + } else if (pos >= sizeof(log_buf)) { + log_buf[sizeof(log_buf) - 2] = '~'; + } + + if (IS_ENABLED(CONFIG_APP_EVENT_MANAGER_LOG_EVENT_TYPE)) { + LOG_INF("e: %s %s", et->name, log_strdup(log_buf)); + } else { + LOG_INF("%s", log_strdup(log_buf)); + } +} +#endif /*CONFIG_APP_EVENT_MANAGER_USE_DEPRECATED_LOG_FUN*/ + +static void log_event(const struct app_event_header *aeh) +{ + const struct event_type *et = aeh->type_id; + + if (!IS_ENABLED(CONFIG_APP_EVENT_MANAGER_SHOW_EVENTS) || + !log_is_event_displayed(et)) { + return; + } + + + if (et->log_event_func) { + et->log_event_func(aeh); + } +#ifdef CONFIG_APP_EVENT_MANAGER_USE_DEPRECATED_LOG_FUN + else if (et->log_event_func_dep) { + log_event_using_buffer(aeh, et); + } +#endif + + else if (IS_ENABLED(CONFIG_APP_EVENT_MANAGER_LOG_EVENT_TYPE)) { + LOG_INF("e: %s", et->name); + } +} + +static void log_event_progress(const struct event_type *et, + const struct event_listener *el) +{ + if (!IS_ENABLED(CONFIG_APP_EVENT_MANAGER_SHOW_EVENTS) || + !IS_ENABLED(CONFIG_APP_EVENT_MANAGER_SHOW_EVENT_HANDLERS) || + !log_is_event_displayed(et)) { + return; + } + + LOG_INF("|\tnotifying %s", el->name); +} + +static void log_event_consumed(const struct event_type *et) +{ + if (!IS_ENABLED(CONFIG_APP_EVENT_MANAGER_SHOW_EVENTS) || + !IS_ENABLED(CONFIG_APP_EVENT_MANAGER_SHOW_EVENT_HANDLERS) || + !log_is_event_displayed(et)) { + return; + } + + LOG_INF("|\tevent consumed"); +} + +static void log_event_init(void) +{ + if (!IS_ENABLED(CONFIG_LOG)) { + return; + } + + STRUCT_SECTION_FOREACH(event_type, et) { + if (app_event_get_type_flag(et, APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE)) { + size_t idx = et - _event_type_list_start; + + atomic_set_bit(_app_event_manager_event_display_bm.flags, idx); + } + } +} + +void * __weak app_event_manager_alloc(size_t size) +{ + void *event = k_malloc(size); + + if (unlikely(!event)) { + LOG_ERR("Application Event Manager OOM error\n"); + __ASSERT_NO_MSG(false); + #if defined(CONFIG_REBOOT) + sys_reboot(SYS_REBOOT_WARM); + #else + k_panic(); + #endif + return NULL; + } + + return event; +} + +void __weak app_event_manager_free(void *addr) +{ + k_free(addr); +} + +static void event_processor_fn(struct k_work *work) +{ + sys_slist_t events = SYS_SLIST_STATIC_INIT(&events); + + /* Make current event list local. */ + k_spinlock_key_t key = k_spin_lock(&lock); + + if (sys_slist_is_empty(&eventq)) { + k_spin_unlock(&lock, key); + return; + } + + sys_slist_merge_slist(&events, &eventq); + + k_spin_unlock(&lock, key); + + /* Traverse the list of events. */ + sys_snode_t *node; + + while (NULL != (node = sys_slist_get(&events))) { + struct app_event_header *aeh = CONTAINER_OF(node, + struct app_event_header, + node); + + APP_EVENT_ASSERT_ID(aeh->type_id); + + const struct event_type *et = aeh->type_id; + + if (IS_ENABLED(CONFIG_APP_EVENT_MANAGER_PREPROCESS_HOOKS)) { + STRUCT_SECTION_FOREACH(event_preprocess_hook, h) { + h->hook(aeh); + } + } + + log_event(aeh); + + bool consumed = false; + + for (const struct event_subscriber *es = et->subs_start; + (es != et->subs_stop) && !consumed; + es++) { + + __ASSERT_NO_MSG(es != NULL); + + const struct event_listener *el = es->listener; + + __ASSERT_NO_MSG(el != NULL); + __ASSERT_NO_MSG(el->notification != NULL); + + log_event_progress(et, el); + + consumed = el->notification(aeh); + + if (consumed) { + log_event_consumed(et); + } + } + + if (IS_ENABLED(CONFIG_APP_EVENT_MANAGER_POSTPROCESS_HOOKS)) { + STRUCT_SECTION_FOREACH(event_postprocess_hook, h) { + h->hook(aeh); + } + } + + app_event_manager_free(aeh); + } +} + +void _event_submit(struct app_event_header *aeh) +{ + __ASSERT_NO_MSG(aeh); + APP_EVENT_ASSERT_ID(aeh->type_id); + + k_spinlock_key_t key = k_spin_lock(&lock); + + if (IS_ENABLED(CONFIG_APP_EVENT_MANAGER_SUBMIT_HOOKS)) { + STRUCT_SECTION_FOREACH(event_submit_hook, h) { + h->hook(aeh); + } + } + sys_slist_append(&eventq, &aeh->node); + k_spin_unlock(&lock, key); + + k_work_submit(&event_processor); +} + +int app_event_manager_init(void) +{ + int ret = 0; + + __ASSERT_NO_MSG(_event_type_list_end - _event_type_list_start <= + CONFIG_APP_EVENT_MANAGER_MAX_EVENT_CNT); + + log_event_init(); + + if (IS_ENABLED(CONFIG_APP_EVENT_MANAGER_POSTINIT_HOOK)) { + STRUCT_SECTION_FOREACH(app_event_manager_postinit_hook, h) { + ret = h->hook(); + if (ret) { + break; + } + } + } + + return ret; +} diff --git a/subsys/app_event_manager/app_event_manager_shell.c b/subsys/app_event_manager/app_event_manager_shell.c new file mode 100644 index 0000000000000..7d109c83da450 --- /dev/null +++ b/subsys/app_event_manager/app_event_manager_shell.c @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +static int show_events(const struct shell *event_manager_shell, size_t argc, + char **argv) +{ + shell_fprintf(event_manager_shell, SHELL_NORMAL, + "Registered Events:\n"); + + for (const struct event_type *et = _event_type_list_start; + (et != NULL) && (et != _event_type_list_end); et++) { + + size_t ev_id = et - _event_type_list_start; + + shell_fprintf(event_manager_shell, + SHELL_NORMAL, + "%c %d:\t%s\n", + (atomic_test_bit(_app_event_manager_event_display_bm.flags, ev_id)) ? + 'E' : 'D', + ev_id, + et->name); + } + + return 0; +} + +static int show_listeners(const struct shell *event_manager_shell, size_t argc, + char **argv) +{ + shell_fprintf(event_manager_shell, SHELL_NORMAL, "Registered Listeners:\n"); + + STRUCT_SECTION_FOREACH(event_listener, el) { + __ASSERT_NO_MSG(el != NULL); + shell_fprintf(event_manager_shell, SHELL_NORMAL, "|\t[L:%s]\n", el->name); + } + + return 0; +} + +static int show_subscribers(const struct shell *event_manager_shell, size_t argc, + char **argv) +{ + shell_fprintf(event_manager_shell, SHELL_NORMAL, "Registered Subscribers:\n"); + + STRUCT_SECTION_FOREACH(event_type, et) { + bool is_subscribed = false; + + for (const struct event_subscriber *es = + et->subs_start; + es != et->subs_stop; + es++) { + + __ASSERT_NO_MSG(es != NULL); + const struct event_listener *el = es->listener; + + __ASSERT_NO_MSG(el != NULL); + shell_fprintf(event_manager_shell, SHELL_NORMAL, + "|\t[E:%s] -> [L:%s]\n", + et->name, el->name); + + is_subscribed = true; + } + + + if (!is_subscribed) { + shell_fprintf(event_manager_shell, SHELL_NORMAL, + "|\t[E:%s] has no subscribers\n", + et->name); + } + shell_fprintf(event_manager_shell, SHELL_NORMAL, "\n"); + } + + return 0; +} + +static void set_event_displaying(const struct shell *event_manager_shell, size_t argc, + char **argv, bool enable) +{ + /* If no IDs specified, all registered events are affected */ + if (argc == 1) { + for (const struct event_type *et = _event_type_list_start; + (et != NULL) && (et != _event_type_list_end); + et++) { + + size_t ev_id = et - _event_type_list_start; + + atomic_set_bit_to(_app_event_manager_event_display_bm.flags, ev_id, enable); + } + + shell_fprintf(event_manager_shell, + SHELL_NORMAL, + "Displaying all events %sabled\n", + enable ? "en":"dis"); + } else { + int event_indexes[argc - 1]; + + for (size_t i = 0; i < ARRAY_SIZE(event_indexes); i++) { + char *end; + + event_indexes[i] = strtol(argv[i + 1], &end, 10); + + if ((event_indexes[i] < 0) + || (event_indexes[i] >= _event_type_list_end - _event_type_list_start) + || (*end != '\0')) { + + shell_error(event_manager_shell, "Invalid event ID: %s", + argv[i + 1]); + return; + } + } + + for (size_t i = 0; i < ARRAY_SIZE(event_indexes); i++) { + atomic_set_bit_to(_app_event_manager_event_display_bm.flags, + event_indexes[i], + enable); + const struct event_type *et = + _event_type_list_start + event_indexes[i]; + const char *event_name = et->name; + + shell_fprintf(event_manager_shell, + SHELL_NORMAL, + "Displaying event %s %sabled\n", + event_name, + enable ? "en":"dis"); + } + } +} + +static int enable_event_displaying(const struct shell *event_manager_shell, size_t argc, + char **argv) +{ + set_event_displaying(event_manager_shell, argc, argv, true); + return 0; +} + +static int disable_event_displaying(const struct shell *event_manager_shell, + size_t argc, char **argv) +{ + set_event_displaying(event_manager_shell, argc, argv, false); + return 0; +} + +SHELL_STATIC_SUBCMD_SET_CREATE(sub_app_event_manager, + SHELL_CMD_ARG(show_listeners, NULL, "Show listeners", + show_listeners, 0, 0), + SHELL_CMD_ARG(show_subscribers, NULL, "Show subscribers", + show_subscribers, 0, 0), + SHELL_CMD_ARG(show_events, NULL, "Show events", show_events, 0, 0), + SHELL_CMD_ARG(disable, NULL, "Disable displaying event with given ID", + disable_event_displaying, 0, + sizeof(_app_event_manager_event_display_bm) * 8 - 1), + SHELL_CMD_ARG(enable, NULL, "Enable displaying event with given ID", + enable_event_displaying, 0, + sizeof(_app_event_manager_event_display_bm) * 8 - 1), + SHELL_SUBCMD_SET_END +); + +SHELL_CMD_REGISTER(app_event_manager, &sub_app_event_manager, + "Application Event Manager commands", NULL); diff --git a/tests/subsys/app_event_manager/CMakeLists.txt b/tests/subsys/app_event_manager/CMakeLists.txt new file mode 100644 index 0000000000000..3b783dca789d2 --- /dev/null +++ b/tests/subsys/app_event_manager/CMakeLists.txt @@ -0,0 +1,20 @@ +# +# Copyright (c) 2022 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project("Application Event Manager unit tests") + +# Include application event headers +zephyr_library_include_directories(src/events) +zephyr_library_include_directories(src/modules) + +# Add test sources +target_sources(app PRIVATE src/main.c) +add_subdirectory(src/events) +add_subdirectory(src/modules) +add_subdirectory(src/utils) diff --git a/tests/subsys/app_event_manager/overlay-event_size.conf b/tests/subsys/app_event_manager/overlay-event_size.conf new file mode 100644 index 0000000000000..9f1768103acc6 --- /dev/null +++ b/tests/subsys/app_event_manager/overlay-event_size.conf @@ -0,0 +1,7 @@ +# +# Copyright (c) 2022 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +CONFIG_APP_EVENT_MANAGER_PROVIDE_EVENT_SIZE=y diff --git a/tests/subsys/app_event_manager/prj.conf b/tests/subsys/app_event_manager/prj.conf new file mode 100644 index 0000000000000..9376c9eee60f1 --- /dev/null +++ b/tests/subsys/app_event_manager/prj.conf @@ -0,0 +1,15 @@ +# +# Copyright (c) 2022 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +CONFIG_ZTEST=y + +# Configuration required by Application Event Manager +CONFIG_APP_EVENT_MANAGER=y +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 +CONFIG_HEAP_MEM_POOL_SIZE=1024 + +# Custom reboot handler is implemented for test purposes +CONFIG_REBOOT=n diff --git a/tests/subsys/app_event_manager/src/events/CMakeLists.txt b/tests/subsys/app_event_manager/src/events/CMakeLists.txt new file mode 100644 index 0000000000000..738dba2973318 --- /dev/null +++ b/tests/subsys/app_event_manager/src/events/CMakeLists.txt @@ -0,0 +1,15 @@ +# +# Copyright (c) 2022 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/data_event.c) + +target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/multicontext_event.c) + +target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/order_event.c) + +target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/sized_events.c) + +target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/test_events.c) diff --git a/tests/subsys/app_event_manager/src/events/data_event.c b/tests/subsys/app_event_manager/src/events/data_event.c new file mode 100644 index 0000000000000..7c6f8ccd2d230 --- /dev/null +++ b/tests/subsys/app_event_manager/src/events/data_event.c @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "data_event.h" + + +APP_EVENT_TYPE_DEFINE(data_event, + NULL, + NULL, + APP_EVENT_FLAGS_CREATE()); diff --git a/tests/subsys/app_event_manager/src/events/data_event.h b/tests/subsys/app_event_manager/src/events/data_event.h new file mode 100644 index 0000000000000..a06b7e90ab984 --- /dev/null +++ b/tests/subsys/app_event_manager/src/events/data_event.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _DATA_EVENT_H_ +#define _DATA_EVENT_H_ + +/** + * @brief Data Event + * @defgroup data_event Data Event + * @{ + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct data_event { + struct app_event_header header; + + int8_t val1; + int16_t val2; + int32_t val3; + uint8_t val1u; + uint16_t val2u; + uint32_t val3u; + + char *descr; +}; + +APP_EVENT_TYPE_DECLARE(data_event); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* _DATA_EVENT_H_ */ diff --git a/tests/subsys/app_event_manager/src/events/multicontext_event.c b/tests/subsys/app_event_manager/src/events/multicontext_event.c new file mode 100644 index 0000000000000..72c32d6eb37a5 --- /dev/null +++ b/tests/subsys/app_event_manager/src/events/multicontext_event.c @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "multicontext_event.h" + +APP_EVENT_TYPE_DEFINE(multicontext_event, + NULL, + NULL, + APP_EVENT_FLAGS_CREATE()); diff --git a/tests/subsys/app_event_manager/src/events/multicontext_event.h b/tests/subsys/app_event_manager/src/events/multicontext_event.h new file mode 100644 index 0000000000000..2e19dc6848942 --- /dev/null +++ b/tests/subsys/app_event_manager/src/events/multicontext_event.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _MULTICONTEXT_EVENT_H_ +#define _MULTICONTEXT_EVENT_H_ + +/** + * @brief Multicontext Event + * @defgroup multicontext_event Multicontext Event + * @{ + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct multicontext_event { + struct app_event_header header; + + int val1; + int val2; +}; + +APP_EVENT_TYPE_DECLARE(multicontext_event); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* _MULTICONTEXT_EVENT_H_ */ diff --git a/tests/subsys/app_event_manager/src/events/order_event.c b/tests/subsys/app_event_manager/src/events/order_event.c new file mode 100644 index 0000000000000..835a501675bda --- /dev/null +++ b/tests/subsys/app_event_manager/src/events/order_event.c @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "order_event.h" + +APP_EVENT_TYPE_DEFINE(order_event, + NULL, + NULL, + APP_EVENT_FLAGS_CREATE()); diff --git a/tests/subsys/app_event_manager/src/events/order_event.h b/tests/subsys/app_event_manager/src/events/order_event.h new file mode 100644 index 0000000000000..6bf765a31cd4a --- /dev/null +++ b/tests/subsys/app_event_manager/src/events/order_event.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _ORDER_EVENT_H_ +#define _ORDER_EVENT_H_ + +/** + * @brief Order Event + * @defgroup order_event Order Event + * @{ + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct order_event { + struct app_event_header header; + + int val; +}; + +APP_EVENT_TYPE_DECLARE(order_event); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* _ORDER_EVENT_H_ */ diff --git a/tests/subsys/app_event_manager/src/events/sized_events.c b/tests/subsys/app_event_manager/src/events/sized_events.c new file mode 100644 index 0000000000000..b0a9b9a81b042 --- /dev/null +++ b/tests/subsys/app_event_manager/src/events/sized_events.c @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "sized_events.h" + +APP_EVENT_TYPE_DEFINE(test_size1_event, NULL, NULL, APP_EVENT_FLAGS_CREATE()); +APP_EVENT_TYPE_DEFINE(test_size2_event, NULL, NULL, APP_EVENT_FLAGS_CREATE()); +APP_EVENT_TYPE_DEFINE(test_size3_event, NULL, NULL, APP_EVENT_FLAGS_CREATE()); +APP_EVENT_TYPE_DEFINE(test_size_big_event, NULL, NULL, APP_EVENT_FLAGS_CREATE()); + +APP_EVENT_TYPE_DEFINE(test_dynamic_event, NULL, NULL, APP_EVENT_FLAGS_CREATE()); +APP_EVENT_TYPE_DEFINE(test_dynamic_with_data_event, + NULL, + NULL, + APP_EVENT_FLAGS_CREATE()); diff --git a/tests/subsys/app_event_manager/src/events/sized_events.h b/tests/subsys/app_event_manager/src/events/sized_events.h new file mode 100644 index 0000000000000..b26dde6406cf0 --- /dev/null +++ b/tests/subsys/app_event_manager/src/events/sized_events.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _SIZED_EVENTS_H_ +#define _SIZED_EVENTS_H_ + +/** + * @brief Events with different sizes + * @defgroup sized_events Events used to test @ref app_event_manager_event_size + * @{ + */ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +struct test_size1_event { + struct app_event_header header; + + uint8_t val1; +}; + +APP_EVENT_TYPE_DECLARE(test_size1_event); + + +struct test_size2_event { + struct app_event_header header; + + uint8_t val1; + uint8_t val2; +}; + +APP_EVENT_TYPE_DECLARE(test_size2_event); + + +struct test_size3_event { + struct app_event_header header; + + uint8_t val1; + uint8_t val2; + uint8_t val3; +}; + +APP_EVENT_TYPE_DECLARE(test_size3_event); + + +struct test_size_big_event { + struct app_event_header header; + + uint32_t array[64]; +}; + +APP_EVENT_TYPE_DECLARE(test_size_big_event); + + +struct test_dynamic_event { + struct app_event_header header; + + struct event_dyndata dyndata; +}; + +APP_EVENT_TYPE_DYNDATA_DECLARE(test_dynamic_event); + + +struct test_dynamic_with_data_event { + struct app_event_header header; + + uint32_t val1; + uint32_t val2; + struct event_dyndata dyndata; +}; + +APP_EVENT_TYPE_DYNDATA_DECLARE(test_dynamic_with_data_event); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* _SIZED_EVENTS_H_ */ diff --git a/tests/subsys/app_event_manager/src/events/test_events.c b/tests/subsys/app_event_manager/src/events/test_events.c new file mode 100644 index 0000000000000..bd71b19c31d92 --- /dev/null +++ b/tests/subsys/app_event_manager/src/events/test_events.c @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "test_events.h" + +APP_EVENT_TYPE_DEFINE(test_start_event, + NULL, + NULL, + APP_EVENT_FLAGS_CREATE()); + +APP_EVENT_TYPE_DEFINE(test_end_event, + NULL, + NULL, + APP_EVENT_FLAGS_CREATE()); diff --git a/tests/subsys/app_event_manager/src/events/test_events.h b/tests/subsys/app_event_manager/src/events/test_events.h new file mode 100644 index 0000000000000..cdff32efaf015 --- /dev/null +++ b/tests/subsys/app_event_manager/src/events/test_events.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _TEST_EVENTS_H_ +#define _TEST_EVENTS_H_ + +/** + * @brief Test Start and End Events + * @defgroup test_events Test Start and End Events + * @{ + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum test_id { + TEST_IDLE, + TEST_BASIC, + TEST_DATA, + TEST_EVENT_ORDER, + TEST_SUBSCRIBER_ORDER, + TEST_OOM_RESET, + TEST_MULTICONTEXT, + + TEST_CNT +}; + +struct test_start_event { + struct app_event_header header; + + enum test_id test_id; +}; + +APP_EVENT_TYPE_DECLARE(test_start_event); + +struct test_end_event { + struct app_event_header header; + + enum test_id test_id; +}; + +APP_EVENT_TYPE_DECLARE(test_end_event); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* _TEST_EVENTS_H_ */ diff --git a/tests/subsys/app_event_manager/src/main.c b/tests/subsys/app_event_manager/src/main.c new file mode 100644 index 0000000000000..d7044825b8896 --- /dev/null +++ b/tests/subsys/app_event_manager/src/main.c @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "sized_events.h" +#include "test_events.h" + +static enum test_id cur_test_id; +static K_SEM_DEFINE(test_end_sem, 0, 1); +static bool expect_assert; + +/* Provide custom assert post action handler to handle the assertion on OOM + * error in Application Event Manager. + */ +BUILD_ASSERT(!IS_ENABLED(CONFIG_ASSERT_NO_FILE_INFO)); +void assert_post_action(const char *file, unsigned int line) +{ + printk("assert_post_action - file: %s (line: %u)\n", file, line); + if (expect_assert) { + expect_assert = false; + printk("Assertion was expected.\n"); + } else { + zassert(false, "", "Unexpected assertion reached."); + } +} + +void test_init(void) +{ + zassert_false(app_event_manager_init(), "Error when initializing"); +} + +static void test_start(enum test_id test_id) +{ + cur_test_id = test_id; + struct test_start_event *ts = new_test_start_event(); + + zassert_not_null(ts, "Failed to allocate event"); + ts->test_id = test_id; + APP_EVENT_SUBMIT(ts); + + int err = k_sem_take(&test_end_sem, K_SECONDS(30)); + zassert_equal(err, 0, "Test execution hanged"); +} + +static void test_basic(void) +{ + test_start(TEST_BASIC); +} + +static void test_data(void) +{ + test_start(TEST_DATA); +} + +static void test_event_order(void) +{ + test_start(TEST_EVENT_ORDER); +} + +static void test_subs_order(void) +{ + test_start(TEST_SUBSCRIBER_ORDER); +} + +static void test_multicontext(void) +{ + test_start(TEST_MULTICONTEXT); +} + +static void test_event_size_static(void) +{ + if (!IS_ENABLED(CONFIG_APP_EVENT_MANAGER_PROVIDE_EVENT_SIZE)) { + ztest_test_skip(); + return; + } + + struct test_size1_event *ev_s1; + struct test_size2_event *ev_s2; + struct test_size3_event *ev_s3; + struct test_size_big_event *ev_sb; + + ev_s1 = new_test_size1_event(); + zassert_equal(sizeof(*ev_s1), app_event_manager_event_size(&ev_s1->header), + "Event size1 unexpected size"); + app_event_manager_free(ev_s1); + + ev_s2 = new_test_size2_event(); + zassert_equal(sizeof(*ev_s2), app_event_manager_event_size(&ev_s2->header), + "Event size2 unexpected size"); + app_event_manager_free(ev_s2); + + ev_s3 = new_test_size3_event(); + zassert_equal(sizeof(*ev_s3), app_event_manager_event_size(&ev_s3->header), + "Event size3 unexpected size"); + app_event_manager_free(ev_s3); + + ev_sb = new_test_size_big_event(); + zassert_equal(sizeof(*ev_sb), app_event_manager_event_size(&ev_sb->header), + "Event size_big unexpected size"); + app_event_manager_free(ev_sb); +} + +static void test_event_size_dynamic(void) +{ + if (!IS_ENABLED(CONFIG_APP_EVENT_MANAGER_PROVIDE_EVENT_SIZE)) { + ztest_test_skip(); + return; + } + + struct test_dynamic_event *ev; + + ev = new_test_dynamic_event(0); + zassert_equal(sizeof(*ev) + 0, app_event_manager_event_size(&ev->header), + "Event dynamic with 0 elements unexpected size"); + app_event_manager_free(ev); + + ev = new_test_dynamic_event(10); + zassert_equal(sizeof(*ev) + 10, app_event_manager_event_size(&ev->header), + "Event dynamic with 10 elements unexpected size"); + app_event_manager_free(ev); + + ev = new_test_dynamic_event(100); + zassert_equal(sizeof(*ev) + 100, app_event_manager_event_size(&ev->header), + "Event dynamic with 100 elements unexpected size"); + app_event_manager_free(ev); +} + +static void test_event_size_dynamic_with_data(void) +{ + if (!IS_ENABLED(CONFIG_APP_EVENT_MANAGER_PROVIDE_EVENT_SIZE)) { + ztest_test_skip(); + return; + } + + struct test_dynamic_with_data_event *ev; + + ev = new_test_dynamic_with_data_event(0); + zassert_equal(sizeof(*ev) + 0, app_event_manager_event_size(&ev->header), + "Event dynamic with 0 elements unexpected size"); + app_event_manager_free(ev); + + ev = new_test_dynamic_with_data_event(10); + zassert_equal(sizeof(*ev) + 10, app_event_manager_event_size(&ev->header), + "Event dynamic with 10 elements unexpected size"); + app_event_manager_free(ev); + + ev = new_test_dynamic_with_data_event(100); + zassert_equal(sizeof(*ev) + 100, app_event_manager_event_size(&ev->header), + "Event dynamic with 100 elements unexpected size"); + app_event_manager_free(ev); +} + +static void test_event_size_disabled(void) +{ + if (IS_ENABLED(CONFIG_APP_EVENT_MANAGER_PROVIDE_EVENT_SIZE)) { + ztest_test_skip(); + return; + } + + struct test_size1_event *ev_s1; + + ev_s1 = new_test_size1_event(); + expect_assert = true; + zassert_equal(0, app_event_manager_event_size(&ev_s1->header), + "Event size1 unexpected size"); + zassert_false(expect_assert, + "Assertion during app_event_manager_event_size function execution was expected"); + app_event_manager_free(ev_s1); +} + +void test_oom_reset(void); + +void test_main(void) +{ + ztest_test_suite(app_event_manager_tests, + ztest_unit_test(test_init), + ztest_unit_test(test_basic), + ztest_unit_test(test_data), + ztest_unit_test(test_event_order), + ztest_unit_test(test_subs_order), + ztest_unit_test(test_oom_reset), + ztest_unit_test(test_multicontext), + ztest_unit_test(test_event_size_static), + ztest_unit_test(test_event_size_dynamic), + ztest_unit_test(test_event_size_dynamic_with_data), + ztest_unit_test(test_event_size_disabled) + ); + + ztest_run_test_suite(app_event_manager_tests); +} + +static bool app_event_handler(const struct app_event_header *aeh) +{ + if (is_test_end_event(aeh)) { + struct test_end_event *ev = cast_test_end_event(aeh); + + zassert_equal(cur_test_id, ev->test_id, + "End test ID does not equal start test ID"); + cur_test_id = TEST_IDLE; + k_sem_give(&test_end_sem); + + return false; + } + + zassert_true(false, "Wrong event type received"); + return false; +} + +APP_EVENT_LISTENER(test_main, app_event_handler); +APP_EVENT_SUBSCRIBE(test_main, test_end_event); diff --git a/tests/subsys/app_event_manager/src/modules/CMakeLists.txt b/tests/subsys/app_event_manager/src/modules/CMakeLists.txt new file mode 100644 index 0000000000000..fddf978efdb8d --- /dev/null +++ b/tests/subsys/app_event_manager/src/modules/CMakeLists.txt @@ -0,0 +1,18 @@ +# +# Copyright (c) 2022 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/test_basic.c) + +target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/test_data.c) + +target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/test_multicontext.c) + +target_sources(app PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/test_multicontext_handler.c) + +target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/test_oom.c) + +target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/test_subs.c) diff --git a/tests/subsys/app_event_manager/src/modules/test_basic.c b/tests/subsys/app_event_manager/src/modules/test_basic.c new file mode 100644 index 0000000000000..d7f0172b078f1 --- /dev/null +++ b/tests/subsys/app_event_manager/src/modules/test_basic.c @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include +#include + +#include "test_config.h" + +#define MODULE test_basic + +static bool app_event_handler(const struct app_event_header *aeh) +{ + if (is_test_start_event(aeh)) { + struct test_start_event *st = cast_test_start_event(aeh); + + switch (st->test_id) { + case TEST_BASIC: + { + struct test_end_event *et = new_test_end_event(); + + zassert_not_null(et, "Failed to allocate event"); + et->test_id = st->test_id; + APP_EVENT_SUBMIT(et); + break; + } + + case TEST_DATA: + { + struct data_event *event = new_data_event(); + static char descr[] = TEST_STRING; + + zassert_not_null(event, "Failed to allocate event"); + event->val1 = TEST_VAL1; + event->val2 = TEST_VAL2; + event->val3 = TEST_VAL3; + event->val1u = TEST_VAL1U; + event->val2u = TEST_VAL2U; + event->val3u = TEST_VAL3U; + + event->descr = descr; + + APP_EVENT_SUBMIT(event); + break; + } + + case TEST_EVENT_ORDER: + { + for (size_t i = 0; i < TEST_EVENT_ORDER_CNT; i++) { + struct order_event *event = new_order_event(); + + zassert_not_null(event, "Failed to allocate event"); + event->val = i; + APP_EVENT_SUBMIT(event); + } + break; + } + + case TEST_SUBSCRIBER_ORDER: + { + struct order_event *event = new_order_event(); + + zassert_not_null(event, "Failed to allocate event"); + APP_EVENT_SUBMIT(event); + break; + } + + default: + /* Ignore other test cases, check if proper test_id. */ + zassert_true(st->test_id < TEST_CNT, + "test_id out of range"); + break; + } + + return false; + } + + zassert_true(false, "Event unhandled"); + + return false; +} + +APP_EVENT_LISTENER(MODULE, app_event_handler); +APP_EVENT_SUBSCRIBE(MODULE, test_start_event); diff --git a/tests/subsys/app_event_manager/src/modules/test_config.h b/tests/subsys/app_event_manager/src/modules/test_config.h new file mode 100644 index 0000000000000..d760fe16a97b6 --- /dev/null +++ b/tests/subsys/app_event_manager/src/modules/test_config.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* TEST_DATA */ + +#define TEST_VAL1 -10 +#define TEST_VAL2 20 +#define TEST_VAL3 -30 + +#define TEST_VAL1U 10 +#define TEST_VAL2U 20 +#define TEST_VAL3U 30 + +#define TEST_STRING "description123" + + +/* TEST_EVENT_ORDER */ +#define TEST_EVENT_ORDER_CNT 20 diff --git a/tests/subsys/app_event_manager/src/modules/test_data.c b/tests/subsys/app_event_manager/src/modules/test_data.c new file mode 100644 index 0000000000000..3083a678c769a --- /dev/null +++ b/tests/subsys/app_event_manager/src/modules/test_data.c @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include +#include + +#include "test_config.h" + +#define MODULE test_data + +static enum test_id cur_test_id; + +static bool app_event_handler(const struct app_event_header *aeh) +{ + if (is_test_start_event(aeh)) { + struct test_start_event *event = cast_test_start_event(aeh); + + cur_test_id = event->test_id; + + return false; + } + + if (is_data_event(aeh)) { + if (cur_test_id == TEST_DATA) { + struct data_event *event = cast_data_event(aeh); + static char descr[] = TEST_STRING; + + zassert_equal(event->val1, TEST_VAL1, "Wrong value"); + zassert_equal(event->val2, TEST_VAL2, "Wrong value"); + zassert_equal(event->val3, TEST_VAL3, "Wrong value"); + zassert_equal(event->val1u, TEST_VAL1U, "Wrong value"); + zassert_equal(event->val2u, TEST_VAL2U, "Wrong value"); + zassert_equal(event->val3u, TEST_VAL3U, "Wrong value"); + zassert_false(strcmp(event->descr, descr), + "Wrong string"); + + struct test_end_event *te = new_test_end_event(); + + zassert_not_null(te, "Failed to allocate event"); + te->test_id = TEST_DATA; + APP_EVENT_SUBMIT(te); + } + + return false; + } + + if (is_order_event(aeh)) { + if (cur_test_id == TEST_EVENT_ORDER) { + static int i; + struct order_event *event = cast_order_event(aeh); + + zassert_equal(event->val, i, "Incorrent event order"); + i++; + + if (i == TEST_EVENT_ORDER_CNT) { + struct test_end_event *te = new_test_end_event(); + + zassert_not_null(te, "Failed to allocate event"); + te->test_id = TEST_EVENT_ORDER; + APP_EVENT_SUBMIT(te); + } + } + + return false; + } + + zassert_true(false, "Event unhandled"); + + return false; +} + +APP_EVENT_LISTENER(MODULE, app_event_handler); +APP_EVENT_SUBSCRIBE(MODULE, data_event); +APP_EVENT_SUBSCRIBE(MODULE, order_event); +APP_EVENT_SUBSCRIBE(MODULE, test_start_event); diff --git a/tests/subsys/app_event_manager/src/modules/test_multicontext.c b/tests/subsys/app_event_manager/src/modules/test_multicontext.c new file mode 100644 index 0000000000000..6451d1d6c2d00 --- /dev/null +++ b/tests/subsys/app_event_manager/src/modules/test_multicontext.c @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include + +#include "test_multicontext_config.h" + +#define MODULE test_multictx +#define THREAD_STACK_SIZE 2448 + + +static void send_event(int a, bool sleep) +{ + struct multicontext_event *ev = new_multicontext_event(); + + zassert_not_null(ev, "Failed to allocate event"); + /* For every event both values should be the same - + * used to check if Application Event Manager sends proper values. + */ + ev->val1 = a; + if (sleep) { + k_sleep(K_MSEC(5)); + } + ev->val2 = a; + + APP_EVENT_SUBMIT(ev); +} + +static void timer_handler(struct k_timer *timer_id) +{ + send_event(SOURCE_ISR, false); +} + +static K_TIMER_DEFINE(test_timer, timer_handler, NULL); +static K_THREAD_STACK_DEFINE(thread_stack1, THREAD_STACK_SIZE); +static K_THREAD_STACK_DEFINE(thread_stack2, THREAD_STACK_SIZE); + +static struct k_thread thread1; +static struct k_thread thread2; +static enum test_id cur_test_id; + +static void thread1_fn(void) +{ + send_event(SOURCE_T1, true); +} + +static void thread2_fn(void) +{ + k_timer_start(&test_timer, K_MSEC(2), K_NO_WAIT); + send_event(SOURCE_T2, true); +} + +static void start_test(void) +{ + k_thread_create(&thread1, thread_stack1, + K_THREAD_STACK_SIZEOF(thread_stack1), + (k_thread_entry_t)thread1_fn, + NULL, NULL, NULL, + THREAD1_PRIORITY, 0, K_NO_WAIT); + + k_thread_create(&thread2, thread_stack2, + K_THREAD_STACK_SIZEOF(thread_stack2), + (k_thread_entry_t)thread2_fn, + NULL, NULL, NULL, + THREAD2_PRIORITY, 0, K_NO_WAIT); +} + + +static bool app_event_handler(const struct app_event_header *aeh) +{ + if (is_test_start_event(aeh)) { + struct test_start_event *st = cast_test_start_event(aeh); + + switch (st->test_id) { + case TEST_MULTICONTEXT: + { + cur_test_id = st->test_id; + start_test(); + + break; + } + + default: + /* Ignore other test cases, check if proper test_id. */ + zassert_true(st->test_id < TEST_CNT, + "test_id out of range"); + break; + } + + return false; + } + + zassert_true(false, "Event unhandled"); + + return false; +} + +APP_EVENT_LISTENER(MODULE, app_event_handler); +APP_EVENT_SUBSCRIBE(MODULE, test_start_event); diff --git a/tests/subsys/app_event_manager/src/modules/test_multicontext_config.h b/tests/subsys/app_event_manager/src/modules/test_multicontext_config.h new file mode 100644 index 0000000000000..6618f51ba2609 --- /dev/null +++ b/tests/subsys/app_event_manager/src/modules/test_multicontext_config.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* TEST_MULTICONTEXT */ + +#include + +#define THREAD1_PRIORITY K_PRIO_COOP(1) +#define THREAD2_PRIORITY K_PRIO_COOP(2) + +enum source_id { + SOURCE_T1, + SOURCE_T2, + SOURCE_ISR, + + SOURCE_CNT +}; diff --git a/tests/subsys/app_event_manager/src/modules/test_multicontext_handler.c b/tests/subsys/app_event_manager/src/modules/test_multicontext_handler.c new file mode 100644 index 0000000000000..d76abcc910ba4 --- /dev/null +++ b/tests/subsys/app_event_manager/src/modules/test_multicontext_handler.c @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include + +#include "test_multicontext_config.h" + +#define MODULE test_multictx_handler +#define THREAD_STACK_SIZE 400 + +static enum test_id cur_test_id; + +static void end_test(void) +{ + struct test_end_event *event = new_test_end_event(); + + zassert_not_null(event, "Failed to allocate event"); + event->test_id = cur_test_id; + + APP_EVENT_SUBMIT(event); +} + +static bool app_event_handler(const struct app_event_header *aeh) +{ + if (is_test_start_event(aeh)) { + struct test_start_event *st = cast_test_start_event(aeh); + + switch (st->test_id) { + case TEST_MULTICONTEXT: + { + cur_test_id = st->test_id; + + break; + } + + default: + /* Ignore other test cases, check if proper test_id. */ + zassert_true(st->test_id < TEST_CNT, + "test_id out of range"); + break; + } + + return false; + } + + if (is_multicontext_event(aeh)) { + if (cur_test_id == TEST_MULTICONTEXT) { + static bool isr_received; + static bool t1_received; + static bool t2_received; + + struct multicontext_event *ev = + cast_multicontext_event(aeh); + + zassert_equal(ev->val1, ev->val2, + "Invalid event data"); + + zassert_true(ev->val1 < SOURCE_CNT, + "Invalid source ID"); + + if (ev->val1 == SOURCE_T1) { + zassert_true(isr_received, + "Incorrect event order"); + t1_received = true; + } else if (ev->val1 == SOURCE_T2) { + zassert_true(isr_received, + "Incorrect event order"); + t2_received = true; + + } else if (ev->val1 == SOURCE_ISR) { + isr_received = true; + } + + if (isr_received && t1_received && t2_received) { + end_test(); + } + } + + return false; + } + + zassert_true(false, "Event unhandled"); + + return false; +} + +APP_EVENT_LISTENER(MODULE, app_event_handler); +APP_EVENT_SUBSCRIBE(MODULE, test_start_event); +APP_EVENT_SUBSCRIBE(MODULE, multicontext_event); diff --git a/tests/subsys/app_event_manager/src/modules/test_oom.c b/tests/subsys/app_event_manager/src/modules/test_oom.c new file mode 100644 index 0000000000000..897632d112827 --- /dev/null +++ b/tests/subsys/app_event_manager/src/modules/test_oom.c @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include +#include "test_oom.h" + +#define MODULE test_oom +#define TEST_EVENTS_CNT 150 + +static struct data_event *event_tab[TEST_EVENTS_CNT]; + +/* Custom reboot handler to check if OOM error is handled. */ +void oom_error_handler(void) +{ + int i = 0; + + /* Freeing memory to enable further testing. */ + while (event_tab[i] != NULL) { + k_free(event_tab[i]); + i++; + } + + ztest_test_pass(); + + while (true) { + ; + } +} + +void test_oom_reset(void) +{ + /* Sending large number of events to cause out of memory error. */ + int i; + + for (i = 0; i < ARRAY_SIZE(event_tab); i++) { + event_tab[i] = new_data_event(); + if (event_tab[i] == NULL) { + break; + } + } + + /* This shall only be executed if OOM is not triggered. */ + zassert_true(i < ARRAY_SIZE(event_tab), "No OOM detected, increase TEST_EVENTS_CNT"); + zassert_unreachable("OOM error not detected"); +} diff --git a/tests/subsys/app_event_manager/src/modules/test_oom.h b/tests/subsys/app_event_manager/src/modules/test_oom.h new file mode 100644 index 0000000000000..d78718eb53f47 --- /dev/null +++ b/tests/subsys/app_event_manager/src/modules/test_oom.h @@ -0,0 +1,7 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +void oom_error_handler(void); diff --git a/tests/subsys/app_event_manager/src/modules/test_subs.c b/tests/subsys/app_event_manager/src/modules/test_subs.c new file mode 100644 index 0000000000000..3bbbdd6054f43 --- /dev/null +++ b/tests/subsys/app_event_manager/src/modules/test_subs.c @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include + +#include "test_config.h" + + +static enum test_id cur_test_id; + +static int first_cnt; +static int early_cnt; +static int normal_cnt; + +static bool app_event_handler_first(const struct app_event_header *aeh) +{ + if (is_test_start_event(aeh)) { + struct test_start_event *event = cast_test_start_event(aeh); + + cur_test_id = event->test_id; + + return false; + } + + if (is_order_event(aeh)) { + if (cur_test_id == TEST_SUBSCRIBER_ORDER) { + first_cnt++; + } + + return false; + } + + zassert_true(false, "Event unhandled"); + return false; +} + +/* Create one first listener. */ +APP_EVENT_LISTENER(first, app_event_handler_first); +APP_EVENT_SUBSCRIBE_FIRST(first, order_event); +APP_EVENT_SUBSCRIBE_EARLY(first, test_start_event); + + +static bool app_event_handler_early(const struct app_event_header *aeh) +{ + if (is_order_event(aeh)) { + if (cur_test_id == TEST_SUBSCRIBER_ORDER) { + zassert_equal(first_cnt, 1, "Incorrect subscriber order" + " - early before first"); + early_cnt++; + } + + return false; + } + + zassert_true(false, "Event unhandled"); + return false; +} + +/* Create 3 early listeners. */ +APP_EVENT_LISTENER(early1, app_event_handler_early); +APP_EVENT_SUBSCRIBE_EARLY(early1, order_event); + +APP_EVENT_LISTENER(early2, app_event_handler_early); +APP_EVENT_SUBSCRIBE_EARLY(early2, order_event); + +APP_EVENT_LISTENER(early3, app_event_handler_early); +APP_EVENT_SUBSCRIBE_EARLY(early3, order_event); + + +static bool app_event_handler_normal(const struct app_event_header *aeh) +{ + if (is_order_event(aeh)) { + if (cur_test_id == TEST_SUBSCRIBER_ORDER) { + zassert_equal(first_cnt, 1, "Incorrect subscriber order" + " - normal before first"); + zassert_equal(early_cnt, 3, "Incorrect subscriber order" + " - normal before early"); + normal_cnt++; + } + + return false; + } + + zassert_true(false, "Wrong event type received"); + + return false; +} + +/* Create 3 normal listeners. */ +APP_EVENT_LISTENER(listener1, app_event_handler_normal); +APP_EVENT_SUBSCRIBE(listener1, order_event); + +APP_EVENT_LISTENER(listener2, app_event_handler_normal); +APP_EVENT_SUBSCRIBE(listener2, order_event); + +APP_EVENT_LISTENER(listener3, app_event_handler_normal); +APP_EVENT_SUBSCRIBE(listener3, order_event); + +static bool app_event_handler_final(const struct app_event_header *aeh) +{ + if (is_order_event(aeh)) { + if (cur_test_id == TEST_SUBSCRIBER_ORDER) { + zassert_equal(first_cnt, 1, "Incorrect subscriber order" + " - late before first"); + zassert_equal(early_cnt, 3, "Incorrect subscriber order" + " - late before early"); + zassert_equal(normal_cnt, 3, + "Incorrect subscriber order" + " - late before normal"); + + struct test_end_event *te = new_test_end_event(); + + zassert_not_null(te, "Failed to allocate event"); + te->test_id = cur_test_id; + APP_EVENT_SUBMIT(te); + } + + return false; + } + + zassert_true(false, "Wrong event type received"); + + return false; +} + +/* Create one final listener. */ +APP_EVENT_LISTENER(final, app_event_handler_final); +APP_EVENT_SUBSCRIBE_FINAL(final, order_event); diff --git a/tests/subsys/app_event_manager/src/utils/CMakeLists.txt b/tests/subsys/app_event_manager/src/utils/CMakeLists.txt new file mode 100644 index 0000000000000..9dd8ae54ab832 --- /dev/null +++ b/tests/subsys/app_event_manager/src/utils/CMakeLists.txt @@ -0,0 +1,7 @@ +# +# Copyright (c) 2022 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +target_sources(app PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/test_event_allocator.c) diff --git a/tests/subsys/app_event_manager/src/utils/test_event_allocator.c b/tests/subsys/app_event_manager/src/utils/test_event_allocator.c new file mode 100644 index 0000000000000..e50f90f8d88cd --- /dev/null +++ b/tests/subsys/app_event_manager/src/utils/test_event_allocator.c @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "test_oom.h" +#include + +void *app_event_manager_alloc(size_t size) +{ + void *event = k_malloc(size); + + if (unlikely(!event)) { + oom_error_handler(); + } + + return event; +} + +void app_event_manager_free(void *addr) +{ + k_free(addr); +} diff --git a/tests/subsys/app_event_manager/testcase.yaml b/tests/subsys/app_event_manager/testcase.yaml new file mode 100644 index 0000000000000..b0339592bb648 --- /dev/null +++ b/tests/subsys/app_event_manager/testcase.yaml @@ -0,0 +1,18 @@ +tests: + app_event_manager.core: + integration_platforms: + - nrf51dk_nrf51422 + - nrf52dk_nrf52832 + - nrf52840dk_nrf52840 + - nrf9160dk_nrf9160_ns + - qemu_cortex_m3 + tags: app_event_manager + app_event_manager.size_enabled: + extra_args: OVERLAY_CONFIG=overlay-event_size.conf + integration_platforms: + - nrf51dk_nrf51422 + - nrf52dk_nrf52832 + - nrf52840dk_nrf52840 + - nrf9160dk_nrf9160_ns + - qemu_cortex_m3 + tags: app_event_manager