Embedded Programming Model | Observer Pattern

Hello everyone, I am the Mixed Content Master.

This time we share a commonly used concept/programming model in embedded systems—the Observer Pattern.

Overview of the Observer Pattern

The Observer Pattern is a behavioral design pattern that focuses on establishing a dynamic subscription-notification mechanism between objects.

It defines a one-to-many dependency between objects, so that when the state of one object (the subject, also known as the Subject) changes, all objects that depend on it (the observers) are notified and updated automatically.

In embedded systems, the Observer Pattern is widely used to decouple event publishers from subscribers, making it particularly suitable for the following application scenarios:

  • Handling sensor data updates
  • Changes in hardware status
  • Collaboration among multiple modules

Embedded Application Scenarios

1. Sensor Data Distribution

Multiple modules (such as display, storage, alarm) need to receive real-time updates of sensor data changes. The Observer Pattern can treat the sensor as the subject (Subject) and each module as an observer (Observer), achieving automatic notifications when data updates occur.

Class Diagram:

Embedded Programming Model | Observer Pattern

Subject Class (SensorSubject):

  • Contains a list of observers (observers array)

  • Maintains the current sensor value (sensor_value)

  • Provides two key methods: attach and set_value

Observer Interface (ObserverCallback):

  • Defines a unified update interface
  • Corresponds to function pointer types in the code

Concrete Observer Classes:

  • DisplayObserver: Handles display updates
  • LoggerObserver: Handles logging
  • AlarmObserver: Handles threshold alarms

Code:

#include<stdio.h>

#define OBSERVER_MAX_NUM  5

// Observer callback function type
typedef void(*ObserverCallback)(int value);

// Subject (Observed)
typedef struct
{
    ObserverCallback observers[OBSERVER_MAX_NUM];
    int count;
    int sensor_value;
} SensorSubject;

// Attach observer to subject
void sensor_attach(SensorSubject* subject, ObserverCallback callback)
{
    if (!subject || !callback) 
    {
        printf("Invalid parameters!\n");
        return;
    }

    if (subject->count >= OBSERVER_MAX_NUM) 
    {
        printf("Observers full!\n");
        return;
    }

    subject->observers[subject->count++] = callback;
}

// Update sensor value and notify observers
void sensor_set_value(SensorSubject* subject, int value)
{
    if (!subject) 
    {
        printf("Invalid parameters!\n");
        return;
    }

    subject->sensor_value = value;
    
    // Notify all observers
    for (int i = 0; i < subject->count; ++i) 
    {
        if (subject->observers[i]) 
        {
            subject->observers[i](subject->sensor_value);
        }
    }
}

// Observer 1: Display module
void display_update(int value)
{
    printf("[Display] Value: %d\n", value);
}

// Observer 2: Logger module
void logger_update(int value)
{
    printf("[Logger] Value: %d\n", value);
}

// Observer 3: Alarm module
void alarm_update(int value)
{
    if (value > 100) 
    {
        printf("[Alarm] Value %d exceeds limit!\n", value);
    }
}

int main(void)
{
    // Initialize sensor subject
    SensorSubject sensor = 
    {
        .observers = {0},
        .count = 0,
        .sensor_value = 0
    };

    // Register observers
    sensor_attach(&sensor, display_update);
    sensor_attach(&sensor, logger_update);
    sensor_attach(&sensor, alarm_update);

    // Simulate sensor data updates
    sensor_set_value(&sensor, 25);
    sensor_set_value(&sensor, 120);

    return 0;
}
Embedded Programming Model | Observer Pattern

This example allows objects (display, logging, alarm modules) to subscribe to another object (sensor), and when the subject’s state changes, all observers are automatically notified.

Note: This example is only to illustrate the basic idea of the Observer Pattern. It implements the core functionality of the Observer Pattern in a single-threaded environment. For practical applications, additional thread safety mechanisms, dynamic memory management, and more comprehensive error handling are needed.

2. Zephyr Sensor Subsystem

