Event Manager

The Event Manager provides the infrastructure for implementing applications in an event-driven architecture. Instead of using direct function calls between modules, the modules can communicate and transfer data using events, which reduces the number of direct dependencies between modules.

The Event Manager processes all events and propagates them to the modules (listeners) that subscribe to an event type. Multiple modules can subscribe to the same event type at the same time. You can easily swap out listeners to, for example, create multiple configurations. A typical use case for this is to support different hardware configurations with one application.

Events are distinguished by event type. Listeners can process events differently based on their type. You can easily define custom event types for your application. Currently, up to 32 event types can be used in an application.

You can use the Profiler to observe the propagation of an event in the system, view the data connected with the event, or create statistics. Shell integration is available to display additional information and to dynamically enable or disable logging for given event types.

See the Event Manager sample for an example of how to use the Event Manager.

Configuration

Apart from standard configuration parameters, there are several required settings:

CONFIG_LINKER_ORPHAN_SECTION_PLACE

The Event Manager uses orphan memory sections. Set this option to suppress warnings and errors.

CONFIG_HEAP_MEM_POOL_SIZE

Events are dynamically allocated using heap memory. Set this option to enable dynamic memory allocation and configure a heap size that is suitable for your application.

CONFIG_REBOOT

If an out-of-memory error occurs when allocating an event, the system should reboot. Set this option to enable the sys_reboot API.

Call event_manager_init() during the application start to initialize the Event Manager.

Events

Events are used for communication between modules. Every event has a specified type. It can also contain additional data.

To submit an event of a given type (for example, sample_event), you must first allocate it by calling the function with the name new_event_type_name (for example, new_sample_event()). You can then write values to the data fields. Finally, use 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:

/* Allocate event. */
struct sample_event *event = new_sample_event();

/* Write data to datafields. */
event->value1 = value1;
event->value2 = value2;
event->value3 = value3;

/* Submit event. */
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:

/* 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. */
EVENT_SUBMIT(event);

For additional details on event types defining data with a variable size, see the Header file section.

After the event is submitted, the Event Manager adds it to the processing queue. When the event is processed, the Event Manager notifies all modules that subscribe to this event type.

Warning

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.

Implementing an event type

The Event Manager provides macros to easily create and implement custom event types. For each event type, create a header file and a source file.

Note

Currently, up to 32 event types can be used in an application.

Header file

