On this page

TaskTree Traffic Light

The TaskTree Traffic Light example demonstrates how to implement state machine behavior using TaskTree through a traffic light simulation. The example is an alternative implementation for the Traffic Light examples in Qt SCXML and Qt State Machine.

Traffic light simulation aplication.

The application consists of three main components:

  • A TrafficLight widget that displays the current state.
  • A TaskTree recipe that controls state transitions.
  • A GlueInterface that synchronizes the UI with the state machine.

The following sections describe more details of the example's code.

Helper Glue Class

The GlueInterface class provides a clean separation between the UI and operational logic.

enum class Light
{
    Off    = 0,
    Red    = 1 << 0,
    Yellow = 1 << 1,
    Green  = 1 << 2,
};

Q_DECLARE_FLAGS(Lights, Light)
Q_DECLARE_OPERATORS_FOR_FLAGS(Lights)

class GlueInterface final : public QObject
{
    Q_OBJECT

public:
    // operational logic -> GUI
    void setLights(Lights lights) { emit lightsChanged(lights); }

    // GUI -> operational logic
    void smash() { emit smashed(); }
    void repair() { emit repaired(); }

signals:
    void lightsChanged(Lights lights);

    void smashed();
    void repaired();
};

The GlueInterface class provides the following interface methods:

MethodPurpose
setLights()Updates traffic light state from TaskTree recipe
lightsChanged()Notifies UI when light states change
smash()/ repair()Handles fault simulation and recovery actions

When users interact with the UI:

  • Clicking pause triggers smash(), simulating a fault.
  • Clicking play triggers repair(), restoring normal operation.

Implementing the state machine

The state machine logic is implemented through a TaskTree recipe. The recipe() method creates this logic using a reference to GlueInterface:

ExecutableItem recipe(GlueInterface &iface)
{
    return Forever {
        Group { // "working" state
            stopOnSuccess,
            parallel,
            signalAwaiterTask(&iface, &GlueInterface::smashed), // transitions to the "broken" state
            Forever {
                switchLightsTask(&iface, Light::Red, 3s), // "red" state
                switchLightsTask(&iface, Light::Red | Light::Yellow, 1s), // "redGoingGreen" state
                switchLightsTask(&iface, Light::Green, 3s), // "green" state
                switchLightsTask(&iface, Light::Yellow, 1s), // "greenGoingRed" state
            }
        },
        Group { // "broken" state
            stopOnSuccess,
            parallel,
            signalAwaiterTask(&iface, &GlueInterface::repaired), // transitions to the "working" state
            Forever {
                switchLightsTask(&iface, Light::Yellow, 1s), // "blinking" state
                switchLightsTask(&iface, Light::Off, 1s), // "unblinking" state
            }
        }
    };
}
Root state management

The top level Forever item executes its children in an infinite loop. It contains two subgroups representing working and broken states.

State transition rules:

  • States execute in sequence, because the default execution mode is sequential.
  • When one state completes, the other begins.
  • Transitions are triggered when a user clicks the pause or play button in the UI, emitting the corresponding signal.
The "working" operation state

The working Group consists of two tasks running in parallel:

  • The signalAwaiterTask connects to the GlueInterface::smashed signal. If the signal is triggered, the task immediately finishes with DoneResult::Success. This causes the working Group to stop executing, and the recipe transitions to the broken state.
  • The nested Forever task consist of four switchLightsTask tasks ("red", "redGoingGreen", "green", and "greenGoingRed") executed sequentially in an infinite loop.

The stopOnSuccess item of each group instructs it to stop executing when any of its child tasks finishes with DoneResult::Success.

The switchLightsTask implementation:

  1. Updates light states through GlueInterface::setLights().
  2. Creates a timed delay using timeoutTask.
  3. Maintains state for the specified duration timeout.
static Group switchLightsTask(GlueInterface *iface, Lights lights, const milliseconds &timeout)
{
    return {
        onGroupSetup([iface, lights] { iface->setLights(lights); }),
        timeoutTask(timeout, DoneResult::Success)
    };
}
The "broken" operation state

The broken Group is similar to the working Group. The only difference is that it connects to the GlueInterface::repaired signal, and the lights cycle only between 2 states toggling the yellow light on and off.

Application setup

The main application setup creates and connects all components:

int main(int argc, char **argv)
{
    QApplication app(argc, argv);

    GlueInterface iface;
    QTaskTree taskTree({recipe(iface)});
    TrafficLight widget(iface);

    widget.show();
    taskTree.start();

    return app.exec();
}

The main() function instantiates QApplication, GlueInterface, QTaskTree and TrafficLight objects.

To construct the taskTree object, we pass a recipe() constructed with the iface object. The widget also uses the passed iface object.

Before executing the application, the main() function shows the TrafficLight widget and starts the task tree.

Running the Example

To run the example from Qt Creator, open the Welcome mode and select the example from Examples. For more information, see Qt Creator: Tutorial: Build and run.

Example project @ code.qt.io

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.