Pull to refresh

Quantum Leaps QP и UML statecharts

Reading time 6 min
Views 7.5K

Предисловие


Данная статья, как мне кажется, будет интересна тем, кто знаком с UML диаграммами состояний и последовательности (statecharts diagram и sequence diagram), а также с событийно-ориентированным программированием (event-driven programming).

Вступление


Кроссплатформенный фреймворк QP (Quantum Platform) от компании Quantum Leaps представлен его создателями как средство разработки RTOS на C/C++. Он позволят существенно увеличить скорость разработки и надежность приложений, а также имеет мощный инструментарий по отладке и логированию. Ко всему этому добавляется еще и то, что QP является очень гибким и легким: разделен на множество модулей, почти каждый из которых можно реализовать самому при сборке фреймворка или воспользоваться предложенным решением; множество настроек выполняется во время прекомпиляции.

QP-components

Программа, написанная с использованием QP, представляет собой реализацию совокупности диаграмм состояний UML statecharts.
Статья является неявным рассмотрением модуля QEP, являющегося основой фреймворка (QEP реализует обработку событий), и написана с целью сподвигнуть на дальнейшее изучение.

Система обработки событий в QP


Для реализации диаграмм состояний QP предоставляет UML-совместимую систему обработки событий. Но стоит обратить внимание на одно обстоятельство: фреймворком поддерживаются только локальные переходы, т. е. переход в подсостояние не сопровождается событием exit, а переход в суперсостояние не возбуждает событие entry. В UML 2 для совместимости с прошлыми версиями была оставлена возможность делать внешние переходы. В более ранних версиях языка доступны только внешние переходы: любой переход сопровождается событием exit и entry для текущего и целевого состояний соответственно.

image

Разработчику QP предлагает следующие классы:
QFsm для реализации одноуровневого конечного автомата (без суперсостояний)
QHsm для реализации иерархического автомата (с суперсостояниями)
Объекты этих классов хранят текущее состояние UML-диаграммы, и имеют публичный метод void dispatch(const QEvent* e) для обработки события.
Также QP предоставляет класс QActive, по-умолчанию являющийся расширением QHsm (можно сменить на QFsm). Объекты данного класса, называемые активными объектами, реализуют возможность работы автомата в отдельном потоке выполнения.

Событие в QP — это объекты класса QEvent или его наследники. Каждое событие характеризуется сигналом — поле sig, отличающее один тип события от другого.

Состояние в QP — это функция (или статический метод), указатель на которую имеет вид:
typedef QState (*QStateHandler)(void *me, QEvent const *e), где me — контекст (объект автомата), e — поступившее событие

Фреймворк предусматривает для состояния три основных способа обработки события:
  • Q_HANDLED(), если нужно просто обработать событие, не совершая перехода;
  • Q_TRAN(<адрес следующего состояния>) — сделать переход;
  • Q_SUPER(<адрес суперсостояния>) — передать суперсостоянию. Доступно только для QHsm-автоматов.

QP также имеет специальные события, которые могут генерироваться при переходе. Порядок возникновения этих событий и последовательность действий при совершении перехода следующие:
  1. выполняются атомарные действия перехода;
  2. событие exit (Q_EXIT_SIG). Возникает в текущем состоянии при выходе из него. Если к тому же происходит выход из суперсостояния, то exit для этого суперсостояния происходит сразу после exit для текущего. Событие exit может быть только обработано (Q_HANDLED);
  3. автомат переходит в целевое состояние;
  4. событие entry (Q_ENTRY_SIG). Возникает при входе в состояние. Это событие может быть только обработано;
  5. событие с сигналом Q_INIT_SIG (только для QHsm-автоматов) — инициализирующее событие. Возникает всегда. Служит для совершения перехода (Q_TRAN) в подсостояние, но в принципе его тоже можно просто обработать;

Общий вид QFsm состояний:
static QState state1(Fsm* me, const QEvent* e) {
    switch(e->sig) {
        case Q_ENTRY_SIG: {
            /* processing */
            return Q_HANDLED();
        }
        case EVENT1_SIG: {
            /* processing */
            return Q_TRAN(&state2);
        }
        case EVENT2_SIG: {
            /* processing */
            return Q_HANDLED();
        }
        case Q_EXIT_SIG: {
            /* processing */
            return Q_HANDLED();
        }
    }
    return Q_HANDLED();
}


* This source code was highlighted with Source Code Highlighter.


Общий вид QHsm состояний:
static QState state1(Hsm* me, const QEvent* e) {
    switch(e->sig) {
        case Q_ENTRY_SIG: {
            /* processing */
            return Q_HANDLED();
        }
        case Q_INIT_SIG: {
            /* processing */
            return Q_TRAN(&state1_substate1); // return Q_HANDLED();
        }
        case EVENT1_SIG: {
            /* processing */
            return Q_TRAN(&state2);
        }
        case EVENT2_SIG: {
            /* processing */
            return Q_HANDLED();
        }
        case Q_EXIT_SIG: {
            /* processing */
            return Q_HANDLED();
        }
    }
    return Q_SUPER(&superstate1);
}


* This source code was highlighted with Source Code Highlighter.


Специальные события типа Q_ENTRY_SIG, Q_INIT_SIG, Q_EXIT_SIG существуют в рамках одного состояния и не делегируются суперсостоянию, обрабатывать их необязательно.

Пример


