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:
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=""s_initial""/>
</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=""s_false""/>
</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=""s_true""/>
</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?
You should use setScxmlProperty instead of setProperty
chart.dataModel()->setScxmlProperty("test_var", checked, "");
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>
P.S. You may use the next reference materials for better understanding SCXML (I'm promoting my own website)