Cours:TPqtFNS : Différence entre versions
(→Machine à état fini) |
(→diy transition : principe) |
||
(33 révisions intermédiaires par 2 utilisateurs non affichées) | |||
Ligne 1 : | Ligne 1 : | ||
− | =Machine à | + | =Machine à états finis= |
Les {{Rouge|automates à états finis}} sont en moyen de décrire un système avec des {{Rouge|comportements}} différents. | Les {{Rouge|automates à états finis}} sont en moyen de décrire un système avec des {{Rouge|comportements}} différents. | ||
Ligne 12 : | Ligne 12 : | ||
Vous pouvez consulter la page [https://fr.wikipedia.org/wiki/Automate_fini wikipedia] pour plus de détails. | Vous pouvez consulter la page [https://fr.wikipedia.org/wiki/Automate_fini wikipedia] pour plus de détails. | ||
+ | |||
+ | |||
+ | |||
+ | La librairie QT permet de décrire facilement des {{Rouge|F}}inite {{Rouge|S}}tate {{Rouge|M}}achine à l'aide d'un ensemble de classes listées sur la page [https://doc.qt.io/qt-5/statemachine-api.html statemachine-api.html] | ||
+ | |||
+ | |||
+ | =Prise en main de l'api FSM= | ||
+ | |||
+ | En vous servant de la page [https://doc.qt.io/qt-5/statemachine-api.html statemachine-api.html], écrire un programme répondant au cahier des charges suivant : | ||
+ | |||
+ | *sur l'interface graphique, disposez 2 objets : | ||
+ | **{{Rouge|led}} : de type WidgetLampe (classe donnée ci dessous, cf séance sur la spécialisation de widget pour l'ajouter) | ||
+ | **{{Rouge|bp}} : de type QPushButton | ||
+ | *le comportement est le suivant : | ||
+ | **il y a 3 états différents (stateRouge,stateVert,stateNoir) | ||
+ | **on change d'état à l'appui sur {{Rouge|bp}} | ||
+ | **la {{Rouge|led}} sera : | ||
+ | ***rouge dans l'état stateRouge | ||
+ | ***verte dans l'état stateVert | ||
+ | ***noire dans l'état stateNoir | ||
+ | |||
+ | |||
+ | [[Image:Diagetat1.png |500px]] | ||
+ | |||
+ | {|style="vertical-align:middle; width:100%; text-align:left; " | ||
+ | |- | ||
+ | | {{boîte déroulante/début|titre=widgetlampe.h}} | ||
+ | <source lang=cpp> | ||
+ | #ifndef WIDGETLAMPE_H | ||
+ | #define WIDGETLAMPE_H | ||
+ | |||
+ | #include <QPushButton> | ||
+ | |||
+ | class WidgetLampe : public QPushButton | ||
+ | { | ||
+ | Q_OBJECT | ||
+ | public: | ||
+ | explicit WidgetLampe(QWidget *parent = 0); | ||
+ | |||
+ | signals: | ||
+ | |||
+ | public slots: | ||
+ | void changeCouleur(int etat); | ||
+ | void setRouge(); | ||
+ | void setVert(); | ||
+ | void setNoir(); | ||
+ | |||
+ | }; | ||
+ | |||
+ | #endif // WIDGETLAMPE_H | ||
+ | </source> | ||
+ | {{boîte déroulante/fin}} | ||
+ | ||{{boîte déroulante/début|titre=widgetlampe.cpp}} | ||
+ | <source lang=cpp> | ||
+ | #include "widgetlampe.h" | ||
+ | #include <QDebug> | ||
+ | |||
+ | WidgetLampe::WidgetLampe(QWidget *parent) : QPushButton(parent) | ||
+ | { | ||
+ | this->setDisabled(true); | ||
+ | } | ||
+ | |||
+ | void WidgetLampe::changeCouleur(int etat) | ||
+ | { | ||
+ | qDebug()<<"change"; | ||
+ | QString style = "background-color: rgb(%1, %2, %3);"; | ||
+ | this->setStyleSheet(style.arg(etat*25).arg(0).arg(0)); | ||
+ | } | ||
+ | |||
+ | void WidgetLampe::setRouge() | ||
+ | { | ||
+ | qDebug()<<"rouge"; | ||
+ | QString style = "background-color: rgb(%1, %2, %3);"; | ||
+ | this->setStyleSheet(style.arg(255).arg(0).arg(0)); | ||
+ | } | ||
+ | |||
+ | void WidgetLampe::setVert() | ||
+ | { | ||
+ | qDebug()<<"vert"; | ||
+ | QString style = "background-color: rgb(%1, %2, %3);"; | ||
+ | this->setStyleSheet(style.arg(0).arg(255).arg(0)); | ||
+ | } | ||
+ | |||
+ | void WidgetLampe::setNoir() | ||
+ | { | ||
+ | qDebug()<<"noir"; | ||
+ | QString style = "background-color: rgb(%1, %2, %3);"; | ||
+ | this->setStyleSheet(style.arg(0).arg(0).arg(0)); | ||
+ | } | ||
+ | </source> | ||
+ | {{boîte déroulante/fin}} | ||
+ | |} | ||
+ | |||
+ | |||
+ | {{Question|Ajouter un bouton bpRecule dans l'interface graphique qui permettra de parcourir les états dans le sens inverse}} | ||
+ | |||
+ | [[Image:Diagetat1aa.png |500px]] | ||
+ | |||
+ | =Ajout de sous-états= | ||
+ | |||
+ | Dans certains cas, il peut être intéressant d'avoir une machine d'état qui décrit le comportement d'un état, une machine d'état dans la machine d'état. | ||
+ | |||
+ | Ceci est décrit sur [https://doc.qt.io/qt-5/statemachine-api.html#sharing-transitions-by-grouping-states la page qt à cet endroit]. | ||
+ | |||
+ | |||
+ | On souhaite ajouter au diagramme précédent une étape de clignotement : | ||
+ | *on ajoute 1 état {{Rouge|stateClignote}} | ||
+ | *on ajoute 2 {{Rouge|sous-états}} clignoteOn et clignoteOff | ||
+ | *il nous faut un objet {{Rouge|tictoc}} de type QTimer | ||
+ | **le signal {{Rouge|timeout}} permettra la transition entre les sous-états | ||
+ | **on veillera à mettre en route/arrêter le timer au bon moment | ||
+ | |||
+ | '''Remarque''' : il existe 2 slots {{Rouge|start}} dans la classe QTimer, il faut sélectionner le bon : | ||
+ | <source lang=cpp> | ||
+ | connect(s3, &QState::entered, | ||
+ | &ticToc, QOverload<>::of(&QTimer::start)); | ||
+ | </source> | ||
+ | |||
+ | [[Image:Diagetat2.png | 500px]] | ||
+ | |||
+ | =diy transition : principe= | ||
+ | |||
+ | Pour le moment les transitions sont valides au moment de l'émission du signal choisi. | ||
+ | |||
+ | On peut créer un nouveau type de transition ( donc une nouvelle classe ) qui permettra par exemple d'activer ou non la transition en fonction de l'état d'une valeur binaire au moment de l'émission du signal. | ||
+ | |||
+ | <source lang=cpp> | ||
+ | // on ajoute un transition depuis l'état s1 | ||
+ | // qui sera vérifiée au moment de l'appui sur le bouton bpChange | ||
+ | ConditionnalTransition * t3; | ||
+ | t3=new ConditionnalTransition(ui->bpChange,&QPushButton::clicked, s1); | ||
+ | // l'étape d'arrivée sera s3 | ||
+ | t3->setTargetState(s3); | ||
+ | // la validité de la transition est activée | ||
+ | // si la case à cocher de l'interface graphique est cochée | ||
+ | connect(ui->choix,&QCheckBox::toggled, | ||
+ | t3,&ConditionnalTransition::setEnable); | ||
+ | // ou | ||
+ | // la validité de la transition est activée | ||
+ | // si la case à cocher de l'interface graphique est décochée | ||
+ | connect(ui->choix,&QCheckBox::toggled, | ||
+ | t3,&ConditionnalTransition::setDisable); | ||
+ | // attention à modifier la valeur initiale | ||
+ | t3->setInitState(ui->choix->isChecked()); | ||
+ | </source> | ||
+ | |||
+ | |||
+ | {{Question|Modifier votre programme pour avoir le comportement suivant :}} | ||
+ | *l'état {{Rouge|initial}} est {{Rouge|stateNoir}} | ||
+ | *à l'{{Rouge|appui sur bp}} : | ||
+ | **si {{Rouge|"la case est cochée"}} alors on passe à l'état {{Rouge|stateVert}} | ||
+ | **si {{Rouge|"la case est décochée"}} alors on passe à l'état {{Rouge|stateRouge}} | ||
+ | *de {{Rouge|stateRouge}} on revient à {{Rouge|stateNoir}} à l'{{Rouge|appui sur bp}} | ||
+ | *de {{Rouge|stateVert}} on revient à {{Rouge|stateNoir}} à l'{{Rouge|appui sur bp}} | ||
+ | |||
+ | [[Image:Diagetat3.png |500px]] | ||
+ | |||
+ | {|style="vertical-align:middle; width:100%; text-align:left; " | ||
+ | |- | ||
+ | | {{boîte déroulante/début|titre=conditionnaltransition.h}} | ||
+ | <source lang=cpp> | ||
+ | #ifndef CONDITIONNALTRANSITION_H | ||
+ | #define CONDITIONNALTRANSITION_H | ||
+ | |||
+ | #include <QSignalTransition> | ||
+ | #include <QCheckBox> | ||
+ | |||
+ | class ConditionnalTransition : public QSignalTransition | ||
+ | { | ||
+ | Q_OBJECT | ||
+ | |||
+ | public: | ||
+ | ConditionnalTransition(const QObject *sender, const char * signal, QState *sourceState = nullptr); | ||
+ | |||
+ | template <typename Func> | ||
+ | ConditionnalTransition(const typename QtPrivate::FunctionPointer<Func>::Object *obj, | ||
+ | Func sig, QState *srcState = nullptr) | ||
+ | : ConditionnalTransition(obj, QMetaMethod::fromSignal(sig).methodSignature().constData(), srcState) | ||
+ | { | ||
+ | } | ||
+ | |||
+ | bool eventTest(QEvent *event) override; | ||
+ | void setInitState(bool newEtat); | ||
+ | |||
+ | private: | ||
+ | bool etat; | ||
+ | |||
+ | public slots: | ||
+ | void setEnable(bool newEtat); | ||
+ | void setDisable(bool newEtat); | ||
+ | |||
+ | signals: | ||
+ | |||
+ | }; | ||
+ | |||
+ | #endif // CONDITIONNALTRANSITION_H | ||
+ | </source> | ||
+ | {{boîte déroulante/fin}} | ||
+ | ||{{boîte déroulante/début|titre=conditionnaltransition.cpp}} | ||
+ | <source lang=cpp> | ||
+ | #include "conditionnaltransition.h" | ||
+ | |||
+ | ConditionnalTransition::ConditionnalTransition(const QObject *sender, const char * signal, QState *sourceState) | ||
+ | : QSignalTransition{sender, signal, sourceState} | ||
+ | { | ||
+ | etat=false; | ||
+ | } | ||
+ | |||
+ | void ConditionnalTransition::setEnable(bool newEtat) | ||
+ | { | ||
+ | etat = newEtat; | ||
+ | } | ||
+ | |||
+ | void ConditionnalTransition::setDisable(bool newEtat) | ||
+ | { | ||
+ | etat = ! newEtat; | ||
+ | } | ||
+ | |||
+ | bool ConditionnalTransition::eventTest(QEvent *event) | ||
+ | { | ||
+ | if (etat==true)return QSignalTransition::eventTest(event); | ||
+ | else return false; | ||
+ | } | ||
+ | |||
+ | void ConditionnalTransition::setInitState(bool newEtat) | ||
+ | { | ||
+ | etat=newEtat; | ||
+ | } | ||
+ | </source> | ||
+ | {{boîte déroulante/fin}} | ||
+ | |} | ||
+ | |||
+ | =diy transition : à vous ! = | ||
+ | |||
+ | on ajoute un QSlider sur l'interface graphique qui donnera une valeur entre 0 et 99 | ||
+ | |||
+ | {{Question|Utiliser ce QSlider pour valider une transition en comparant par rapport à un seuil :}} | ||
+ | *créer une nouvelle classe qui hérite de QSignalTransition | ||
+ | *modifier pour valider l'état en fonction du QSlider | ||
=Ressources= | =Ressources= |
Version actuelle datée du 30 septembre 2024 à 09:33
Sommaire
Machine à états finis
Les automates à états finis sont en moyen de décrire un système avec des comportements différents.
A chaque état est associé un comportement.
Des transitions permettent de passer d'un état à un autre
A chaque transition est associé une condition pour valider ce changement d'état.
Vous pouvez consulter la page wikipedia pour plus de détails.
La librairie QT permet de décrire facilement des Finite State Machine à l'aide d'un ensemble de classes listées sur la page statemachine-api.html
Prise en main de l'api FSM
En vous servant de la page statemachine-api.html, écrire un programme répondant au cahier des charges suivant :
- sur l'interface graphique, disposez 2 objets :
- led : de type WidgetLampe (classe donnée ci dessous, cf séance sur la spécialisation de widget pour l'ajouter)
- bp : de type QPushButton
- le comportement est le suivant :
- il y a 3 états différents (stateRouge,stateVert,stateNoir)
- on change d'état à l'appui sur bp
- la led sera :
- rouge dans l'état stateRouge
- verte dans l'état stateVert
- noire dans l'état stateNoir
widgetlampe.h #ifndef WIDGETLAMPE_H
#define WIDGETLAMPE_H
#include <QPushButton>
class WidgetLampe : public QPushButton
{
Q_OBJECT
public:
explicit WidgetLampe(QWidget *parent = 0);
signals:
public slots:
void changeCouleur(int etat);
void setRouge();
void setVert();
void setNoir();
};
#endif // WIDGETLAMPE_H
|
widgetlampe.cpp #include "widgetlampe.h"
#include <QDebug>
WidgetLampe::WidgetLampe(QWidget *parent) : QPushButton(parent)
{
this->setDisabled(true);
}
void WidgetLampe::changeCouleur(int etat)
{
qDebug()<<"change";
QString style = "background-color: rgb(%1, %2, %3);";
this->setStyleSheet(style.arg(etat*25).arg(0).arg(0));
}
void WidgetLampe::setRouge()
{
qDebug()<<"rouge";
QString style = "background-color: rgb(%1, %2, %3);";
this->setStyleSheet(style.arg(255).arg(0).arg(0));
}
void WidgetLampe::setVert()
{
qDebug()<<"vert";
QString style = "background-color: rgb(%1, %2, %3);";
this->setStyleSheet(style.arg(0).arg(255).arg(0));
}
void WidgetLampe::setNoir()
{
qDebug()<<"noir";
QString style = "background-color: rgb(%1, %2, %3);";
this->setStyleSheet(style.arg(0).arg(0).arg(0));
}
|
Ajouter un bouton bpRecule dans l'interface graphique qui permettra de parcourir les états dans le sens inverse
Ajout de sous-états
Dans certains cas, il peut être intéressant d'avoir une machine d'état qui décrit le comportement d'un état, une machine d'état dans la machine d'état.
Ceci est décrit sur la page qt à cet endroit.
On souhaite ajouter au diagramme précédent une étape de clignotement :
- on ajoute 1 état stateClignote
- on ajoute 2 sous-états clignoteOn et clignoteOff
- il nous faut un objet tictoc de type QTimer
- le signal timeout permettra la transition entre les sous-états
- on veillera à mettre en route/arrêter le timer au bon moment
Remarque : il existe 2 slots start dans la classe QTimer, il faut sélectionner le bon :
connect(s3, &QState::entered,
&ticToc, QOverload<>::of(&QTimer::start));
diy transition : principe
Pour le moment les transitions sont valides au moment de l'émission du signal choisi.
On peut créer un nouveau type de transition ( donc une nouvelle classe ) qui permettra par exemple d'activer ou non la transition en fonction de l'état d'une valeur binaire au moment de l'émission du signal.
// on ajoute un transition depuis l'état s1
// qui sera vérifiée au moment de l'appui sur le bouton bpChange
ConditionnalTransition * t3;
t3=new ConditionnalTransition(ui->bpChange,&QPushButton::clicked, s1);
// l'étape d'arrivée sera s3
t3->setTargetState(s3);
// la validité de la transition est activée
// si la case à cocher de l'interface graphique est cochée
connect(ui->choix,&QCheckBox::toggled,
t3,&ConditionnalTransition::setEnable);
// ou
// la validité de la transition est activée
// si la case à cocher de l'interface graphique est décochée
connect(ui->choix,&QCheckBox::toggled,
t3,&ConditionnalTransition::setDisable);
// attention à modifier la valeur initiale
t3->setInitState(ui->choix->isChecked());
Modifier votre programme pour avoir le comportement suivant :
- l'état initial est stateNoir
- à l'appui sur bp :
- si "la case est cochée" alors on passe à l'état stateVert
- si "la case est décochée" alors on passe à l'état stateRouge
- de stateRouge on revient à stateNoir à l'appui sur bp
- de stateVert on revient à stateNoir à l'appui sur bp
conditionnaltransition.h #ifndef CONDITIONNALTRANSITION_H
#define CONDITIONNALTRANSITION_H
#include <QSignalTransition>
#include <QCheckBox>
class ConditionnalTransition : public QSignalTransition
{
Q_OBJECT
public:
ConditionnalTransition(const QObject *sender, const char * signal, QState *sourceState = nullptr);
template <typename Func>
ConditionnalTransition(const typename QtPrivate::FunctionPointer<Func>::Object *obj,
Func sig, QState *srcState = nullptr)
: ConditionnalTransition(obj, QMetaMethod::fromSignal(sig).methodSignature().constData(), srcState)
{
}
bool eventTest(QEvent *event) override;
void setInitState(bool newEtat);
private:
bool etat;
public slots:
void setEnable(bool newEtat);
void setDisable(bool newEtat);
signals:
};
#endif // CONDITIONNALTRANSITION_H
|
conditionnaltransition.cpp #include "conditionnaltransition.h"
ConditionnalTransition::ConditionnalTransition(const QObject *sender, const char * signal, QState *sourceState)
: QSignalTransition{sender, signal, sourceState}
{
etat=false;
}
void ConditionnalTransition::setEnable(bool newEtat)
{
etat = newEtat;
}
void ConditionnalTransition::setDisable(bool newEtat)
{
etat = ! newEtat;
}
bool ConditionnalTransition::eventTest(QEvent *event)
{
if (etat==true)return QSignalTransition::eventTest(event);
else return false;
}
void ConditionnalTransition::setInitState(bool newEtat)
{
etat=newEtat;
}
|
diy transition : à vous !
on ajoute un QSlider sur l'interface graphique qui donnera une valeur entre 0 et 99
Utiliser ce QSlider pour valider une transition en comparant par rapport à un seuil :
- créer une nouvelle classe qui hérite de QSignalTransition
- modifier pour valider l'état en fonction du QSlider