Предисловие
Данная статья, как мне кажется, будет интересна тем, кто знаком с UML диаграммами состояний и последовательности (statecharts diagram и sequence diagram), а также с событийно-ориентированным программированием (event-driven programming).
Вступление
Кроссплатформенный фреймворк QP (Quantum Platform) от компании Quantum Leaps представлен его создателями как средство разработки RTOS на C/C++. Он позволят существенно увеличить скорость разработки и надежность приложений, а также имеет мощный инструментарий по отладке и логированию. Ко всему этому добавляется еще и то, что QP является очень гибким и легким: разделен на множество модулей, почти каждый из которых можно реализовать самому при сборке фреймворка или воспользоваться предложенным решением; множество настроек выполняется во время прекомпиляции.
Программа, написанная с использованием QP, представляет собой реализацию совокупности диаграмм состояний UML statecharts.
Статья является неявным рассмотрением модуля QEP, являющегося основой фреймворка (QEP реализует обработку событий), и написана с целью сподвигнуть на дальнейшее изучение.
Система обработки событий в QP
Для реализации диаграмм состояний QP предоставляет UML-совместимую систему обработки событий. Но стоит обратить внимание на одно обстоятельство: фреймворком поддерживаются только локальные переходы, т. е. переход в подсостояние не сопровождается событием exit, а переход в суперсостояние не возбуждает событие entry. В UML 2 для совместимости с прошлыми версиями была оставлена возможность делать внешние переходы. В более ранних версиях языка доступны только внешние переходы: любой переход сопровождается событием exit и entry для текущего и целевого состояний соответственно.
Разработчику 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 также имеет специальные события, которые могут генерироваться при переходе. Порядок возникновения этих событий и последовательность действий при совершении перехода следующие:
- выполняются атомарные действия перехода;
- событие exit (Q_EXIT_SIG). Возникает в текущем состоянии при выходе из него. Если к тому же происходит выход из суперсостояния, то exit для этого суперсостояния происходит сразу после exit для текущего. Событие exit может быть только обработано (Q_HANDLED);
- автомат переходит в целевое состояние;
- событие entry (Q_ENTRY_SIG). Возникает при входе в состояние. Это событие может быть только обработано;
- событие с сигналом 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-метода.
Книга стоит денег, но можно найти в Интернете по запросу «скачать бесплатно».