How to architect an embedded system with multiple input and output capabilities. Some based on hardware, some on software settings

Viewed 43

I have an ESP8266 project programmed in the Arduino framework that gathers data from the network and then displays on a display. The device can be built with a few different display hardware types (eink, led, oled). These are set at compile time with #defines. However there are also a few different type of data and data transport mechanisms that can be used. Some require hardware (LoRa TX/RX) and are enabled at compile time but some can be changed at runtime based on user settings (eg. HTTP or MQTT).

I'm already using a factory design pattern to instantiate the Data transport object at runtime but still use compile time build flags to select which display hardware to use. I have a Display class, a Datasource class and a Config class. This has worked well but is now reaching its limit as I try to add Cellular functionality.

I wonder if there is a good design pattern / architecture design that will facilitate this kind of flexibility without having to keep adding more and more intrusive #ifdef statements all over my code.

Attached is a little mind map of the basic layout of possibilities of this device.enter image description here

1 Answers

If you want to make a decision what algorithn should be injected at runtime, then you can try to use Strategy pattern.

As wiki says about strategy pattern:

In computer programming, the strategy pattern (also known as the policy pattern) is a behavioral software design pattern that enables selecting an algorithm at runtime. Instead of implementing a single algorithm directly, code receives run-time instructions as to which in a family of algorithms to use

So you can read your config file and choose what object should be instantiated. For example, you have many displays:

public enum DisplayMark
{
    Samsung, Sony, Dell
}

and then yoy should create a base class Display:

public abstract class Display 
{
    public abstract string Show();
}

And then you need concrete implementations of this base class Display:

public class SamsungDisplay : Display
{
    public override string Show()
    {
        return "I am Samsung";
    }
}

public abstract class SonyDisplay : Display
{
    public override string Show()
    {
        return "I am Sony";
    }
}

public abstract class DellDisplay : Display
{
    public override string Show()
    {
        return "I am Dell";
    }
}

So far, so good. Now we need something like mapper which will be responsible to bring correct instance by selected display from config:

public class DisplayFactory
{
    public Dictionary<DisplayMark, Display> DisplayByMark { get; private set; } 
        = new Dictionary<DisplayMark, Display>
    {
        { DisplayMark.Sony, new SonyDisplay()},
        { DisplayMark.Samsung, new SamsungDisplay()},
        { DisplayMark.Dell, new DellDisplay()},
    };
}

and then when you will know what display should be used from config file, then you can get desired instance:

public void UseDisplay(DisplayMark displayMark) 
{
    DisplayFactory displayFactory = new DisplayFactory();
    Display display = displayFactory.DisplayByMark[displayMark];
    // Here you can use your desired display class
    display.Show();
}
Related