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:
- I am 100% sure that state is connected.
- I am 100% sure that signal is fired.
- 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
- It happens randomly in all reactions (there is no particular reaction or substate connected with this problem)
- Substate which caused problem in one run (reaction), runs without any problem in other runs of same reaction
- 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!