Designing a Traffic Light System with Automatons

Using automatons (State Machines) to design a Traffic Light System.

In this illustrative example, we design a Traffic Light System, employing the efficient method of Finite State Machines (FSM), often referred to as “automatons”. For more information about automatons, see Create Automatons.

An automaton consists of several key elements:

  • States: The system contains diverse states, including a starting state that corresponds to the starting point of the execution.
  • Actions: Each state can contain one or several actions to perform, corresponding to the system behavior.
  • Transitions: Transitions between states implement the flow of the system. These transitions are characterized by a priority and can include optional triggering conditions to refine the logic of the system.

A First Very Basic Traffic Light Automaton

The most simplistic model of a Traffic Light is an automaton with 3 states (Green, Yellow, and Red) and three transitions without condition: Green to Yellow, Yellow to Red, and Red to Green.



Important: Automatons need to be defined inside “node” operators as they need memory to store the current state; if they are inside a “function” operator, an error is raised.
The flow of this automaton during simulation is the following:
  1. Green (initial state, marked by the little arrow symbol)
  2. Yellow
  3. Red
  4. Back to step 1 and so on

An improvement to this model involves storing the current light color in the operator’s output. To achieve this, we use an enumerate Color and define the corresponding value to be the output light in each state.

We create the following type for the color:
type Color = enum { Red, Yellow, Green, Off, BlinkingRed }
The model has the following signature and implementation:
node SimpleModelWithEnum ()
  returns (light : Color;)


Note: In the Scade One tool, a state of an automaton is a scope and can contain:
  • dedicated local variable(s),
  • any model that could be implemented in the main scope of the operator, including block diagrams or even other state machines.

Crossroad System with Timer for Triggering Transitions

For an accurate model, we need to add two important features:

  1. A timer to control the time between the different states.
  2. An additional state to represent a Traffic Light System at a crossroad (rather than just a single light).
To implement a timer, we can benefit from the reset of state memory each time we change a state. Therefore, we can define a variable countdown starting with a given value until it reaches 0 as follows:
(<startValue> pre countdown) - 1

Once countdown reaches 0, we can transition to a new state and start again. This is done by setting the guard value (that is, the condition) to countdown = 0.

The resulting model is the following (with 3 input values for setting different times and 3 local variables countdownY, countdownG, countdownR, local to each state):
node SimpleModelWithTimer (timeY: int32 default = 5;
                           timeG: int32 default = 20;
                           timeR: int32 default = 10;)
  returns (light : Color;)




As a timer is a common transition condition, this check is available via the operator Times provided by the Scade One library Flows: it returns a given Boolean value after a specific number of cycles.
node #pragma cg expand #end Times (count: 't ; in: bool) returns (out: bool) where 't signed

The model can be updated by replacing the computation of the condition purely on the transition and making it much simpler:



Crossroad System with Four Directions

Now , let us make some minor adjustments to our model to depict a crossroad with two perpendicular roads, one running North-South (NS) and the other running East-West (EW), featuring four signal phases:
Table 1.
PhaseNorth-South (NS)East-West (EW)
1GreenRed
2YellowRed
3RedGreen
4RedYellow


The adjusted model has 3 inputs (2 for green and 1 for yellow), two outputs (the color of each light), and 4 states:

node CrossroadModelWithTimer (yellowTimer: int32 default = 5;
                              greenTimerNS: int32 default = 20;
                              greenTimerEW: int32 default = 10;)
  returns (lightNS : Color;
           lightWE : Color;)


Adding an Error State

Beyond the standard trio of colors (red, yellow, and green), a traffic light may incorporate a blinking red signal to convey distinct messages. This dynamic feature is essential in various scenarios such as malfunctions, maintenance routines, intersection faults etc.

We need therefore to modify our enumerate for color to add a blinking red and a “Off” (that is, no light):

type color = enum { Red, Yellow, Green, Off, BlinkingRed };

To add an error state in our model, we need to add a transition from each state to the error state, with a priority higher than standard transitions to be sure to override any possible transition.

In addition, we can add a true initial state with all lights red when we reset our system from this error state.

Our operator becomes as follows:

node CrossroadModelWithErrorModeV1 (yellowTimer: int32 default = 5;
                                    greenTimerNS: int32 default = 20;
                                    greenTimerEW: int32 default = 10;
                                    systemError : bool default=false;
                                    systemReset : bool default=false;)
  returns (lightNS : Color;
           lightWE : Color;)


Note: The numbers ① and ② correspond to the priority of the transitions (with ① being a higher priority than ②).

When introducing an error state in our design, several drawbacks become apparent:

  1. The total number of transitions is doubled due to the addition of error-handling transitions.
  2. We must manually prioritize the standard transitions by decreasing their priority levels to ensure that error transitions take precedence when necessary.
  3. The diagram readability suffers, making it more challenging to discern the system's underlying logic.

Instead of adding an error state directly, we can utilize a more effective design approach called Hierarchical Automatons. In this scenario, we create an outer automaton with two states: "SystemOperational" and "SystemError". Inside the operational state, we implement the four traffic signal states (and their corresponding transitions) and an initial state (when the system is reset).

By embracing hierarchical automatons, we create a more efficient, more organized, more maintainable, and easier-to-understand design for handling errors in our traffic signal system.

The previous model improved with hierarchical automatons is then as follows:



Debugging

One great advantage of using automatons is the ability to have a clear visualization to easily identify problems in the model.

In particular, the debugging feature of Scade One enhances the visualization and allows to:

  • Trace the sequence of states and easily identify where the system deviates from the expected flow
  • Isolate errors to a specific state or transition
  • Provide a dynamic visualization of the execution
Here is an example of the debugging of our first version of traffic light with timer (using a countdown):

To improve the visualization of the model and add an interaction with the model during debug, we can use a graphical panel created with Ansys SCADE Rapid Prototyper using the Scade One Co-simulation generator configuration.

We can use this graphical panel as a Scade One operator:

node #pragma harness data_source="../resources/TrafficLightSystem.spanel" #end TrafficLightSystem(
  switchLightNS: int32;
  switchLightEW: int32
) returns (
  emitSystemError: bool;
  emitSystemReset: bool
);

To use this operator, we need to convert the enumerate representing the traffic color (including “flashing red”) to a flow of integers that trigger the change of colors. One way to do this is the following implementation (using a succession of “red” and “no color” to model the flashing red):



Moreover, the outputs of the graphical panel (system failure and system reset) can only be used at the next cycle, and therefore we need to use the pre operator before using them as inputs of the Traffic Light System.

The resulting debug test harness looks like this:



Note: To choose times for each light in seconds, we need to use the clock of debug (200 ms/cycle by default) to compute the number of cycles to run for each light.

Once the debug is run, the graphical panel appears along the debug.