[blog] Observer Pattern using Sleep/Wake synchronization mechanism

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.

Figure 1. The Observer Pattern on an automotive software. Speed changes are pushed to a set of observer clients. [Automotive Software Architectures, Staron 2017]

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.

Figure 2. The implementation running

Author: Antonio Giacomelli de Oliveira

Embedded Systems Engineer

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: