Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Конечный автомат на Qt
Форум на CrossPlatform.RU > Библиотеки > Qt > Qt Общие вопросы
Litkevich Yuriy
Нашел в лаборатории у тролей Qt State Machine Framework
SABROG
Я не совсем понимаю предназначение. В качестве примера там приводится "машина" (замкнутый цикл?) из трех состояний, для кнопки. Жмем первый раз - меняется текст, у кнопки состояние 2, жмем второй раз - снова меняется текст, у кнопки состояние 3. Жмем третий раз - устанавливается первоначальный текст и состояние кнопки 1. Так вот чем это отличается от простого числового параметра у объекта?
Litkevich Yuriy
Цитата(SABROG @ 1.2.2009, 0:13) *
Так вот чем это отличается от простого числового параметра у объекта?
параметр описывает текущее состояние. Но его можно изменять. И там приведен пример изменения состояния.

Пример удачнее видимо будет на "флажке" (QCheckBox), который имеет три состояния, менятся они могут только поочереди - простой пример конечного автомата.
SABROG
Почитал в инете примеры со строками на эту тему тут, тут и тут, а так и не понял какое преимущество он дает.

Ну, а чем тогда конечный автомат лучше для QCheckBox по сравнению с этим кодом?

    Qt::CheckState state = checkBox1->checkState();
    if (checkBox1->isTristate())
    {
        checkBox1->setCheckState((state+1)%3); //чеким по-кругу 0,1,2,0,1,2...n
    }
    else
    {
        checkBox1->setCheckState(state^Qt::Checked); //xor'им 0 или 2 с 2 (Qt::Checked).
    }

Возможно и не работает, но идея должна быть понятна
Litkevich Yuriy
Цитата(SABROG @ 1.2.2009, 3:04) *
Возможно и не работает, но идея должна быть понятна
вот идея как раз не понятна. код нечитаем.
И как он изменится если введем четвертое состояние (неважно зачем)?
SABROG
Цитата(Litkevich Yuriy @ 1.2.2009, 0:14) *
И как он изменится если введем четвертое состояние (неважно зачем)?


Придется всего-лишь поменять 3 на 4.
Antrix
Конечный автомат, это ориентированый граф, который используется для переключения состояний. Вот например ты пишешь игру. У игры есть три состояния:
- bool is GameStart = false; //игра началась
- bool isPause = false; //пауза
- bool is gameEnd = false;//конец игры

Но вот если подумать, то при таком раскладе, возможен вариант, когда все перемены true, тоесть игра началась, и в тоже время пауза, и игра закончилась, что не есть правильно. Так как, тип bool имет 2 состояния true или false, и переменных типа bool у нас 3, то и вариантов комбинаций 3 в квадрате = 9, хатя нам реально нужно всего 3. Что б это исправить, используют конечный автомат - отдельная функция или класс, которая и управляеет переключением состояний.
Первое состояние - игра началась, значит:
- bool is GameStart = true;
- bool isPause = false;
- bool is gameEnd = false;
Второе состояние - пауза:
-- bool is GameStart = false;
- bool isPause = true;
- bool is gameEnd = false;
Третье состояние - конец:
-- bool is GameStart = false;
- bool isPause = false;
- bool is gameEnd = true;
Причем возможны только такие переходы;
игра началась <--> пауза;
игра началась --> конец игры.
Вот хороший пример с лифтом, правда на паскале:
http://books.google.com/books?id=JO9-7Riwl...1&ct=result
kwisp
Цитата(Antrix @ 1.2.2009, 16:23) *
У игры есть три состояния:
- bool is GameStart = false; //игра началась
- bool isPause = false; //пауза
- bool is gameEnd = false;//конец игры


мне кажется пример, мягко говоря, немного неудачен.
не понятно зачем 3 переменные bool???
если не ошибаюсь чаще используется перечисления

enum EStation {e_NOPALY,e_PLAY,e_PAUSE} m_station;


Раскрывающийся текст
Цитата
Первое состояние - игра началась, значит:
- bool is GameStart = true;
- bool isPause = false;
- bool is gameEnd = false;

m_station = e_PLAY;

Цитата
Второе состояние - пауза:
-- bool is GameStart = false;
- bool isPause = true;
- bool is gameEnd = false;

m_station = e_PAUSE;

Цитата
Третье состояние - конец:
-- bool is GameStart = false;
- bool isPause = false;
- bool is gameEnd = true;

m_station = e_NOPLAY;

