IoT - Gadgeteer - An Observering Display

In my previous blog I added a temperature and humidity sensor to the Gadgeteer project.
On every ‘TimerTick’ we asked the sensor to give us the current values and we wrote them to the Output Windows in Visual Studio.
In this blog we’re going to add a display and write the values to the display instead. We will also be implementing our first Design Pattern; The Observer Pattern.

Adding the display

As with the previous modules we can just select the Character Display module from the Toolbox and drag in onto the Program.Gadgeteer Designer Board.

If you’re not sure how to do this please read carefully the previous blog (Connecting a Sensor Module).

When you’ve dragged the Display Module on the Designer Board right click on the Display Module and select ‘Connect all modules’. This attaches and names the Display Module so we can use it in our code. Please make sure to attach the module as described by the program. In my case it asked to attach the Display Module to Socket 13.

Attach the display module to the mainboard (click on the image to enlarge)

So now we have a Power, Temperature and a Display module connected.

Getting the display to work

Writing data to the display is actually fairly simple. The following code below describes the process of displaying the Temperature Sensor Data.

1
2
characterDisplay.Clear();
characterDisplay.Print(measurement.Temperature.ToString("F1")+ " C");

The first line obviously clears the screen. The second line print either a single character or a complete line of text to the display. In this case I’ve not printed the entire Temperature measurement (as it uses 20 digits after the comma) but only a single digit after the comma.

The display is cursor based, meaning that after it wrote a piece of text the cursor remains at its last location. So when you do not use the Clear() function, the cursor continues where it left of.
So instead of ‘20,3 C’ being overwritten, it appends the data to the display resulting in ‘20,3 C20,4 C20,4 C ..’ etcetera.
You can either Clear() the screen or overwrite the data with the ‘CursorHome()’ function.

When using the ‘CursorHome()’ function, you have to make sure to have at least the same amount of digits, otherwise the data is partially overwritten resulting in something like ‘20 C C’.

Updating the data - Applying the Observer Pattern

It doesn’t make sense to update the display on every TimerTick (roughly every second) because the living room temperature will never change that quick. Actually I only want to update the display whenever something changes.
This screams ‘Observer Pattern’ to me.
The Observer Pattern is about subscription of members. A member (Observer) can subscribe to a Subject. When something changes on that subject the member is notified. So in our example, the Display is going to be a member which will be subscribed to a subject which keeps track of the measurements. Whenever the measurements change, the display is notified and it will update its values. This way the display does not have to update on every TimerTick.

First I created three interfaces.
ISubject, which keeps track of the Observers (members)

1
2
3
4
5
6
7
8
public interface ISubject
{
void RegisterObserver(IObserver observer);

void RemoveObserver(IObserver observer);

void NotifyObservers();
}

IObserver, which is the object that gets notified when something changes.

1
2
3
4
public interface IObserver
{
void Update(double temperature, double humidity);
}

IDisplayElement, which is the object that displays the data.

1
2
3
4
public interface IDisplayElement
{
void Display();
}

Then I created an implementation for each interface starting off with a new class called ‘MainData’ which implements the ISubject interface.
The MainData class contains a list of observers to which it’s sends updates.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class MainData : ISubject
{
private ArrayList _observers;
private double _temperature;
private double _humidity;

public MainData()
{

_observers = new ArrayList();
}

public void RegisterObserver(IObserver observer)
{

if (!_observers.Contains(observer))
_observers.Add(observer);
}

public void RemoveObserver(IObserver observer)
{

_observers.Remove(observer);
}

public void NotifyObservers()
{

foreach (IObserver observer in _observers)
{
observer.Update(_temperature, _humidity);
}
}
}

Next up I created the MainDisplay class which takes care of updating the display implementing the IObserver interface as also the IDisplayElement interface.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class MainDisplay : IObserver, IDisplayElement
{
public const string UniqueId = "95FD2509-984B-4F37-9294-39241D3A45B6";

private double _temperature;
private double _humidity;

private ISubject _subject;
private readonly Modules.CharacterDisplay _characterDisplay;

public MainDisplay(ISubject subject, Modules.CharacterDisplay characterDisplay)
{

_characterDisplay = characterDisplay;
_subject = subject;
_subject.RegisterObserver(this);
}

public void Update(double temperature, double humidity)
{

Debug.Print("Updating display");

_temperature = temperature;
_humidity = humidity;
Display();
}

public void Display()
{


}
}

After that I had to change the ProgramStarted event handler to initialize the MainData class and the MainDisplay observer.

1
2
3
4
5
6
7
8
9
10
private void ProgramStarted()
{

Debug.Print("Application startup");
_mainData = new MainData();

var display = new MainDisplay(_mainData, characterDisplay);

_mainTimer.Tick += OnMainTimerTick;
_mainTimer.Start();
}

We removed as much as possible from the OnMainTimerTick event handler to keep it as clean as possible. Resulting in the following code:

1
2
3
4
5
6
private void OnMainTimerTick(Timer timer)
{

base.PulseDebugLED();
var measurement = TakeMeasurement();
_mainData.SetMeasurements(measurement.Temperature, measurement.RelativeHumidity);
}

The TakeMeasurement() function is defined as such:

1
2
3
4
5
6
7
8
9
10
private Modules.TempHumidSI70.Measurement TakeMeasurement()
{
var measurementTime = DateTime.Now.ToUniversalTime();
var measurement = tempHumidSI70.TakeMeasurement();

Debug.Print(measurementTime + ": Temperature: " + measurement.Temperature + " °C");
Debug.Print(measurementTime + ": Humidity: " + measurement.RelativeHumidity + " %");

return measurement;
}

To make this work I had to add the ‘SetMeasurement’ function to the ‘MainData’ class.

1
2
3
4
5
6
7
public void SetMeasurements(double temperature, double humidity)
{

if (_temperature.ToString("F1") == temperature.ToString("F1") && _humidity.ToString("F1") == humidity.ToString("F1")) return;
_temperature = temperature;
_humidity = humidity;
MeasurementsChanged();
}

The SetMeasurement function calls the MeasurementsChanged function which in turn calls the ‘NotifyObservers’ function. Which ofcourse notifies all subsribed observers.

1
2
3
4
public void MeasurementsChanged()
{

NotifyObservers();
}

For the display to finally show data I modified the Display function on the MainDisplay class:

1
2
3
4
5
6
7
public void Display()
{
_characterDisplay.CursorHome();
_characterDisplay.Print(_temperature.ToString("F1") + " 'C");
_characterDisplay.Print(" "); // Four spaces to fill up the displays 16 characters.
_characterDisplay.Print(_humidity.ToString("F1") + " %");
}

I’m using ‘CursorHome’ here because the first line will always be completely overwritten (as you have 16 characters per line).
I notifed that the ‘Clear’ function on the DisplayModule lets the display sort of blink before writing the new data which seems a bit nervous to me.

The end result:
Drag the sensor module onto the workplace (click on the image to enlarge)

So this time a bit more code was involved but the application now uses the first Design Pattern - The Observer Pattern.