How to use conditional transitions in Qt SCXML statecharts

885 Views Asked by At

I'm currently trying to understand Qts scxml state charts, and how to properly integrate them in my applications. One problem I stumbled uppon are conditional transitions. To explain how I use conditions here and what goes wrong, I made a minimum viable example:

state chart visualization

The initial state s_initial has two transitions to the states s_false and s_true. Both transitions are triggered by the same event t_button_clicked. Depending on the variable test_var, only one transition is possible possible at any time. When another t_button_clicked event occurs, the state machine returns to s_initial.

To test the state machine I created a simple Qt-Widgets application with one push-button to trigger t_button_clicked, and a checkbox to change the variable test_var:

(mainwindow.cpp)

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
    , chart(this)
{
    ui->setupUi(this);
    connect(ui->checkBox, &QCheckBox::clicked, [this](bool checked){
        qDebug() << "> checkbox:" << checked;
        chart.dataModel()->setProperty("test_var", checked);
    });
    connect(ui->pushButton, &QPushButton::released, [this](){
        qDebug() << "> button";
        chart.submitEvent("t_button_clicked");
    });
    chart.start();
}

(testchart.scxml)

<?xml version="1.0" encoding="UTF-8"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" binding="early" xmlns:qt="http://www.qt.io/2015/02/scxml-ext" name="TestChart" qt:editorversion="4.14.1" datamodel="ecmascript" initial="s_initial">
    <qt:editorinfo initialGeometry="213.11;86.67;-20;-20;40;40"/>
    <state id="s_initial">
        <qt:editorinfo scenegeometry="213.11;233.50;153.11;183.50;120;100" geometry="213.11;233.50;-60;-50;120;100"/>
        <transition type="external" event="t_button_clicked" target="s_false" cond="!test_var">
            <qt:editorinfo endTargetFactors="11.79;50.87"/>
        </transition>
        <transition type="external" event="t_button_clicked" target="s_true" cond="test_var">
            <qt:editorinfo movePoint="37.73;-3.06" endTargetFactors="19.26;54.39"/>
        </transition>
        <onentry>
            <log expr="&quot;s_initial&quot;"/>
        </onentry>
    </state>
    <state id="s_false">
        <qt:editorinfo scenegeometry="529.21;233.50;469.21;183.50;120;100" geometry="529.21;233.50;-60;-50;120;100"/>
        <onentry>
            <log expr="&quot;s_false&quot;"/>
        </onentry>
        <transition type="external" event="t_button_clicked" target="s_initial">
            <qt:editorinfo movePoint="3.06;9.18" endTargetFactors="88.28;64.08" startTargetFactors="13.98;61.45"/>
        </transition>
    </state>
    <state id="s_true">
        <qt:editorinfo scenegeometry="529.21;419.09;469.21;369.09;120;100" geometry="529.21;419.09;-60;-50;120;100"/>
        <onentry>
            <log expr="&quot;s_true&quot;"/>
        </onentry>
        <transition type="external" event="t_button_clicked" target="s_initial">
            <qt:editorinfo movePoint="-37.73;6.12" endTargetFactors="68.74;85.18" startTargetFactors="14.04;72.02"/>
        </transition>
    </state>
    <datamodel>
        <data id="test_var" expr="false"/>
    </datamodel>
</scxml>

As can be seen in the scxml-file, I added logging output to each state-onentry to see whether the state was entered or not. Additionally, I added debugging output to the button - and checkbox-clicks. When I run the application, the console output is not what I would've expected:

scxml.statemachine: "" : "s_initial"
> checkbox: false
> button
scxml.statemachine: "" : "s_false"
> button
scxml.statemachine: "" : "s_initial"
> checkbox: true
> button
scxml.statemachine: "" : "s_false"
> button
scxml.statemachine: "" : "s_initial"
> checkbox: false
> button
scxml.statemachine: "" : "s_false"
> button
scxml.statemachine: "" : "s_initial"

It doesn't matter what the value of test_var is. The state machine always takes the first transition to s_false, not checking the condition guards I added. As far as I can tell, I used valid ecmascript expressions in my chart and scxml should be able to choose the right transition according to its specification. What am I doing wrong?

1

There are 1 best solutions below

1
On BEST ANSWER
  1. You should use setScxmlProperty instead of setProperty

    chart.dataModel()->setScxmlProperty("test_var", checked, "");

  2. And you may simply use _event.data for passing checkBox current value.

    chart.submitEvent("t_button_clicked", ui->checkBox->checked() ? 1:0 );

<scxml datamodel="ecmascript" initial="s_initial" name="TestChart" version="1.0" xmlns="http://www.w3.org/2005/07/scxml">
    <state id="s_initial">
        <onentry>
            <log expr="'s_initial'"/>
        </onentry>
        <transition cond="_event.data==1" event="t_button_clicked" target="s_true"/>
        <transition event="t_button_clicked" target="s_false"/>
    </state>
    <state id="s_false">
        <onentry>
            <log expr="'s_false'"/>
        </onentry>
        <transition cond="_event.data==1" event="t_button_clicked" target="s_true"/>
    </state>
    <state id="s_true">
        <onentry>
            <log expr="'s_true'"/>
        </onentry>
        <transition cond="! (_event.data==1)" event="t_button_clicked" target="s_false"/>
    </state>
</scxml>

statechart

P.S. You may use the next reference materials for better understanding SCXML (I'm promoting my own website)