0

I have a working state machine that sends similar messages from several states. At present they are all hard coded, so I have fragments in my .scxml file like:

<state id="state1">
  <transition event="event_1">
    <send event="unexpectedEvent1FromState1"/>
  </transition>
</state>

and

<state id="state2">
  <transition event="event_2">
    <send event="unexpectedEventEvent2FromState2"/>
  </transition>
</state>

and I have to catch multiple unexpectedEventXxxxFromYyyy messages elsewhere in my C++ code.

I'd like to standardise these message so that I just have to catch a single, parameterised unexpectedEvent signal in my code, which will examine the QScxmlEvent object to find the transition and source state that caused the signal to be emitted.

Having looked at the Qt documentation, I believe I need to add a data model. I don't use these anywhere so have no familiarity. I have previously experimented fairly successfully with an EcmaScript data model but found that the application crashes on my machine if I try to create more than around 150 machines, apparently because of the memory required for 150+ V8 JavaScript engines. Since I need to run 1000+ copies of the state machine, an EcmaScript data model is ruled out and I need to use a C++ data model.

I've had no luck with this and the program crashes when the first machine I instantiate first tries to process events. I have reduced the code in the data model to the barest of bare bones as below, and it still crashes.

Please can someone tell me what to do to get my data model working? I've looked at the Qt examples and they all seem too trivial to be helpful, can anyone point me to any meatier examples? Many thanks.

Bare bones code changes

Added to the root element in the .scxml file:

 datamodel="cplusplus:FooDatamodel:foodatamodel.h"

foodatamodel.h:

#ifndef FOODATAMODEL_H
#define FOODATAMODEL_H

#include "qscxmlcppdatamodel.h"

class FooDatamodel : public QScxmlCppDataModel
{
    Q_OBJECT
    Q_SCXML_DATAMODEL

public:
    FooDatamodel();
};

#endif // FOODATAMODEL_H

foodatamodel.cpp

#include "foodatamodel.h"

FooDatamodel::FooDatamodel()
{
}

Disclaimers:

  1. I'm using the state machine editor in Qt Creator and I may well have left out something vital in the hand-written SCXML fragment at the top. I'm pretty sure that the real file is syntactically and semantically valid - although the datamodel attribute above is pretty accurate.
  2. The real filenames and state and transition names are different, and I may have failed to change something in the C++ fragments above. The real files do not contain any substantial code.

Thanks again, apologies for the length of the question.

nurdglaw
  • 2,107
  • 19
  • 37

2 Answers2

2

In case anyone ever comes this way again...

It turns out that you need explicit code in the QScxmlCppDataModel-derived class to associate it with the state machine. This is achieved by calling QScxmlDataModel::setStateMachine, passing a pointer to the state machine instance.

Given that this function is in a base class of the one that I was deriving from, I feel more irritated than embarrassed that I missed it. YMMV.

I'm sorry to any future reader that, seven months after raising the question, I cannot now construct a simple example of what is required.

nurdglaw
  • 2,107
  • 19
  • 37
0

As there is very little on SO regarding this ... here is my 2p (sorry English) ...

My DataModel.h look like this

#ifndef DATAMODEL_H
#define DATAMODEL_H

#include <QDebug>
#include <QScxmlCppDataModel>
#include <QScxmlEvent>

class DataModel :public QScxmlCppDataModel
{
    Q_OBJECT
    Q_SCXML_DATAMODEL

public:
    DataModel(QObject *parent);
    void UpdateFields(QString call, int rst, QString exchange);
    bool CallIsValid(QString s);
    bool RstIsValid(int i);
    bool ExchangeIsValid(QString s);

    QString Call;
    int Rst;
    QString Exchange;

    QString m_Descript;
    QVariant m_var;
};


#define DATAMODEL_H
#endif // DATAMODEL_H

The implementation of it I do like this ....

#include "DataModel.h"



DataModel::DataModel(QObject *parent):
       QScxmlCppDataModel(parent)
{
    qDebug() << "Data Model Initalized";
}

void DataModel::UpdateFields(QString call, int rst, QString exchange)
{
    Call=call;
    Rst=rst;
    Exchange=exchange;
}

