User Interface Design Part 4: The Model-View-Controller paradigm

Thus far we’ve talked about designing a user interface: the visual language that tells users what they can do, the elements of the language (nouns and verbs), the way these things contribute to consistency and discoverability and how they can be used to create a user interface that seems… inevitable. Simple. Invisible.

Where this attention to detail shines is when it comes to building our interface.

But before we can do this, let’s talk a little bit about a common way used to organize interfaces, and discuss the non-interface elements of our code.

The single most common used design pattern in application development today is the Model-View-Controller. In fact, it has been called “the seminal insight of the whole field of graphical user interfaces.” Originally invented at Xerox Parc in the 1970’s, it was first used to help build Smalltalk-80 user interface applications, and it was first described in the paper A Cookbook for Using the Model-View-Controller User Interface Paradigm in Smalltalk-80 by Glenn Krasner and Stephen Pope.

This paradigm is so important that nearly every modern application API (from iOS to Android to MacOS to Microsoft Windows) either explicitly provides interfaces that build on this model, or implicitly provides a way by which programmers can use this model.

The key idea is that we can think of our programs as being made up of three major parts:

Model View Controller

At the bottom is our “Model.” This is the “domain-specific software” which implements our application–and is the collection of code which does stuff like load a file, or provide in-memory editing of a text file, or (in our case) connects to the physical hardware that controls an HVAC system or which provides the time.

Our user interface is then built on top of this model code.

Our user interface roughly comprises of two parts. The first part are the “views”; these deal with everything graphical. They grab data from the model and display the contents of the screen–and they can contain “subviews” and be contained in “superviews.”

The second part is the “control” code. Controllers contain the logic which coordinates our views; they deal with user interface interactions and translate those actions into meaningful action. (So, for example, a view may represent a button, but it is the control code which determines what pressing that button “means” in terms of updating the interface and updating the model.)

For our Arduino application we don’t fully implement “views,” since some of the overhead may not quite fit in our small memory footprint. But they can help us segregate our code and help organize our thinking about the code, by thinking of the graphical parts as being separate from the control code and from our model code.

And notice one nice property about our visual language–about our thinking of a visual language in terms of the ‘nouns’ and ‘verbs’ and the consistent use of visual designs. All of this maps very nicely on our Model-View-Controller paradigm.

Our “Model” is the concrete implementation of our “nouns”: the thermostat settings. The schedule. The current time.

Our “Views” represents our visual language: how we show the user our “nouns”, our “verbs”, how we represent the actions a user can take.

And our “Control” code represents how our visual language works: how a sequence of taps on a bunch of icons translates into the user “changing the time” or “turning down the heater.”

Let’s give a concrete example of one of these “nouns”; the actual thermostat control code which determines if we need to turn on or off the heater, air conditioner or fan.

Our AdaThermostat, declared as a single GThermostat object, defines the actual code for setting the temperature, for getting the current temperature, and for turning on and off the three control lines that control our HVAC unit.

class AdaThermostat

        void            periodicUpdate();

        uint8_t         heatSetting;        /* Target heat to temperature */
        uint8_t         coolSetting;        /* Target cool to temperature */
        uint8_t         fanSetting;         /* ADAVAC_OFF/AUTO/ON */
        uint8_t         curTemperature;     /* Current interior temperature */
        uint8_t         lastSet;            /* Last schedule used or 0xFF */

        bool            heat;               /* True if heater runs */
        bool            cool;               /* True if aircon runs */
        bool            fan;                /* True if fan runs */

Ultimately, when you change the thermostat setting–say, to heat the room to 70°–at the very core of our software, this sets GThermostat.heatSetting to 70.

Our class has only one entry point, periodicUpdate, which must be called periodically in order to run our logic: to periodically read the temperature sensor and set the curTemperature variable, and to decide when to turn on the heater and the air conditioner.

All of our model code looks like this: a loose collection of classes which control the hardware on our thermostat.

For example, our model includes code for getting and setting the time in AdaTime.h:

extern void AdaTimeInitialize();

extern void AdaSetTime(uint32_t t);     // Time (seconds elapsed since 1/1/2017)
extern uint32_t AdaGetTime();           // Time (seconds elapsed since 1/1/2017)
extern uint32_t AdaGetElapsedTime();    // Time (seconds elapsed since power on)

This uses the TIMER2 hardware module on the ATmega 328 CPU. This sets the timer to create an interrupt every 1/125th of a second, and the interrupt itself counts until 1 full second has elapsed before updating two global variables–one with the current time (measured as seconds from January 1, 2017), and the other with the number of seconds since the device was turned on.

Our code also provides support routines for converting the time to a Gregorian calendar date and for quickly finding the day of the week. This allows us to determine quickly which day of the week it is when we update the thermostat according to the schedule.

We also provide a class for getting and setting the thermostat’s schedule, which is stored in EEPROM on the ATmega 328.

class AdaSchedule

        void            periodicUpdate();

        void            setCurSchedule(uint8_t value);
        uint8_t         getCurSchedule();

        AdaScheduleDay  getSchedule(uint8_t schedule, uint8_t dow);
        void            setSchedule(uint8_t schedule, uint8_t dow, const AdaScheduleDay &item);

This allows us to get the schedule for a single day, which is an array of four temperature settings and four times:

struct AdaScheduleItem
    uint8_t hour;           /* 0-23 = hour of day, 0xFF = skip */
    uint8_t minute;         /* 0-59 = minute */
    uint8_t heat;           /* temperature to heat to */
    uint8_t cool;           /* temperature to cool to */

struct AdaScheduleDay
    struct AdaScheduleItem setting[4];  // 4 settings per day

Our user interface code for running the main display of our thermostat then makes use of these model classes to get the current state of the system.

For example, in the part of our home page which displays the temperature buttons, our code is:

    GC.drawButton(RECT(160,200,40,37),buffer,28,KCornerUL | KCornerLL,KCenterAlign);
    GC.drawButton(RECT(201,200,40,37),buffer,28,KCornerUR | KCornerLR,KCenterAlign);

This will read and display the temperature on our device.

By using the Model-View-Controller paradigm and separating out the parts which control our hardware from the parts that control our display, and make creating our user interface code that much easier. It also means we can test those parts of our code that controls our hardware separately from the rest of our application.

The complete source code, which strips out most of our user interface software and illustrates the models of our thermostat software, is checked into GitHub and is available for download.

Next time we’ll discuss the code we use for managing screens and the controller code which draws our home screen.

Published by

William Woody

I'm a software developer who has been writing code for over 30 years in everything from mobile to embedded to client/server. Now I tinker with stuff and occasionally help out someone with their startup.

Leave a Reply

Please log in using one of these methods to post your comment: Logo

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

Facebook photo

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

Connecting to %s