A software design pattern is a reusable solution for a commonly occurring problem. It is not a finished solution, but rather a template for how to solve problems in a variety of different contexts. Using a design pattern saves a lot of time and helps to better understand, analyse, and organize the code to get the best solution.
In this post, we propose using the language of design patterns to describe some of the most common programing structures in the EV3 Software, and outline the following design patterns:
- Simple design pattern (sequential programing)
- General design pattern (loop programing)
- Parallel loop design pattern
- Master and slave design pattern
- State machine design pattern
Simple design pattern
One of the simplest ways we can program an EV3 robot is to drop different action blocks onto the sequence beam in the order, or sequence, we want them executed.
For example, to make a standard two-wheeled robot carry out a series of movements, we can drag out the required action blocks, arranging them as desired, from left to right. Each block is executed one at a time, all the way until the end of the program. We call this purely sequential approach the simple design pattern.
Note that the blocks are connected to each other by a data wire, known as the sequence beam, and that the sequence beam is normally hidden. Although we normally think of the blocks as being executed from left to right, it is in fact the sequence beam that determines the order of execution. To make the sequence beam visible, click the connector on the right hand of any block and the following block will move to the right.
It is quite natural to start designing programs sequentially. Most of the time, however, the events we need our robots to handle do not occur in an order that is known ahead of time. For example, what if some actions need to be repeated? What if different actions need to be taken under different specific conditions? What if the order that actions are taken depends on the current situation? What if we need to stop the program in a particular circumstance?
To implement his kind of behaviour, we need a more complex design pattern.
General design pattern
When we have to repeat a series of actions, we need a more complex structure than the sequential design pattern. In the general design pattern, the structure consists of some initial actions, a loop that contains the main program, and some closing actions. The main program of the loop may simply be a sequential program, but can be much more complex if required.
For example, let’s imagine a wheeled robot with a colour sensor that drives forward, beeping each time it drives over a green line, and stops at a red line. We do not know how many green lines the robot will encounter before the red line – or even if it will see any at all before reaching the red line. So how could we program the robot using a strictly sequential pattern? Unless we know exactly how many lines the challenge has and in what order they are placed, we cannot. If one line is changed or removed, the program will not work anymore. Instead, we can use the general design pattern.
We can of course perform many actions sequentially within each iteration of the loop, but we can also use nested switch blocks to establish an order of priority for a variety of actions, without having to know in what order they will occur when the program runs.
For example, what if we want to our robot to not only beep as it crosses each line, but also change direction each time it encounters an obstacle? We can nest the colour sensor switch inside a distance sensor switch or vice-versa, depending on which condition should be checked first.
What if we need to repeatedly run different actions at the same time but at different rates? It is not enough to split the sequence beam inside the main loop because the main loop will not repeat until the longest split sequence is finished! The solution is to run different loops in parallel.
Parallel loop design pattern
The parallel loop design pattern is a basic programming strategy that allows controlling simultaneously different independent tasks.
Building on the first version of the previous example, let’s say that, in addition to moving forward and beeping as it crosses each green line, we want the robot to activate its gripper whenever it detects an obstacle in its path. Clearly the gripper needs to run independently and at a different rate than the main program. The only way we can accomplish this is by running two parallel loops at the same time, one for the main program and another for the gripper.
Note: Care must be taken to avoid a race condition, which can occur when program blocks within multiple loops (or multiple branches) attempt to control the same resource at the same time. For example, if motor blocks in parallel loops attempt to control the same motor, the behaviour of the motor will depend on the timing of the loops. Race conditions often lead to unpredictable and undesirable results.
But now the problem is: how can we control, coordinate, and communicate between the different loops of the parallel loop design pattern? Can we use data wires? The answer is not at all, because data wires will not allow the loops to run in parallel. In fact, a block connected to a data wire will not execute until the block connected at the left of the wire is executed. This is why we use the sequence beam as a very special data wire that ensures the sequential execution of the program blocks. The answer is: we need to use variables.
Master and slave design pattern
The master and slave design pattern is useful when you have two or more processes running simultaneously but at different speeds that need some sort of coordination.
One loop acts as the master and the others as the slaves. The master loop controls all the slave loops and communicates with them using variables.
As an example, imagine we want to build a proximity sensor that beeps when an object is present. Furthermore, how could we implement this in such a way that the closer the object is, the faster the robot beeps? Clearly the beep repetition speed will be proportional to the obstacle proximity. In addition, the beeper proximity function will have to run in parallel with whatever else the robot is doing and at its own speed. If the distance is already being read within the main program loop (the master loop), one solution is to store this value in a variable, and then read the stored value in a separate loop (the slave loop) that handles the beeping.
If we analyze the two loops what can we say about their execution speeds?
Although reading a sensor takes some time, like in this example’s master loop, playing a sound and waiting to repeat the sound, such as in the slave loop, presumably takes much longer to execute. Because the slave loop runs slower than the master loop, we can expect that some distance values set by the master loop will be missed by the slave loop. Despite this, the beeps seem to be quite proportional to the obstacle distance anyway. In this situation, it is not crucial that the slave loop runs slower than the master.
For more examples of the master/slave design pattern, see these posts:
State machine design pattern
In the simple design pattern, blocks are executed sequentially. The sequence beam establishes the program blocks execution order, from left to right. Although sequential programing is one of the most common and often more obvious programing styles, it is not always the most efficient, flexible or understandable approach in the long run.
- What if we have to change the order that a sequence of actions is executed? For example, if the sensor events that trigger different actions is changed.
- What if we have to repeat one or more actions several times? For example, to repeat several turns to one side, or to go forward across several different color lines.
- What if we have to execute a sequence of code only when a specific condition is met? For example, if an obstacle is detected, drive around it, otherwise follow the line.
- What if we want to stop the execution of a program when a specific condition is met, without waiting until the rest of the sequence is finished?
How can we improve sequential programing to make it more functional, more compact, easier to understand, and easier to debug? The answer is: by using the state machine design pattern.
A state machine is a system that consists of a finite number of states and rules that determine how the system transitions from one state to another. In the EV3 software, this can be implemented by using a Switch block that contains a case for each state. Each case contains functionality code which consists of the code to be executed in the current state and a transition function which sets the next state.
Often programs will have an initialization state, followed by a default state – where, for example, the sensors are read to make decisions and to call other states or take different actions – and finally, it can have a closing state to carry out some actions before ending the program.
We’ll look at state machines in more depth in a future post, but for the time being, see this post:
This post was inspired by National Instruments’ LabVIEW Core 1 and Core 2 courses and is based on Unit 13 of my Teachers Introduction Course to LEGO Mindstorms NXT & EV3.