Рассмотрим систему с одним активным объектом (sm) и односторонним воздействием на него. В этом случае можно обойтись без использования модуля многозадачности (QK) и класса QActive.



Пусть объект sm имеет диаграмму состояний, являющуюся иерархическим конечным автоматом.



Вот так можно реализовать эту задачу, используя QP:
// сигналы
enum Signals {
    PROCEED_SIG = Q_USER_SIG,
    CANCEL_SIG,
};

// класс событий, соответствующий сигналу PROCEED_SIG
struct ProceedEvt : public QEvent {
    ProceedEvt(int value = 0) : value(value) { sig = PROCEED_SIG; }
    int value;
};

// класс событий, соответствующий сигналу CANCEL_SIG
struct CancelEvt : public QEvent {
    CancelEvt() { sig = CANCEL_SIG; }
};

// класс иерархического автомата sm
class Hsm : public QHsm {
    public:
        Hsm() : QHsm((QStateHandler)&initial) { init(); }

    private:
        // псевдосостояние initial state
        static QState initial(Hsm* me, const QEvent* e) {
            return Q_TRAN(&superState);
        }

        static QState superState(Hsm* me, const QEvent* e) {
            switch (e->sig) {
                case Q_ENTRY_SIG: {
                    me->count = 10;
                    return Q_HANDLED();
                }
                case Q_INIT_SIG: {
                    return Q_TRAN(&stateA);
                }
                case CANCEL_SIG: {
                    return Q_TRAN(&stateC);
                }
            }

            /* QHsm::top - самое верхнее суперсостояние,
             * которое просто возвращает Q_HANDLED(). */
            return Q_SUPER(&QHsm::top);
        }

        static QState stateA(Hsm* me, const QEvent* e) {
            switch (e->sig) {
                case PROCEED_SIG: {
                    return Q_TRAN(&stateB);
                }
            }
            return Q_SUPER(&superState);
        }

        static QState stateB(Hsm* me, const QEvent* e) {
            switch (e->sig) {
                case PROCEED_SIG: {
                    if (me->count > 1) {
                        me->count *= 2;
                        return Q_TRAN(&stateA);
                    }
                    ++me->count;
                    return Q_HANDLED();
                }
                case Q_EXIT_SIG: {
                    cout << "count = " << me->count << endl;
                    me->count = 0;
                    return Q_HANDLED();
                }
            }
            return Q_SUPER(&superState);
        }

        static QState stateC(Hsm* me, const QEvent* e) {
            switch (e->sig) {
                case PROCEED_SIG: {
                    if (static_cast<const ProceedEvt*>(e)->value == 1) {
                        return Q_TRAN(&superState);
                    }
                    break;
                }
            }
            return Q_SUPER(&QHsm::top);
        }

        int count;
};


* This source code was highlighted with Source Code Highlighter.


Теперь создадим объект sm класса Hsm (см. диаграмму последовательности выше) и обработаем десяток событий:
int main(int argc, char* argv[]) {
    Hsm sm; // stateA, count = 10

    for (int i = 0; i < 2; ++i) {
        sm.dispatch(&ProceedEvt());        // stateB, count = 10
        sm.dispatch(&ProceedEvt());        // stateA, count = 0
        sm.dispatch(&ProceedEvt());        // stateB, count = 0
        sm.dispatch(&ProceedEvt());        // stateB, count = 1
        sm.dispatch(&ProceedEvt());        // stateB, count = 2
        sm.dispatch(&ProceedEvt());        // stateA, count = 0
        sm.dispatch(&ProceedEvt());        // stateB, count = 0
        sm.dispatch(&ProceedEvt());        // stateB, count = 1
        sm.dispatch(&ProceedEvt());        // stateB, count = 2
        sm.dispatch(&CancelEvt());        // stateC, count = 2
        sm.dispatch(&CancelEvt());        // stateC, count = 2
        sm.dispatch(&ProceedEvt());        // stateC, count = 2
        sm.dispatch(&ProceedEvt(1));        // stateA, count = 10
    }

    return 0;
}


* This source code was highlighted with Source Code Highlighter.


Заключение


Остальные средства UML statecharts: псевдосостояния initial, final, history, deep history и прочие; отложенная обработка событий, ортогональный компонент состояния, скрытое состояние и т. д. — довольно просто реализуются с помощь «паттернов проектирования состояний» (state design patterns), пять из которых приведены в книге, о которой я расскажу ниже.

Преимущества QP
  • принцип использования в C почти не отличается от C++;
  • является opensource, исходники хорошо прокомментированы;
  • QP уже портирован на множество систем, в частности на GNU Linux системы;
  • существует средство моделирования диаграмм состояний QM от Quantum Leaps;
  • продукцией Quantum Leaps пользуются довольно крупные компании.

О книге

К QP прилагается большая книга «Practical UML Statecharts in C/C++, Second Edition: Event-Driven Programming for Embedded Systems» от основателя компании Quantum Leaps, которая разделена на 2 части: в первой говорится о том, как использовать фреймворк, а вторая посвящена тому, как это работает, как заточить QP под свою систему, как использовать трейсер QS (QSpy), и что такое QP-nano.

Также в учебном пособии приведены сравнения использования фреймворка, паттерна state и switch-метода.

Книга стоит денег, но можно найти в Интернете по запросу «скачать бесплатно».

Ссылки
  • здесь примеры к статье;
  • здесь 32-битный порт QP для GNU Linux x64.

Tags:
Hubs:
+13
Comments 8
Comments Comments 8

Articles