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 @@
+
+
+
+
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