This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
in202:seance_6:jalon_4 [2021/05/02 11:55] bmonsuez [Question n°2] |
in202:seance_6:jalon_4 [2022/11/18 10:47] (current) |
||
---|---|---|---|
Line 28: | Line 28: | ||
et de même pour le port 2, 3, .... | et de même pour le port 2, 3, .... | ||
- | On va profite des objets pour simplifier l'écriture. Pour ce faire, nous allons définir une classe interne de la classe ''button'' qui va permetter de fournir les trois fonctions de base ''Pressed'' corresspondant à l'événement le bouton a été pressé, ''Released'' que le bouton a été relâché, ''Changed'' que l'état du bouton a changé. Ces fonctions vont simplement appelé pour le bouton si un bouton est présent sur le port donné la méthode virtuelle ''OnPressed()'', (resp. ''OnReleased()'' ou ''OnChanged()'') défini dans la classe ''Button''. | + | On va profite des objets pour simplifier l'écriture. Pour ce faire, nous allons définir une classe qui va permettre de créer de manière récursive les treizes instances des fonctions de base ''Pressed'' corresspondant à l'événement le bouton a été pressé, ''Released'' que le bouton a été relâché, ''Changed'' que l'état du bouton a changé. |
+ | Ces fonctions vont simplement appelé pour le bouton si un bouton est présent sur le port donné la méthode virtuelle ''OnPressed()'', (resp. ''OnReleased()'' ou ''OnChanged()'') définies dans la classe ''Button''. Nous appelons cette classe ''_ButtonEvents''. | ||
<code cpp> | <code cpp> | ||
- | class Button | + | template <int... _Is> class _ButtonEvents; |
+ | </code> | ||
+ | |||
+ | Ceci déclare la classe ''ButtonEvents'' comme étant une classe qui peut-être paramétrée par aucune, une ou plusieurs valeurs entières, c'est pourquoi il y a les ''...'' après ''int''. | ||
+ | |||
+ | Ainsi, nous pouvons écrire : | ||
+ | |||
+ | <code cpp> | ||
+ | _ButtonEvents<> emptyEvents; | ||
+ | _ButtonEvents<0> emptyEvents; | ||
+ | _ButtonEvents<0, 1> emptyEvents; | ||
+ | _ButtonEvents<0, 1, ..., 10> emptyEvents; | ||
+ | </code> | ||
+ | |||
+ | Maintenant, nous allons utiliser définir un schéma de template récursif. | ||
+ | |||
+ | Nous définissons la classe vide, ie. le noeud terminal qui est vide. | ||
+ | <code cpp> | ||
+ | template<> class _ButtonEvents {}; | ||
+ | </code> | ||
+ | |||
+ | <code cpp> | ||
+ | template<int First, int... _Rest> | ||
+ | class _ButtonEvents <First, _Rest...> : private _ButtonEvents <_Rest...> | ||
{ | { | ||
private: | private: | ||
- | struct EventHandler | + | static Button* mButton; |
- | { | + | static void Pressed(); |
- | public: | + | static void Released(); |
- | static Button* MonitoredButton; | + | static void Changed() { if (mButton != NULL) mButton->OnChanged(); } |
- | static void Pressed() | + | }; |
- | { | + | |
- | if(MonitoredButton != NULL) | + | |
- | MonitoredButton->OnPressed(); | + | |
- | } | + | |
- | static void Released() | + | |
- | { | + | |
- | if(MonitoredButton != NULL) | + | |
- | MonitoredButton->OnReleased(); | + | |
- | } | + | |
- | static void Changed() | + | |
- | { | + | |
- | if(MonitoredButton != NULL) | + | |
- | MonitoredButton->OnReleased(); | + | |
- | } | + | |
- | }; | + | |
</code> | </code> | ||
- | Ceci va donc nous créer nos 3 fonctions ''Pressed'', ''Released'' et ''Changed'' de type ''void interruptHandler()'' que nous pouvons passer à la fonction ''attachInterupt''. | + | Et voilà, c'est fait, la définition suivante va créer 3 instances des fonctions |
+ | ''Pressed()'', ''Released()'' et''Changed()''. | ||
+ | <code cpp> | ||
+ | _ButtonEvents<0, 1, 2> EventsFor3Ports; | ||
+ | </code> | ||
- | Maintenant, il faut en créer 13. Comment faire ? Simplement en ajoutant un tableau statique correspondant aux 13 ports que nous pouvons monitorer et créant pour chacun des port une classe de type ''EventHandler''. | + | Le compilateur va développer récursivement : |
<code cpp> | <code cpp> | ||
- | public class Button | + | _ButtonEvents <0, 1, 2> : private _ButtonEvents <1, 2> |
{ | { | ||
- | class Button | + | private: |
+ | static Button* mButton; | ||
+ | static void Pressed(); | ||
+ | static void Released(); | ||
+ | static void Changed(); | ||
+ | }; | ||
+ | _ButtonEvents <1, 2> : private _ButtonEvents <2> | ||
{ | { | ||
private: | private: | ||
- | struct EventHandler | + | static Button* mButton; |
- | { | + | static void Pressed(); |
- | public: | + | static void Released(); |
- | static Button* MonitoredButton; | + | static void Changed(); |
- | static void Pressed() | + | }; |
- | { | + | _ButtonEvents <2> : private _ButtonEvents <> |
- | if(MonitoredButton != NULL) | + | { |
- | MonitoredButton->OnPressed(); | + | private: |
- | } | + | static Button* mButton; |
- | static void Released() | + | static void Pressed(); |
- | { | + | static void Released(); |
- | if(MonitoredButton != NULL) | + | static void Changed(); |
- | MonitoredButton->OnReleased(); | + | }; |
- | } | + | </code> |
- | static void Changed() | + | |
- | { | + | |
- | if(MonitoredButton != NULL) | + | |
- | MonitoredButton->OnReleased(); | + | |
- | } | + | |
- | }; | + | |
- | static EventHandler m_EventHandlers[14]; | + | C'est bien, mais il faut ensuite penser à d'une part : |
- | } | + | - passer comme argument à cette objet l'objet à appeller. |
+ | - récupérer la fonction. | ||
+ | |||
+ | Pour récupérer les fonctions, nous allons définir les fonctions suivantes : | ||
+ | |||
+ | <code cpp> | ||
+ | template<int First, int... _Rest> | ||
+ | class _ButtonEvents <First, _Rest...> : private _ButtonEvents <_Rest...> | ||
+ | { | ||
+ | private: | ||
+ | static Button* mButton; | ||
+ | static void Pressed(); | ||
+ | static void Released(); | ||
+ | static void Changed(); | ||
+ | public: | ||
+ | static auto GetPressed(int thePort) | ||
+ | { return thePort == First ? Pressed : _ButtonEvents<_Rest...>::GetPressed(thePort); } | ||
+ | static auto GetReleased(int thePort) | ||
+ | { return thePort == First ? Released : _ButtonEvents<_Rest...>::GetReleased(thePort); } | ||
+ | static auto GetChanged(int thePort) | ||
+ | { return thePort == First ? Changed : _ButtonEvents<_Rest...>::GetChanged(thePort); } | ||
+ | }; | ||
</code> | </code> | ||
- | Un champ ''static'' est un champ qui est partagé par tous les objets ''Button'', il n'y a qu'un seul ''m_EventHandlers'' qui est commun à tous les les objets ''Button''. | + | Le fonctionnement de ces fonctions est le même. Nous passons le numéro de port à la fonction. Soit c'est le même numéro de port et nous renvoyons la référence de la fonction définie dans cette classe, soit au contraire, les fonctions associées à ce numéro de port sont définies dans une classe héritée. Nous appelons donc la fonction de la classe héritée avec ce même numéro de port. |
- | Modifier le constructeur pour que chaque fois que l'on crée un nouveau ''Button'' qui est associé au port ''portNumber'', on pense à modifier le tableau ''m_EventHandlers'' pour qu'il sache que le nouveau ''Button'' créé est associé au port ''portNumber'', donc que l'objet situé à l'indice désigne par le ''portNumber'' sache qu'il doit appelé les actions définies dans l'objet nouvellement créé. | + | Cependant, il faut garantier la terminaison et définir ces fonctions pour une liste de paramètre vide. Donc nous devons ajouter à la classe finale des fonctions qui retournent un pointeur ''NULL''. |
- | Typiquement, nous allons avoir un constructeur de type suivant : | + | <code cpp> |
+ | template<> class _ButtonEvents | ||
+ | { | ||
+ | typedef void (*action_type)(); | ||
+ | public: | ||
+ | static auto GetPressed(int thePort) { return (action_type)NULL; } | ||
+ | static auto GetReleased(int thePort) { return (action_type)NULL; } | ||
+ | static auto GetChanged(int thePort) { return (action_type)NULL; } | ||
+ | }; | ||
+ | </code> | ||
+ | |||
+ | Maintenant, chaque fois que nous créeons un bouton, il faut mettre à jour la table des fonctions interruptions pour associer l'objet au port. | ||
+ | |||
+ | Pour ce faire, il suffit de définit une fonction ''SetupButton(int thePort, Button* theButton)'' comme suit : | ||
<code cpp> | <code cpp> | ||
- | public class Button | + | template<int First, int... _Rest> |
+ | class _ButtonEvents <First, _Rest...> : private _ButtonEvents <_Rest...> | ||
{ | { | ||
private: | private: | ||
- | struct EventHandler | + | static Button* mButton; |
+ | static void Pressed(); | ||
+ | static void Released(); | ||
+ | static void Changed(); | ||
+ | public: | ||
+ | static auto GetPressed(int thePort) | ||
+ | { return thePort == First ? Pressed : _ButtonEvents<_Rest...>::GetPressed(thePort); } | ||
+ | static auto GetReleased(int thePort) | ||
+ | { return thePort == First ? Released : _ButtonEvents<_Rest...>::GetReleased(thePort); } | ||
+ | static auto GetChanged(int thePort) | ||
+ | { return thePort == First ? Changed : _ButtonEvents<_Rest...>::GetChanged(thePort); } | ||
+ | static void SetButton(int thePort, Button* theButton) | ||
{ | { | ||
- | public: | + | if(thePort == First) |
- | static Button* MonitoredButton; | + | mButton = theButton; |
- | static void Pressed() | + | else |
- | { | + | _ButtonEvents<_Rest...>::SetButton(thePort, theButton); |
- | if(MonitoredButton != NULL) | + | } |
- | MonitoredButton->OnPressed(); | + | }; |
- | } | + | </code> |
- | static void Released() | + | |
- | { | + | |
- | if(MonitoredButton != NULL) | + | |
- | MonitoredButton->OnReleased(); | + | |
- | } | + | |
- | static void Changed() | + | |
- | { | + | |
- | if(MonitoredButton != NULL) | + | |
- | MonitoredButton->OnChanged(); | + | |
- | } | + | |
- | }; | + | |
- | static EventHandler m_EventHandlers[14]; | + | Et bien entendu, il est nécessaire de ne pas oublier ''SetButton'' dans la fonction terminale même si elle ne fait rien. |
+ | |||
+ | <code cpp> | ||
+ | template<> class _ButtonEvents | ||
+ | { | ||
+ | typedef void (*action_type)(); | ||
public: | public: | ||
+ | static auto GetPressed(int thePort) { return (action_type)NULL; } | ||
+ | static auto GetReleased(int thePort) { return (action_type)NULL; } | ||
+ | static auto GetChanged(int thePort) { return (action_type)NULL; } | ||
+ | static void SetButton(int thePort, Button* theButton) {} | ||
+ | }; | ||
+ | </code> | ||
+ | |||
+ | Que reste-t-il encore à faire ? | ||
+ | |||
+ | * Définir un alias sur la séquence de 14 valeurs : | ||
+ | |||
+ | <code cpp> | ||
+ | typedef _ButtonEvents<0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13> ButtonEvents; | ||
+ | </code> | ||
+ | Il est plus facile d'utiliser ''ButtonEvents'' que ''_ButtonEvents<0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13>''. | ||
+ | |||
+ | * Modifier ''Button'' pour indiquer chaque fois que l'on crée un bouton qu'un nouveau bouton est associé au port. | ||
+ | |||
+ | <code cpp> | ||
+ | class Button | ||
+ | { | ||
+ | private: | ||
+ | ... | ||
+ | public: | ||
+ | ... | ||
Button(uint32_t thePortNumber, const char* theName, bool isDebug = false): | Button(uint32_t thePortNumber, const char* theName, bool isDebug = false): | ||
- | CustomComponent(theName, isDebug) | + | m_PortNumber(thePortNumber), Component(theName, isDebug) |
- | { | + | { |
- | ... | + | pinMode(m_PortNumber, INPUT); |
- | m_EventHandlers[thePortNumber].MonitoredButton = this; | + | ButtonEvents::SetButton(m_PortNumber, this); |
} | } | ||
- | ... | + | ... |
}; | }; | ||
</code> | </code> | ||
+ | |||
+ | * Ajouter à la classe ''Button'' les méthodes ''OnPressed()'', ''OnReleased()'', ''OnChanged()'' : | ||
+ | |||
+ | <code cpp> | ||
+ | virtual void OnPressed() | ||
+ | {} | ||
+ | virtual void OnReleased() | ||
+ | {} | ||
+ | virtual void OnChanged() | ||
+ | {} | ||
+ | </code> | ||
+ | |||
+ | * Définir les méthodes ''Pressed'', ''Released'' et ''OnChanged'' de la classe ''_ButtonEvents''. Ces méthodes devront être définies après la définition de la classe ''Button''. | ||
+ | |||
+ | <code cpp> | ||
+ | template<int First, int... _Rest> | ||
+ | void _ButtonEvents<First, _Rest...>::Pressed() | ||
+ | { if(mButton != NULL) mButton->OnPressed(); } | ||
+ | template <int First, int... _Rest> | ||
+ | void _ButtonEvents<First, _Rest...>::Released() | ||
+ | { if(mButton != NULL) mButton->OnReleased(); } | ||
+ | template <int First, int... _Rest> | ||
+ | void _ButtonEvents<First, _Rest...>::Changed() | ||
+ | { if(mButton != NULL) mButton->OnChanged(); } | ||
+ | </code> | ||
+ | |||
+ | * Indiquer comment on initialise le champ ''mButton'' de la classe ''__ButtonEvents''. | ||
+ | |||
+ | <code cpp> | ||
+ | template<int First, int... _Rest> | ||
+ | Button* _ButtonEvents<First, _Rest...>::mButton = NULL; | ||
+ | </code> | ||
+ | |||
+ | Voilà c'est tout, il suffit de compiler. | ||
===== Question n°3 ===== | ===== Question n°3 ===== | ||
- | Nous considérons les deux événements que nous définissons dans le type ''enum'' suivant qui définit deux valeurs ''PRESSED'' indiquant que le bouton vient d'être enfoncé et ''RELEASED'' indiquant que le bouton est relâché. | + | Nous considérons les deux événements que nous définissons dans le type ''enum'' suivant qui définit trois valeurs ''PRESSED'' indiquant que le bouton vient d'être enfoncé et ''RELEASED'' indiquant que le bouton est relâché et ''CHANGED''. |
<code cpp> | <code cpp> | ||
- | enum button_event { PRESSED = 0x1, RELEASED = 0x2 }; | + | enum button_event { PRESSED, RELEASED, CHANGED }; |
</code> | </code> | ||
- | Nous souhaitons créer une fonction qui active l'interruption ou les interruptions nécessaires pour chacun des événements identifiés par ''PRESSED'' et ''RELEASED''. | + | Nous souhaitons créer une fonction qui active l'interruption ou les interruptions nécessaires pour chacun des événements identifiés par ''PRESSED'', ''RELEASED'' et ''CHANGED'' |
Typiquement, l'action de ''MonitorEvents(PRESSED)'' d'activer le moniteur d'interruption suivant : | Typiquement, l'action de ''MonitorEvents(PRESSED)'' d'activer le moniteur d'interruption suivant : | ||
<code cpp> | <code cpp> | ||
- | attachInterrupt(m_PortNumber, mEventHandles[m_PortNumber].Pressed, RISING); | + | attachInterrupt(m_PortNumber, ButtonEvents.GetPressed(m_PortNumber), RISING); |
</code> | </code> | ||
Line 163: | Line 275: | ||
} | } | ||
} | } | ||
+ | </code> | ||
+ | |||
+ | ===== Code correspondant à la classe Button ===== | ||
+ | |||
+ | <code cpp> | ||
+ | |||
+ | #ifndef buttonHPP | ||
+ | #define buttonHPP | ||
+ | #include"Component.hpp" | ||
+ | |||
+ | enum ButtonEvent { PRESSED = 0x1, RELEASED = 0x2 }; | ||
+ | |||
+ | typedef void (*interrupt_type)(); | ||
+ | |||
+ | class Button; | ||
+ | |||
+ | template <int... _Is> | ||
+ | class _ButtonEvents; | ||
+ | template <> | ||
+ | class _ButtonEvents<> { | ||
+ | public: | ||
+ | static interrupt_type GetPressed(int thePort) { return NULL; } | ||
+ | static interrupt_type GetReleased(int thePort) { return NULL; } | ||
+ | static interrupt_type GetChanged(int thePort) { return NULL; } | ||
+ | static void SetButton(int thePort, Button* theButton) {} | ||
+ | }; // Empty events | ||
+ | template <int First, int... _Rest> | ||
+ | class _ButtonEvents<First, _Rest...> : private _ButtonEvents<_Rest...> | ||
+ | { | ||
+ | private: | ||
+ | static const int mPortNumber = First; | ||
+ | static Button* mButton; | ||
+ | static void Pressed(); | ||
+ | static void Released(); | ||
+ | static void Changed(); | ||
+ | public: | ||
+ | static interrupt_type GetPressed(int thePort) { return thePort == mPortNumber ? Pressed : _ButtonEvents<_Rest...>::GetPressed(thePort); } | ||
+ | static interrupt_type GetReleased(int thePort) { return thePort == mPortNumber ? Released : _ButtonEvents<_Rest...>::GetReleased(thePort); } | ||
+ | static interrupt_type GetChanged(int thePort){ return thePort == mPortNumber ? Changed : _ButtonEvents<_Rest...>::GetChanged(thePort); } | ||
+ | static void SetButton(int thePort, Button* theButton); | ||
+ | }; | ||
+ | |||
+ | typedef _ButtonEvents<0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13> ButtonEvents; | ||
+ | |||
+ | class Button: public Component | ||
+ | { | ||
+ | private: | ||
+ | uint32_t m_PortNumber; | ||
+ | public: | ||
+ | Button(uint32_t thePortNumber, const char* theName, bool isDebug = false): | ||
+ | m_PortNumber(thePortNumber), Component(theName, isDebug) | ||
+ | { | ||
+ | pinMode(m_PortNumber, INPUT); | ||
+ | ButtonEvents::SetButton(m_PortNumber, this); | ||
+ | } | ||
+ | void MonitorEvents(ButtonEvent anEvent) | ||
+ | { | ||
+ | if(anEvent == (PRESSED | RELEASED)) | ||
+ | attachInterrupt(digitalPinToInterrupt(m_PortNumber), | ||
+ | ButtonEvents::GetChanged(m_PortNumber), CHANGE); | ||
+ | else if(anEvent == PRESSED) | ||
+ | { | ||
+ | attachInterrupt(digitalPinToInterrupt(m_PortNumber), | ||
+ | ButtonEvents::GetPressed(m_PortNumber), RISING); | ||
+ | } | ||
+ | else if(anEvent == RELEASED) | ||
+ | attachInterrupt(digitalPinToInterrupt(m_PortNumber), | ||
+ | ButtonEvents::GetReleased(m_PortNumber), FALLING); | ||
+ | } | ||
+ | bool IsPressed() { return digitalRead(m_PortNumber) == HIGH; } | ||
+ | virtual void OnPressed() | ||
+ | { | ||
+ | if(IsDebug()) | ||
+ | Serial << name() << "Button is pressed\n"; | ||
+ | } | ||
+ | virtual void OnReleased() | ||
+ | { | ||
+ | if(IsDebug()) | ||
+ | Serial << name() << "Button is released\n"; | ||
+ | } | ||
+ | virtual void OnChanged() | ||
+ | { | ||
+ | if(IsDebug()) | ||
+ | Serial << name() << "Button state has changed\n"; | ||
+ | } | ||
+ | }; | ||
+ | template<int First, int... _Rest> | ||
+ | Button* _ButtonEvents<First, _Rest...>::mButton = NULL; | ||
+ | template<int First, int... _Rest> | ||
+ | void _ButtonEvents<First, _Rest...>::Pressed() | ||
+ | { if(mButton != NULL) mButton->OnPressed(); } | ||
+ | template <int First, int... _Rest> | ||
+ | void _ButtonEvents<First, _Rest...>::Released() | ||
+ | { if(mButton != NULL) mButton->OnReleased(); } | ||
+ | template <int First, int... _Rest> | ||
+ | void _ButtonEvents<First, _Rest...>::Changed() | ||
+ | { if(mButton != NULL) mButton->OnChanged(); } | ||
+ | |||
+ | template <int First, int... _Rest> | ||
+ | void _ButtonEvents<First, _Rest...>::SetButton(int thePort, Button* theButton) | ||
+ | { | ||
+ | if(thePort == mPortNumber) | ||
+ | mButton = theButton; | ||
+ | else | ||
+ | _ButtonEvents<_Rest...>::SetButton(thePort, theButton); | ||
+ | } | ||
+ | |||
+ | #endif | ||
+ | |||
</code> | </code> | ||
Line 171: | Line 392: | ||
Vous pouvez désormais écrire : | Vous pouvez désormais écrire : | ||
+ | <code cpp> | ||
class LedButton: Button | class LedButton: Button | ||
{ | { | ||
Line 188: | Line 410: | ||
} | } | ||
}; | }; | ||
+ | </code> | ||
+ | |||
+ | ====== Navigation ====== | ||
+ | |||
+ | Précédent: ** Jalon 3: **[[.:jalon_3|Les limites de l'approche synchrone]] | ||
+ | Suivant: ** Jalon 5: **[[.:jalon_5|Ajouter des fonctions supplémentaires]] |