bool DataModel::CallIsValid(QString s)
{
    if (s.length()>4)
    {
        Call=s;
        return true;
    }
    else
        return false;
}

My Test State Engine has 3 states, Call, RST, Exchange... and the Events between then are called

  • gotCall
  • gotRST
  • gotExchange

So my MainWindow header looks like

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QScxmlStateMachine>
#include <QScxmlCppDataModel>
#include <QDebug>
#include "DataModel.h"

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    // These are the States

    void onCallState(bool isActive);
    void onRstState(bool isActive);
    void onExchangeState(bool isActive);

    // These are the Events
    void ongotCallEvent(const QScxmlEvent &event);
    void ongotRstEvent(const QScxmlEvent &event);
    void ongotExchangeEvent(const QScxmlEvent &event);



public slots:
    void OnReturnPressed();


private:
    Ui::MainWindow *ui;
    QScxmlStateMachine *m_stateMachine;
    DataModel *datamodel;


};
#endif // MAINWINDOW_H

The implementation is where all the "wiring" is done ...

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    
    connect(ui->f1,SIGNAL(returnPressed()),this,SLOT(OnReturnPressed()));
    connect(ui->f2,SIGNAL(returnPressed()),this,SLOT(OnReturnPressed()));
    connect(ui->f3,SIGNAL(returnPressed()),this,SLOT(OnReturnPressed()));
    
    m_stateMachine = QScxmlStateMachine::fromFile(":/QsoEx.scxml");
    for(QScxmlError& error:m_stateMachine->parseErrors())
    {
        qDebug()<<error.description();
    }
    
   
    // Connect up the States
    // We have 3 States... Call Rst Exchange
    m_stateMachine->connectToState("Call", this, &MainWindow::onCallState);
    m_stateMachine->connectToState("Rst", this, &MainWindow::onRstState);
    m_stateMachine->connectToState("Exchange", this, &MainWindow::onExchangeState);
    // We have 3 Events which move us between each State
    m_stateMachine->connectToEvent("gotCall", this, &MainWindow::ongotCallEvent);
    m_stateMachine->connectToEvent("gotRst", this, &MainWindow::ongotRstEvent);
    m_stateMachine->connectToEvent("gotExchange", this, &MainWindow::ongotExchangeEvent);
    
    datamodel = new DataModel(this);
    
    m_stateMachine->setDataModel(datamodel);
    m_stateMachine->init();
    m_stateMachine->start();
    
}

The "logic" ... i.e. what controls the movement between states - is expressed like this ... the fields f1,f2 and f3 are line input objects in the UI....

void MainWindow::OnReturnPressed()
{
    QString curState = m_stateMachine->activeStateNames()[0];
    qDebug() << "On Return Pressed Triggered";
    qDebug() << "Current State is " + curState;
    if (curState == "Call")
    {
        qDebug() << "We are in Call State. Checking Call";
        // We can move from call to rst is we have a valid call
        if (datamodel->CallIsValid(ui->f1->text()))
        {
            qDebug() << "Data Is Valid";
            QVariant var = QVariant(ui->f1->text());
            m_stateMachine->submitEvent("gotCall", var);
            ui->f2->setFocus();
        }
        else
        {
            ui->f1->setFocus();
        }
    }
    if (curState == "RST")
    {
        if (datamodel->RstIsValid(QString(ui->f2->text()).toInt()))
        {
            QVariant var = QVariant(ui->f1->text());
            m_stateMachine->submitEvent("gotRst", QVariant("Rst"));
            ui->f3->setFocus();
        }
        else
        {
            ui->f2->setFocus();
        }
    }
    if (curState == "Exchange")
    {
        if (datamodel->ExchangeIsValid(ui->f3->text()))
        {
            QVariant var = QVariant(ui->f1->text());
            m_stateMachine->submitEvent("gotExchange", QVariant("Rst"));
            ui->f1->setFocus();
        }
        else
        {
            ui->f3->setFocus();
        }
    }
    
    
    ui->label->setText("State "+m_stateMachine->activeStateNames()[0]);
}

If you would like a copy of this code, as there may be things I have omitted... please goto https://github.com/timseed/Ex_Qt_StateMachine

Tim Seed
  • 5,119
  • 2
  • 30
  • 26