что касается Qt State Machine Framework, мне кажется эта штука сгодится только для небольших реализаций как в примере с кнопкой. для сложных машин состояний придется все равно самому реализовывать код...
Litkevich Yuriy
Цитата(kwisp @ 1.2.2009, 20:11) *
мне кажется пример, мягко говоря, немного неудачен.
не понятно зачем 3 переменные bool???
угу

SABROG, найду время приведу реальный пример, на основе Охранно-Пожарного Приемно-Контрольного Прибора (ОП ПКП).
kwisp
Цитата(Litkevich Yuriy @ 1.2.2009, 17:19) *
.... на основе Охранно-Пожарного Приемно-Контрольного Прибора (ОП ПКП).

:blink:
может что попроще есть???
Antrix
Возможно пример неудачен, ну я старался объснить как мог :rolleyes: . А с переменными bool я думал будет легче понять.
Litkevich Yuriy
вообще любой пример не удачен, если все состояния в нем изменяются последовательно.
Нематематическое, а электрощиско-практическое определение:
Конечный автомат (КА) - автомат выходной сигнал которого (читай его состояние) зависит от текущего входного сигнала и текущего состояния КА (можно представить в виде предыстории его входных сигналов)

Пожарный шлейф сигнализации (ШС) обладает такими свойствами:
1) снят с ошраны
2) на охране
3) сигнал (состояние) "Внимание"
4) сигнал (состояние) "Пожар"
5) постановка в движении

Обычно пожарные ШС - круглосуточные, т.е. их нельзя снять с охраны (человек нажимает кнопку "Снять с охраны"). вместо снятия с охраны для любых круглосуточных (как пожарных так и охранных ШС) осуществляют "перепостановку". При перепостановке снимается питание с ШС и с линий питаний датчиков, чтобы сброить их состояние. Затем подается питание, с этого момента состояние ШС = №5 (постановка в движении), нужно для установления рабочих режимов датчиков.
т.е.
6) перепостановка (переход в сост. №1 и последующий автоматический переход в состояние №5)

И работа КА выглядит так:
Подаем питание прибора, в КА поступает входной сигнал "Поставить на охрану"
тогда КА переходит в состояние №5, по истечении некоторого времени КА переходит в одно из состояний №2 или №3 или №4

Из состояния №2 может перейти в №6 или №3 или №4
Из состояния №3 может перейти в №6 или №4
Из состояния №4 может перейти в №6

Т.е. состояния непоследовательные
---
Написал наспех может чего пропустил
kwisp
Litkevich Yuriy,
Цитата
вообще любой пример не удачен, если все состояния в нем изменяются последовательно.

в любом наборе.
а пример с кнопкой в Qt State Machine Framework как раз такой. нужно попробовать реализовать "простой" пример, состояний 5 хотя бы с запрещенными переходами непоследовательный может быть не с одним уровнем истории состояний своим (у каждого наверное свой) методом и способом который предлагает Qt State Machine Framework
тогда станет все наглядно ясно.
Litkevich Yuriy
Цитата(kwisp @ 1.2.2009, 21:11) *
а пример с кнопкой в Qt State Machine Framework как раз такой
а там как раз во 2 коментарии сказано о не удачности примера

мне собственно стало интересно как они реализовали работу с КА:
QStateMachine machine;

QState *s1 = new QState(machine.rootState());
s1->setPropertyOnEntry(&button, ”text”, ”In s1?);

QState *s2 = new QState(machine.rootState());
s2->setPropertyOnEntry(&button, ”text”, ”In s2?);

QState *s3 = new QState(machine.rootState());
s3->setPropertyOnEntry(&button, ”text”, ”In s3?);

s1->addTransition(&button, SIGNAL(clicked()), s2);
s2->addTransition(&button, SIGNAL(clicked()), s3);
s3->addTransition(&button, SIGNAL(clicked()), s1);


machine.setInitialState(s1);
machine.start();
Т.е. реализовано добавление переходов (addTransition) и используются сигналы, такое мне в голову не приходило.
Эту штуку можно использовать в программах опрашивающих разные устройства, например, на шине ModBas
Tonal
Цитата(SABROG @ 1.2.2009, 3:04) *
Почитал в инете примеры со строками на эту тему тут, тут и тут, а так и не понял какое преимущество он дает.
Ну, а чем тогда конечный автомат лучше для QCheckBox по сравнению с этим кодом?
    Qt::CheckState state = checkBox1->checkState();
    if (checkBox1->isTristate())
    {
        checkBox1->setCheckState((state+1)%3); //чеким по-кругу 0,1,2,0,1,2...n
    }
    else
    {
        checkBox1->setCheckState(state^Qt::Checked); //xor'им 0 или 2 с 2 (Qt::Checked).
    }