In Zephyr, the sensor subsystem uses a mechanism similar to the Observer Pattern. The sensor driver acts as the subject, and when sensor data is updated, it triggers the corresponding events.

Applications can register as observers to listen for these events and process them when data updates occur.

#include<zephyr.h>
#include<device.h>
#include<devicetree.h>
#include<drivers/sensor.h>

// Sensor event handling function, as the observer's update method
static void sensor_callback(const struct device *dev, struct sensor_trigger *trig)
{
    struct sensor_value temp;

    if (sensor_sample_fetch(dev) < 0) {
        return;
    }
    if (sensor_channel_get(dev, SENSOR_CHAN_AMBIENT_TEMP, &temp) < 0) {
        return;
    }
    // Process sensor data
    printk("Temperature: %d.%06d\n", temp.val1, temp.val2);
}

void main(void)
{
    const struct device *dev = device_get_binding(DT_LABEL(DT_INST(0, st_stts751)));
    if (dev == NULL) {
        return;
    }

    struct sensor_trigger trig = {
       .type = SENSOR_TRIG_DATA_READY,
       .chan = SENSOR_CHAN_AMBIENT_TEMP
    };

    // Register sensor event callback, equivalent to registering an observer
    if (sensor_trigger_set(dev, &trig, sensor_callback) < 0) {
        return;
    }

    while (1) {
        k_sleep(K_MSEC(100));
    }
}

Related articles: Will Zephyr Become the Leader of RTOS in the IoT Era?

3. Inter-task Communication and Synchronization Mechanisms

In RTOS, the communication and synchronization mechanisms between tasks can be likened to the Observer Pattern.

EventGroupHandle_t xEventGroup;

// Create event group
xEventGroup = xEventGroupCreate();

// Task 1 as the subject sets the event
void vTask1(void *pvParameters)
{
    while(1)
    {
        // Set event bits
        xEventGroupSetBits(xEventGroup, 0x01);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

// Task 2 as the observer waits for the event
void vTask2(void *pvParameters)
{
    EventBits_t uxBits;
    while(1)
    {
        // Wait for event bits
        uxBits = xEventGroupWaitBits(
            xEventGroup,   // Event group handle
            0x01,          // Event bits to wait for
            pdTRUE,        // Clear event bits on exit
            pdFALSE,       // Not all bits need to be set
            portMAX_DELAY  // Wait indefinitely
        );
        // Process event
        if ((uxBits & 0x01) != 0)
        {
            // Perform corresponding operations
        }
    }
}

The event group can be seen as a subject, while the tasks waiting for these events can be seen as observers.

When an event in the event group is set (state changes), the tasks waiting for that event will be awakened and execute corresponding operations, just like observers receiving notifications from the subject and updating accordingly.

Related articles: Embedded Event Flag Groups

4. MQTT

MQTT is a lightweight messaging protocol primarily used for communication between IoT devices, and it applies the principles of the Observer Pattern in its design and usage.

Its core concepts include:

  • Publisher: Generates messages and publishes them to specific topics.
  • Topic: A classification label for messages, used to distinguish different types of messages.
  • Broker: Responsible for receiving messages from publishers and forwarding them to subscribers who have subscribed to the corresponding topics.
  • Subscriber: Subscribes to one or more topics and receives messages forwarded by the broker when new messages are published to those topics.

Related articles: MQTT Applied to Inter-process Communication

That concludes this sharing session. If you find the article helpful, please share it. Thank you!

You might also like:

Embedded Programming Model | MVC Model

Step-by-step Guide to Building an Embedded Containerized Development Environment!

An Elegant Multi-functional Debugger for Embedded Systems!

A Very Lightweight Embedded Logging Library!

A Very Lightweight Embedded Thread Pool Library!

Popular C Language Projects on GitHub!

Practical | Teach You to Turn on Lights via Webpage in 10 Minutes

Essential Skills for Embedded Development | Git Submodules

Leave a Comment