1

I'm trying to add to qstatemachine a property with string and an enum, but the result from the machine.property() is empty.

QStatemachine m_machine;

idle->assignProperty(&m_machine, "state", Z_IDLE);
cool->assignProperty(&m_machine, "state", Z_COOL);
start_z->assignProperty(&m_machine, "state", Z_START);

QVariant st = m_machine->property("state");
QString s = st.toString();

I tried to show my approach to see if that can work or no.

Update:

 idle = new QState();
 start_z = new QState();
 lock = new QState();
 connect(this, SIGNAL(machine_exec()), this, SLOT(idle_exec()));
 connect(this, SIGNAL(machine_exec()), this, SLOT(start_z_exec()));
 connect(this, SIGNAL(machine_exec()), this, SLOT(sample_exec()));

  m_machine->addState(idle);
  m_machine->addState(start_z);
  m_machine->addState(lock);

 idle->assignProperty(m_machine, "state", Z_IDLE);
 cool->assignProperty(m_machine, "state", Z_COOL);
 start_z->assignProperty(m_machine, "state", Z_START);

  idle->addTransition(this, SIGNAL(machineToStart()), start_z);
  cool->addTransition(this, SIGNAL(machineToMotDn()), motDn);
  motDn->addTransition(this, SIGNAL(machineToFini()), fini);

    void MeasController::idle_exec()
    {
        qDebug()<<"idle_exec";
         emit machineToStart();

     }

    void MeasController::start_z_exec()
    {
        qDebug()<<"start_z_exec";
        QVariant s = m_machine->property("state");
        qDebug()<<"property value"<<s.toString();
        if (m_machine->property("state") == Z_START) {
            emit machineStartToSample();
            }
    }
andreahmed
  • 135
  • 1
  • 1
  • 9
  • You're doing something seemingly unnecessary. Why do you have a `machine_exec` signal? Do you want certain things to be done when a state is entered? That's what `entered` signal is for. You'd need to provide a self-contained test case, the code you show above is not enough to figure out what you're trying to do. – Kuba hasn't forgotten Monica Apr 20 '16 at 15:02
  • I've edited the answer to show a potentially much simpler approach to what you're doing. Please edit the question to indicate what is the behavior you're trying to achieve, it's almost impossible to approach your question without it. – Kuba hasn't forgotten Monica Apr 20 '16 at 16:47
  • Hi! I have same problem assignProperty - does nothing. My `state` property is `enum` type. My `state` property is property of MyClass. MyClass object is owner of statemachine object. Have anybody any solution or things to try? – fl-web Jun 10 '16 at 21:33
  • Yeah! I just change `state` type from `enum` to `int` and it is worked now! (Qt version 4.8) – fl-web Jun 10 '16 at 21:46
  • @fi-web Maybe the type in the variant vs property must match exactly, not just be comvertible? – Macke Jun 16 '20 at 08:20

1 Answers1

2

The properties are assigned on state transitions only, so if you check the value of the property before the machine was started and the event loop had a chance to run, you won't get a valid value.

Additionally, by using the name of the property multiple times, you're liable to commit a typo. Instead of string literals, you should be using named constants for property names - and they should be sufficiently different that random typos will get caught. I.e. property names like kStateA and kStateB are probably too similar - a typo would be silent.

So, I'd change the code to be:

static const char kState[] = "state";
QStatemachine m_machine;

idle->assignProperty(&m_machine, kState, Z_IDLE);
cool->assignProperty(&m_machine, kState, Z_COOL);
start_z->assignProperty(&m_machine, kState, Z_START);

qDebug() << m_machine->property(kState).toString();

Also note that this notion of a single current state only applies if your machine is non-hierarchical. A general HSM, as implemented by QStateMachine, is always in a set of states, available from configuration(). Most realistic machines should be hierarchical, so your approach won't work.

It is also unclear that there's any use for the _exec methods, as well as the explicit signals used to transition between states.

Below is a complete example of approaches that you might find useful.

First, let's start with how one might concisely specify your MeasController:

class MeasController : public QObject {
   Q_OBJECT
   QStateMachine m_machine{this};
   NamedState
      m_idle    {"Idle", &m_machine},
      m_start_z {"Start Z", &m_machine},
      m_active_z{"Active Z", &m_machine},
      m_downMove{"DownMove", &m_machine};
   Transition
      m_toStartZ{&m_idle, &m_start_z},
      m_toDownMove{&m_active_z, &m_downMove};
   Delay
      m_d1{&m_start_z, &m_active_z, 1000},
      m_d2{&m_downMove, &m_active_z, 2000};
   QList<QState*> states() const { return m_machine.findChildren<QState*>(); }
public:
   MeasController(QObject * parent = 0) : QObject(parent) {
      for (auto state : states())
         connect(state, &QState::entered, [state]{ qDebug() << state->objectName(); });
      m_machine.setInitialState(&m_idle);
      m_machine.start();
   }
   Q_SLOT void startZ() { m_toStartZ.trigger(); }
   Q_SLOT void moveDown() { m_toDownMove.trigger(); }
};

This is a complete specification of the state machine. The states and their transitions and other behaviors are given as members of MeasController, and are thus easy to find in one place. For ease of debugging, debug output is provided upon each state being entered. Slots are provided to trigger behaviors from outside of the class without having to expose the internals.

Let's now look at the idioms defined by the NamedState, Transition and Delay classes.

A named state idiom, similar to Qt 3's QObject taking name in the constructor, is useful for debugging at the very least. If you want to assign any other properties of the state, you can do so in this class as well. Since the states have unique variable names and are members of the parent class any integer identifiers aren't necessary:

// https://github.com/KubaO/stackoverflown/tree/master/questions/state-properties-36745219
#include <QtWidgets>

class NamedState : public QState { Q_OBJECT
public:
   NamedState(const char * name, QStateMachine * parent) : QState(parent) {
      setObjectName(QString::fromUtf8(name));
   }
};

A transition class is useful to give a concise way of specifying the structure of your state machine as simple member definitions of Transition instances:

struct Transition : public QObject { Q_OBJECT
public:
   Transition(QState * source, QState * destination) : QObject(source->machine()) {
      source->addTransition(this, &Transition::trigger, destination);
   }
   Q_SIGNAL void trigger();
};

I'm sure that your state machine has more complex behaviors, but generally it helps to factor out a certain behavior into its own class. E.g. say you want a state transition that occurs after a delay:

class Delay : public Transition { Q_OBJECT
   int m_delay;
   QBasicTimer m_timer;
   void timerEvent(QTimerEvent * ev) {
      if (m_timer.timerId() != ev->timerId()) return;
      m_timer.stop();
      trigger();
   }
public:
   Delay(QState * s, QState * d, int ms) : Transition(s, d), m_delay(ms) {
      connect(s, &QState::entered, this, [this]{ m_timer.start(m_delay, this);});
   }
};

Finally, we can offer a simple UI to test and visualize the behavior of the machine. The debug output is duplicated in a QPlainTextEdit for ease of use.

screenshot of the example

int main(int argc, char ** argv) {
   QApplication app{argc, argv};
   MeasController ctl;
   QWidget w;
   QGridLayout layout{&w};
   QPushButton start{"Start"};
   QPushButton moveDown{"Move Down"};
   QPlainTextEdit log;
   log.setReadOnly(true);
   layout.addWidget(&start, 0, 0);
   layout.addWidget(&moveDown, 0, 1);
   layout.addWidget(&log, 1, 0, 1, 2);

   QObject::connect(&start, &QPushButton::clicked, &ctl, &MeasController::startZ);
   QObject::connect(&moveDown, &QPushButton::clicked, &ctl, &MeasController::moveDown);

   static QtMessageHandler handler = qInstallMessageHandler(
      +[](QtMsgType t, const QMessageLogContext& c, const QString & msg){
      static QPointer<QPlainTextEdit> log{[]{
         for (auto w : qApp->topLevelWidgets())
            for (auto log : w->findChildren<QPlainTextEdit*>()) return log;
         Q_ASSERT(false);
      }()};
      if (log) log->appendPlainText(msg);
      handler(t, c, msg);
   });

   w.show();
   return app.exec();
}

#include "main.moc"

This concludes the complete example. This is a longer example in a somewhat similar style.

Community
  • 1
  • 1
Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313