Возможно и не работает, но идея должна быть понятна

Этот код - тот же конечный автомат только запрограммированный через if-ы.
Конечный автомат - это способ (математический формализм) для описаия поведения некоторой системы через состояния и переходы между ними.
Конечным автоматом можно описать поведения GUI программы, работу с устройством, сетевым протоколом, разбор текстового файла...
Те же регулярные выражения реализуются через конечные автоматы - строка регэкспа разбирается, по ней строится КА (конечный автомат), после чего через него пропускается поток символов - каждый символ - событие, переводящее КА в какое-то состояние. :)

Так что вопрос "что лучше" тут бессмысленен.
Если научится работать с КА и находить их в задачах, то будет проще работать. :)
SABROG
Видимо if'ы обеспечивают как раз тот механизм "непоследовательности" автомата, в то время как операция взяти по модулю - последовательное выполнение.
Litkevich Yuriy
вообще чаще используют переключатель:
примерно так
void HModBus (void)
{
  static byte fase=0;
  volatile UINT clcCRC;
  volatile UINT pakCRC;

  //фазы состояний движка ModBus
  enum fase
    {
        FASE_START_RX,    /* запускаем прием */
        FASE_END_RX,    /* ждем завершения приема */
        FASE_NODE_CHK,    /* проверяем номер узла в сети */
        FASE_CRC16_CHK,    /* проверяем CRC16 */
        FASE_FUNK_CHK,    /* проверяем номер номер функции */
        //FASE_FUNK_WRK,    /*  */
        FASE_START_TX,    /* передаем обратно */
        FASE_END_TX        /* ждем завершения передачи */
    };
    
    switch (fase)
    {
        //====== ЗАПУСК ПРИЕМА ======
        case FASE_START_RX:    
        {
            MbusRxRun();
            fase=FASE_END_RX;
            break;
        };
        //====== ЖДЕМ ЗАВЕРШЕНИЯ ПРИЕМА ======
        case FASE_END_RX:
        {
            if (mbus_f_rxend)
            {
                fase=FASE_NODE_CHK;
            }
            break;
        };
        //====== ПРОВЕРЯЕМ ПРИНЯТОЕ - НОМЕР УЗЛА ======
        case FASE_NODE_CHK:
        {
            BYTE temp;
            
            temp=*(mbus_buff.buff+MBUS_FIELD_NODE);
            if ((temp!=mbus_cfg.node)&&(temp!=MMBUS_ALL_NODE)&&(temp!=MMBUS_PRG_NODE))
            {
                fase=FASE_START_RX;    // Пакет не нам, в любом случае
            }
            else
            {
                fase=FASE_CRC16_CHK;
            }
            break;
        };    
        //====== ПРОВЕРЯЕМ ПРИНЯТОЕ - CRC16 ======
        case FASE_CRC16_CHK:
        {
            BYTE *pbuff=mbus_buff.buff;
            
            // вызвать функцию проверки CRC16
            clcCRC=CalcCRC16(pbuff, rxcnt-2);
            
            pakCRC=pbuff[rxcnt-2];    // младший байт
            pakCRC|=((UINT)pbuff[rxcnt-1])<<8;    // старший байт 03 04 83 00
            
            if (clcCRC!=pakCRC)
            {
                fase=FASE_START_RX;
            }
            else /**/
            {
                fase=FASE_FUNK_CHK;
            }
            break;
        };
        //====== ПРОВЕРЯЕМ/ИСПОЛНЯЕМ ПРИНЯТУЮ ФУНКЦИЮ =====
        case FASE_FUNK_CHK:
        {
            /* если определена функция проверки/исполнения
                ModBus'овских функций, то исполняем ее */
            if(modbusFunc)
            {
                if (modbusFunc()) fase=FASE_START_RX;    //функция вернула не нуль->передавать не надо
                else fase=FASE_START_TX;    // иначе передаем ответ
            }
            else
            {
                #ifdef DBG_MBUS_ON
                    fase=FASE_START_TX;
                #else  // !DBG_MBUS_ON
                    fase=FASE_START_RX;
                #endif // DBG_MBUS_ON            
            }
            break;
        };            
        //====== ПЕРЕДАЕМ ОТВЕТ ======
        case FASE_START_TX:
        {
            MbusTxRun();
            fase=FASE_END_TX;
            break;
        };
        //====== ЖДЕМ ЗАВЕРШЕНИЯ ПЕРЕДАЧИ ======
        case FASE_END_TX:
        {
            if (mbus_f_txend)
            {
                fase=FASE_START_RX;
            }
            break;
        };
    }

} // END OF HModBus()
kuzulis
ООО!!! Хоть тема и старая, но подниму... Наткнулся на нее и свои 5 копеек добавлю.

