3

QStateMachine に奇妙な問題があり、1 週間解決できません。

簡単な説明:

COM ポート経由で接続された生物医学デバイスへのコマンドの送信を制御するために、アプリケーションで QStateMachine を使用しています。ステート マシン全体は、何百ものステートと頻繁なサブステートで複雑になっています。

マシンは、ユーザーの設定に基づいて、デバイスの通信プロトコルから生成されています。たとえば、ユーザーがデバイスから何らかの「反応」を求めている場合、この反応は基本的なステップ (デバイスへの指示) で構成されるいくつかのサブ反応で構成されます。アプリケーションは、これらの基本的なステップから全体の反応を構築し、ステート マシンで開始します。

したがって、特定のサブステートからコンポジットを構築できるクラスCompositeReactionControl (QState から継承) を作成しました。

QSignalTransition インスタンスは、状態を相互に接続するために使用されます。

問題の説明:

前の状態の finish() シグナルが発行されたときに、マシンがある状態から別の状態に遷移しないことがあります。

ノート:

  1. 状態が接続されていることを 100% 確信しています。
  2. 信号が発せられていることは 100% 確信しています。
  3. マシンを介したイベントとその状態は、アプリケーションの残りの部分とは別のスレッドで実行されます。この特定のシグナルが発生し、同じスレッドでキャッチされます
  4. すべての反応でランダムに発生します (この問題に関連する特定の反応またはサブ状態はありません)。
  5. 1 回の実行 (反応) で問題を引き起こしたサブステートは、同じ反応の別の実行では問題なく実行されます
  6. StateMachine が長時間実行されている場合に発生し、4 番目の反応の前には表示されません (ユーザーがどの反応を選択しても)。

コード

問題のある部分は、次のコードで「HERE IS PROBLEM」というコメントでマークされています...

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);
  }
};

良いケースからのログ

この場合、すべてがうまく機能します。重要なのは、PassTransition::EventTest がタイプ 192 のイベントを受け取ることです。これは、前の状態の finished() シグナルから作成されたステート マシン イベントです。

[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.

悪いケースからのログ

ご覧のとおり、RANDOMLY エラーが発生した場合、前述のイベントはメソッド PassTransition::EventTest によって受信されません。5 秒のタイムアウトの後 (さらに長い間隔を試しました)、ステートマシンを停止してエラーを出力します。

[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!
4

0 に答える 0