The header file must include the Event Manager header file (#include event_manager.h). To define the new event type, create a structure for it that contains event_header header as the first field and, optionally, additional custom data fields. Finally, declare the event type with the 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 sample_event:

#include "event_manager.h"

struct sample_event {
        struct event_header header;

        /* Custom data fields. */
        int8_t value1;
        int16_t value2;
        int32_t value3;
};

EVENT_TYPE_DECLARE(sample_event);

In some use cases, the length of the data associated with an event may vary. You can use the EVENT_TYPE_DYNDATA_DECLARE macro instead of EVENT_TYPE_DECLARE to declare an event type with variable data size. The data with variable size must be added as the last member of the event structure. For example, you can add the variable size data to a previously defined event by applying the following change to the code:

#include "event_manager.h"

struct sample_event {
        struct event_header header;

        /* Custom data fields. */
        int8_t value1;
        int16_t value2;
        int32_t value3;
        struct event_dyndata dyndata;
};

EVENT_TYPE_DYNDATA_DECLARE(sample_event);

The event_dyndata contains:

Source file

The source file must include the header file for the new event type. Define the event type with the EVENT_TYPE_DEFINE macro, passing the name of the event type as declared in the header and the 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 following code example shows a source file for the event type sample_event:

#include "sample_event.h"

static int log_sample_event(const struct event_header *eh, char *buf,
                            size_t buf_len)
{
        struct sample_event *event = cast_sample_event(eh);

        return snprintf(buf, buf_len, "val1=%d val2=%d val3=%d", event->value1,
                event->value2, event->value3);
}

EVENT_TYPE_DEFINE(sample_event,         /* Unique event name. */
                  true,                 /* Event logged by default. */
                  log_sample_event,     /* Function logging event data. */
                  NULL);                /* No event info provided. */

Register a module as listener

If you want a module to receive events managed by the Event Manager, you must register it as a listener and you must subscribe it to a given event type. Every listener is identified by a unique name.

To turn a module into a listener for specific event types:

  1. Include the header files for the respective event types, for example, #include "sample_event.h".

  2. Implement an Event handler function and define the module as a listener with the EVENT_LISTENER macro, passing both the name of the module and the event handler function as arguments.

  3. Subscribe the listener to specific event types.

For subscribing to an event type, the Event Manager provides three types of subscriptions, differing in priority. They can be registered with the following macros:

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 EVENT_LISTENER.

Event handler function

The event handler function is called when any of the subscribed event types are being processed. Note that only one event handler function can be registered for a 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 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 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 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 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:

#include "sample_event.h"

static bool event_handler(const struct event_header *eh)
{
        if (is_sample_event(eh)) {

                /* Accessing event data. */
                struct sample_event *event = cast_sample_event(eh);

                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;
}

EVENT_LISTENER(sample_module, event_handler);
EVENT_SUBSCRIBE(sample_module, sample_event);

All the events, both the ones with and the ones without the variable size data, are handled by an application module in the same way. The variable size data is accessed in the same way as the other members of the structure defining an event.

Profiling an event

Event Manager events can be profiled (see Profiler). To profile a given Event Manager event, you must define an event_info structure, using EVENT_INFO_DEFINE, and provide it as an argument when defining the event type. This structure contains a profiling function and information about the data fields that are logged.

The profiling function should log the event data to a given buffer by calling profiler_log_encode_u32(), regardless of the profiled data type.

The following code example shows a profiling function for the event type sample_event:

static void profile_sample_event(struct log_event_buf *buf,
                                 const struct event_header *eh)
{
        struct sample_event *event = cast_sample_event(eh);

        profiler_log_encode_u32(buf, event->value1);
        profiler_log_encode_u32(buf, event->value2);
        profiler_log_encode_u32(buf, event->value3);
}

The following code example shows how to define the event profiling information structure and add it to event type definition:

EVENT_INFO_DEFINE(sample_event,
                  /* Profiled datafield types. */
                  ENCODE(PROFILER_ARG_S8, PROFILER_ARG_S16, PROFILER_ARG_S32),
                  /* Profiled data field names - displayed by profiler. */
                  ENCODE("value1", "value2", "value3"),
                  /* Function used to profile event data. */
                  profile_sample_event);

EVENT_TYPE_DEFINE(sample_event,
                  true,
                  log_sample_event,     /* Function for logging event data. */
                  &sample_event_info);  /* Structure with data for profiling. */

Note

By default, all Event Manager events that are defined with an event_info argument are profiled.

Shell integration

The Event Manager is integrated with Zephyr’s Shell module. When the shell is turned on, an additional subcommand set (event_manager) is added.

This subcommand set contains the following commands:

show_listeners

Show all registered listeners.

show_subscribers

Show all registered subscribers.

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.

enable or 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 show_events, as arguments.

API documentation

Header file: include/event_manager.h
Source files: subsys/event_manager/
group event_manager

Event Manager.

Defines

SUBS_PRIO_MIN

Index of the highest subscriber priority level.

SUBS_PRIO_MAX

Index of the lowest subscriber priority level.

SUBS_PRIO_COUNT

Number of subscriber priority levels.

EVENT_LISTENER(lname, cb_fn)

Create an event listener object.

Parameters
  • lname: Module name.

  • cb_fn: Pointer to the event handler function.

EVENT_SUBSCRIBE_EARLY(lname, ename)

Subscribe a listener to the early notification list for an event type.

Parameters
  • lname: Name of the listener.

  • ename: Name of the event.

EVENT_SUBSCRIBE(lname, ename)

Subscribe a listener to the normal notification list for an event type.

Parameters
  • lname: Name of the listener.

  • ename: Name of the event.

EVENT_SUBSCRIBE_FINAL(lname, ename)

Subscribe a listener to an event type as final module that is being notified.

Parameters
  • lname: Name of the listener.

  • ename: Name of the event.

ENCODE(...)

Encode event data types or labels.

Parameters
  • ...: Data types or labels to be encoded.

EVENT_INFO_DEFINE(ename, types, labels, profile_func)

Define event profiling information.

This macro provides definitions required for an event to be profiled.

Note

Types and labels of the profiled values should be wrapped with the ENCODE macro.

Parameters
  • ename: Name of the event.

  • types: Types of values to profile (represented as profiler_arg).

  • labels: Labels of values to profile.

  • profile_func: Function used to profile event data.

EVENT_TYPE_DECLARE(ename)

Declare an event type.

This macro provides declarations required for an event to be used by other modules.

Parameters
  • ename: Name of the event.

EVENT_TYPE_DYNDATA_DECLARE(ename)

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.

Parameters
  • ename: Name of the event.

EVENT_TYPE_DEFINE(ename, init_log_en, log_fn, ev_info_struct)

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 ename (for example, button_event):

  • new_event_type - Allocates an event of a given type.

  • is_event_type - Checks if the event header that is provided as argument represents the given event type.

  • cast_event_type - Casts the event header that is provided as argument to an event of the given type.

Parameters
  • ename: Name of the event.

  • init_log_en: Bool indicating if the event is logged by default.

  • log_fn: Function to stringify an event of this type.

  • ev_info_struct: Data structure describing the event type.

ASSERT_EVENT_ID(id)

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.

Parameters
  • id: ID.

EVENT_SUBMIT(event)

Submit an event.

This helper macro simplifies the event submission.

Parameters
  • event: Pointer to the event object.

Functions

int event_manager_init(void)

Initialize the Event Manager.

Return Value
  • 0: If the operation was successful.

struct event_header
#include <event_manager.h>

Event header.

When defining an event structure, the event header must be placed as the first field.

Public Members

sys_snode_t node

Linked list node used to chain events.

const struct event_type *type_id

Pointer to the event type object.

struct event_dyndata
#include <event_manager.h>

Dynamic event data.

When defining an event structure, the dynamic event data must be placed as the last field.

Public Members

size_t size

Size of the dynamic data.

uint8_t data[0]

Dynamic data.

struct event_listener
#include <event_manager.h>

Event listener.

All event listeners must be defined using EVENT_LISTENER.

Public Members

const char *name

Name of this listener.

bool (*notification)(const struct event_header *eh)

Pointer to the function that is called when an event is handled.

struct event_subscriber
#include <event_manager.h>

Event subscriber.

Public Members

const struct event_listener *listener

Pointer to the listener.

struct event_info
#include <event_manager.h>

Event description for profiling or logging.

Public Members

void (*profile_fn)(struct log_event_buf *buf, const struct event_header *eh)

Function for profiling this event.

const uint8_t log_arg_cnt

Number of logged data fields.

const char **log_arg_labels

Labels of logged data fields.

enum profiler_arg *log_arg_types

Types of logged data fields.

struct event_type
#include <event_manager.h>

Event type.

Public Members

const char *name

Event name.

const struct event_subscriber *subs_start[(_SUBS_PRIO_FINAL - _SUBS_PRIO_FIRST + 1)]

Array of pointers to the array of subscribers.

const struct event_subscriber *subs_stop[(_SUBS_PRIO_FINAL - _SUBS_PRIO_FIRST + 1)]

Array of pointers to the element directly after the array of subscribers.

bool init_log_enable

Bool indicating if the event is logged by default.

int (*log_event)(const struct event_header *eh, char *buf, size_t buf_len)

Function to log data from this event.

const struct event_info *ev_info

Logging and formatting information.