Цитата
вообще чаще используют переключатель:


Дадада.. Именно! Есть такая штука - как SWITCH технология программирования в которой и используется такой подход. Чаще всего оно применяется в автоматике, АСУТП, при программировании ПЛК и т.п.

В инете оч много литературы по этому поводу (кратко ознакомится на википедии можно).

Этот подход очень удобен, но необычен для основного контингента программистов, которые привыкли думать "классически". :)
Этот подход в сложных алгоритмах со множеством состояний избавляет нас от большого кол-ва ошибок, позволяя делить алгоритм на несколько КА по вложенности и т.п.

Как я к примеру делаю:
1. Делю алгоритм на различные КА, выполняющие определенные функции
2. Для каждого КА рисую граф переходов
3. По графу пишу программу

При этом каждый КА имеет у меня набор свойств:
1. Набор состояний
2. Набор входных воздействий

И в зависимости от текущего воздействия на автомат и его состояния выполняется некий алгоритм, который переводит КА в другие состояния и т.п. В общем, все оч просто и удобно. :)

Я сам нечто похожее делаю для ПЛК (правда к Qt4 это никоим образом не относится)... Результаты - впечатляют! Ошибок по сравнению с "классикой" гораздо меньше и код гораздо понятнее и читабельней!

Анна
Вот сейчас читаю про паттерн "Состояние".
В принципе, это то, что мне надо. При срабатывании QAction программа переходила бы в то или иное состояние, в которых по-разному реагировала бы на срабатывания других QAction (например, некоторые действия должны игнорироваться при одном состоянии и выполняться при другом).
Но вот как увязать описанный в книге паттерн с классами QState и QStateMachine представить не могу.

Может, кто подскажет?
Книжные примеры брала у Э.Фримен и Э.Фримен "Паттерны проектирования" и Гамма, Хельм, Джонсон и Влиссидес "Приёмы объектно-ориентированного проектирования"
Litkevich Yuriy
Цитата(Анна @ 18.12.2017, 19:31) *
Но вот как увязать описанный в книге паттерн с классами QState и QStateMachine представить не могу.
Я про этот патерн не читал.

Один из вариантов использования QStateMachine выглядит примерно так:
Сначала описываем сам автомат (без описания полезной работы)
void MyClass::createMachine()
{

_meassureMachine = new QStateMachine(this);

// Список состояний и связь их со слотами полезной работы
QState *meassurePrepareState = new QState();
meassurePrepareState->setObjectName("_meassure_meassurePrepareState");
connect(meassurePrepareState, SIGNAL(entered()), this, SLOT(meassurePrepareStateSlot()));
_meassureMachine->addState(meassurePrepareState);

_meassureMachine->setInitialState(meassurePrepareState);

QState *measureState = new QState();
measureState->setObjectName("_meassure_measureState");
connect(measureState, SIGNAL(entered()), this, SLOT(measureRunStateSlot()));
_meassureMachine->addState(measureState);


QState *repeatState = new QState();
repeatState->setObjectName("_meassure_repeatState");
connect(repeatState, SIGNAL(entered()), this, SLOT(meassureRepeatStateSlot()));
_meassureMachine->addState(repeatState);

QState *twuErrorState = new QState();
twuErrorState->setObjectName("_meassure_twuErrorState");
connect(twuErrorState, SIGNAL(entered()), this, SLOT(meassureTwuErrorStateSlot()));
_meassureMachine->addState(twuErrorState);


// переходы между состояниями
meassurePrepareState->addTransition(_timerNext, SIGNAL(timeout()), measureState);

measureState->addTransition(measureState, SIGNAL(finished()), repeatState);
measureState->addTransition(this, SIGNAL(twuFailed()), twuErrorState);

}



Далее/ранее по коду создаём слоты которые выполняются при входе в соответствующее состояние
void MyClass::meassurePrepareStateSlot()
{
...
}


void MyClass::measureRunStateSlot()
{
...
}


void MyClass::meassureRepeatStateSlot()
{
...
}

void MyClass::meassureTwuErrorStateSlot()
{
...
}


Ну а QAction умеет посылать сигналы.

П.С.
Выдрано как попало из реального кода
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Форум IP.Board © 2001-2024 IPS, Inc.