The Observer Pattern (also known as Publish-Subscribe pattern) provides notification to a set of interested clients that relevant data have changed. The data-server does not need to have any a priori knowledge about its clients. Most commonly data are sent whenever new data arrive, but clients can also be updated periodically.

It is a special case of the client-server style. In this blog I show a simple implementation of this pattern using the sleep/wake-up synchronization mechanisms designed for the kernel that has been presented. The goal is to use the machinery provided by the kernel – namely the multitasking and synchronization – to implement this very common pattern.
The publisher server does not know anything about the clients; the clients themselves will “subscribe” by going to sleep while new data do not arrive. The code below shows the SensorSet and SensorGet functions using the kernel calls to wake-up all listeners when new data is sensed, and to sleep while no new data arrive, respectively.
Let’s implement what is depicted on Figure 1.
/*
@file sensors.h
*/
#ifndef SENSORS_H_
#define SENSORS_H_
typedef struct Sensor Sensor_t;
/*Sensor number*/
#define SPEED 1
#define TEMP 2
#define FLOW 3
struct Sensor
{
int sensorData;
};
int SensorInit(Sensor_t* sensor);
void SensorSet(Sensor_t* sensor, int data);
int SensorGet(Sensor_t* sensor);
#endif /* SENSORS_H_ */
/*
@file sensors.c
*/
#include "kernel.h"
#include "sensors.h"
int SensorInit(Sensor_t* sensor)
{
if (sensor != NULL)
{
sensor->sensorData = 0;
return OK;
}
return NOK;
}
void SensorSet(Sensor_t* sensor, int data)
{
if (sensor != NULL)
{
sensor->sensorData = data;
/*wake-up all clients*/
kCallWake(&sensor->sensorData);
}
}
int SensorGet(Sensor_t* sensor)
{
if (sensor != NULL)
{
/*sleep for new data*/
kCallSleep(&sensor->sensorData);
return sensor->sensorData;
}
return NOK;
}
To emulate data changes on a sensor, I will send data via keyboard through UART, following a simple APDU: [SensorNumber, NewValue, Return].
/*
@file tasks.c
this usage example shows an observer pattern for sensors
using sleep/wake up synch mechanisms
*/
#include <stdio.h>
#include <assert.h>
#include "kernel.h"
#include "tasks.h"
#include "sensors.h"
SEMA_t rcvd; /*sema to signal publisher server*/
volatile char uartbuffer[3]; /*apdu buffer*/
volatile int norb = 0; /*n of rcvd bytes*/
volatile char rcvdbyte; /*rcvd byte*/
Sensor_t SpeedSensor; /*speed sensor*/
/*get data changes via uart
Apdu={sensor type, new value, '\r'}
*/
ISR(UART_Handler)
{
if((uart_get_status((Uart*)USART_SERIAL) & UART_SR_RXRDY) == UART_SR_RXRDY)
{
uart_read((Uart*)USART_SERIAL, &rcvdbyte);
uartbuffer[norb] = rcvdbyte;
norb++;
if (rcvdbyte == '\r')
{
kSemaSignal(&rcvd); /* signal publisher */
norb=0;
}
}
}
/*publisher server*/
/*highest priority task*/
void PublisherServer(void* args)
{
assert(SensorInit(&SpeedSensor) == OK);
kCall2(SEMAINIT, &rcvd, 0); /*kcall to init sema*/
while(1)
{
WAIT(&rcvd);
/*wake subscribers*/
if (uartbuffer[0] == SPEED)
{
printf("Speed sensor changed\n\r");
SensorSet(&SpeedSensor, (int)uartbuffer[1]);
}
else
{
printf("No route\n\r");
}
}
}
/* windshield client*/
void WindshieldClient(void* args)
{
int value;
while(1)
{
/*Sleep until new data*/
value = SensorGet(&SpeedSensor);
printf("Windshield notified. Speed: %d\n\r", value);
/*do some processing*/
}
}
/* radio volume client */
void RadioClient(void* args)
{
int value;
while(1)
{
value = SensorGet(&SpeedSensor);
printf("Radio notified. Speed: %d\n\r", value);
/*do some processing*/
}
}
/*lane departure client*/
void LaneDepartureClient(void* args)
{
int value;
while(1)
{
value = SensorGet(&SpeedSensor);
printf("Lane departure notified. Speed: %d\n\r", value);
/*do some processing*/
}
}
Note that the SensorGet methods called on the client tasks are not polling or busy-waiting the sensors. They put the tasks to sleep, so they wake up when new data arrive.
The figure below shows the outputs on the PC screen.
