3

i have a strange problem with QStateMachine, which i cannot solve for a week.

Brief explanation:

I use QStateMachine in my application for controlling of sending commands to a biomedical device, connected through COM port. The whole state machine is complicated with hundreds of states and frequent substating.

Machine is being generated from device's communication protocol, based on user's preference. For example, if user wants some "reaction" from device, this reaction is composed of several subreactions that consist of elementary steps (instructions for the device). Application builds whole reaction from these elementary steps and starts it on state machine.

Therefore, I've created class CompositeReactionControl (inherited from QState) which allows to build a composite from particular substates.

QSignalTransition instances are used to connect states together.

Problem description:

Sometimes the machine does not transit from one state to another, when finish() signal of previous state is emitted.

NOTES:

  1. I am 100% sure that state is connected.
  2. I am 100% sure that signal is fired.
  3. Event through machine and its states run in separate thread from the rest of application, this particular signal is fired and caught in SAME thread
  4. It happens randomly in all reactions (there is no particular reaction or substate connected with this problem)
  5. Substate which caused problem in one run (reaction), runs without any problem in other runs of same reaction
  6. It happens when StateMachine runs for long time, it never appears before 4-th reaction (no matter which reactions are chosen by user).

CODE

Problematic parts are marked with comment "HERE IS PROBLEM" in the following code...

Adding of new substate to CompositeReactionControl:

/**
 * @brief Add new reaction control as a child of this composite
 * @param reaction child reaction (inherited from QState)
 * @return added child reaction
 */
AbstractReactionControl* CompositeReactionControl::addChildReaction(
    AbstractReactionControl* reaction) {

  // Check whether reaction is valid
  if (reaction == NULL) {
    QString msg = tr("Cannot add NULL subreaction to reaction '%1'.");
    Logger::getInstance()->addError(msg.arg(getName()), this);
    return NULL;
  }

  // Adopt reaction
  reaction->setParent(this);

  // Store previous reaction control and add current to list
  AbstractReactionControl* prev = controls.size() > 0 ? controls.last() : NULL;
  controls.append(reaction);

  // Connects current state
  connect(reaction, SIGNAL(reactionEntered(core::AbstractCommunicationState*)),
          SLOT(onSubReactionEntered(core::AbstractCommunicationState*)));
  connect(reaction, SIGNAL(reactionFinished(core::AbstractCommunicationState*)),
          SLOT(onSubReactionFinished(core::AbstractCommunicationState*)));

  // If previous state does not exist (this is the first one) then set current
  // state 'c' as initial state and add transition from 'c' to final state
  // Otherwise add transition from previous state to 'c', remove transition from
  // previous state to final state and add transition from 'c' to final state
  if (prev == NULL) {
    this->setInitialState(reaction);
    this->firstState = reaction;
  } else {

    // Remove end transitions from previous state
    prev->removeTransition(endTransition1);
    prev->removeTransition(endTransition2);
    delete endTransition1;
    delete endTransition2;

    // Replaced with PassTransition
    //prev->addTransition(prev, SIGNAL(finished()), reaction);

    // HERE IS PROBLEM: I am 100% sure that finished() signal is emitted,
    // but sometimes not caught by transition
    PassTransition* t = new PassTransition(this, prev, SIGNAL(finished()));
    t->setTargetState(reaction);
    prev->addTransition(t);
  }

  // Assign new end transitions to current state
  endTransition1 = new RepeatTransition(this, reaction, SIGNAL(finished()));
  endTransition2 = new FinishTransition(this, reaction, SIGNAL(finished()));  
  endTransition1->setTargetState(firstState);
  endTransition2->setTargetState(allFinished);
  reaction->addTransition(endTransition1);
  reaction->addTransition(endTransition2);

  // Finish if halt
  reaction->addTransition(this, SIGNAL(halt()), allFinished);

  // Exit reaction
  return reaction;
}
//---------------------------------------------------------------------------

PassTransition:

/**
 * @class PassTransition
 * @brief Passes control from one substate to another
 * @author Michal Rost
 * @date 6.11.2013
 */
class PassTransition : public QSignalTransition {
public:
  PassTransition(CompositeReactionControl* owner, QObject* sender,
                 const char *signal) : QSignalTransition(sender, signal) {
    this->owner = owner;
  }
protected:
  CompositeReactionControl* owner;
  bool eventTest(QEvent *event) {

    // HERE IS PROBLEM: I know that signal is called, but event is not received

    QString msg = tr("Event Test %1, source: %2   target: %3");
    AbstractReactionControl* s = dynamic_cast<AbstractReactionControl*>(this->sourceState());
    AbstractReactionControl* t = dynamic_cast<AbstractReactionControl*>(this->targetState());
    common::Logger::getInstance()->addDebug(msg.arg(event->type()).arg(s->getName()).arg(t->getName()), this);
    return QSignalTransition::eventTest(event);
  }
  void onTransition(QEvent *event) {
    QSignalTransition::onTransition(event);
    QString msg = tr("Transition called");
    common::Logger::getInstance()->addDebug(msg, this);
  }
};

Log from good case

In this case everything works well. Important is that PassTransition::EventTest receives event of type 192, which is state machine event created from finished() signal of previous state.

[08:33:01:976][core::CompositeReactionControl] State 'send_fluorescence_on' finished, disconnecting...
[08:33:01:976][core::CompositeReactionControl] State 'ProfileData' removed control from 'send_fluorescence_on' substate.
[08:33:01:977][core::ReactionEmitter] Sending state entered. Timer Started.
[08:33:01:977][core::PassTransition] Event Test 0, source: send_fluorescence_on   target: send_fluorescence_off
[08:33:01:977][core::PassTransition] Event Test 0, source: measure_fluorescence   target: measure_fluorescence
[08:33:01:977][core::PassTransition] Event Test 192, source: send_fluorescence_on   target: send_fluorescence_off
[08:33:01:977][core::PassTransition] Transition called
[08:33:01:977][core::CompositeReactionControl] State 'ProfileData' gave control to 'send_fluorescence_off' substate.

Log from bad case

As can be seen, if error RANDOMLY occurs, mentioned event is not received by method PassTransition::EventTest. After 5 second timeout (I tryed even longer intervals), I stop the statemachine and print error.

[08:33:01:991][core::CompositeReactionControl] State 'send_fluorescence_off' finished, disconnecting...
[08:33:01:991][core::CompositeReactionControl] State 'ProfileData' removed control from 'send_fluorescence_off' substate.
[08:33:01:991][core::ReactionEmitter] Sending state entered. Timer Started.
[08:33:01:991][core::PassTransition] Event Test 0, source: send_fluorescence_off   target: led_off
[08:33:01:991][core::PassTransition] Event Test 0, source: measure_fluorescence   target: measure_fluorescence
[08:33:16:966][core::ReactionEmitter] State machine is frozen. Reaction will be stopped!
Michal
  • 1,955
  • 5
  • 33
  • 56
  • 1
    You will need to create a stand-alone test case and submit it as a Qt bug. Drop everything unrelated to what's needed to reproduce the issue, it should be very simple. – Kuba hasn't forgotten Monica Nov 12 '13 at 21:04
  • Two things I would suggest is to use qobject_cast instead of dynamic_cast and to use Qt5's new signal and slot connection syntax using function pointers. The function pointers give you compile time validation that all signals and slots are present and compatible with each other. – Keith Jan 22 '14 at 03:57

0 Answers0