Dans l'exemple précédent, nous appelons une fonction externe à la classe Button
. En fait, nous aimerions avoir une classe Button
à laquelle nous pourrions ajouter le comportement que doit avoir l'objet quand un événement se produit, comme par exemple appuyer sur le bouton ou relâcher le bouton.
Ajouter trois méthodes virtuelles à votre classe Button
qui correspondent à :
OnPressed()
: on appuie sur le bouton.OnReleased()
: on relâche le bouton.OnChanged()
: soit le bouton a été appuyé, soit le bouton a été rélâché.ces méthodes sont vides.
La difficulté vient du fait que la méthode attachInterupt
ne passe aucune information à la méthode ISR
qui est appelé. En conséquence, c'est le nom de la méthode qui est associé avec l'interruption qui permet de savoir quel est le port et qu'elles sont les actions à effectuer.
Si nous supposons monitoré l'ensemble des 13 ports de la carte DUE, nous devrions écire trois fonctions pour chacun des ports :
void OnPressedPort1(); void OnReleasedPort1(); void OnChangedPort1();
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 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
.
template <int... _Is> class _ButtonEvents;
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 :
_ButtonEvents<> emptyEvents; _ButtonEvents<0> emptyEvents; _ButtonEvents<0, 1> emptyEvents; _ButtonEvents<0, 1, ..., 10> emptyEvents;
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.
template<> class _ButtonEvents {};
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() { if (mButton != NULL) mButton->OnChanged(); } };
Et voilà, c'est fait, la définition suivante va créer 3 instances des fonctions
Pressed()
, Released()
etChanged()
.
_ButtonEvents<0, 1, 2> EventsFor3Ports;
Le compilateur va développer récursivement :
_ButtonEvents <0, 1, 2> : private _ButtonEvents <1, 2> { private: static Button* mButton; static void Pressed(); static void Released(); static void Changed(); }; _ButtonEvents <1, 2> : private _ButtonEvents <2> { private: static Button* mButton; static void Pressed(); static void Released(); static void Changed(); }; _ButtonEvents <2> : private _ButtonEvents <> { private: static Button* mButton; static void Pressed(); static void Released(); static void Changed(); };
C'est bien, mais il faut ensuite penser à d'une part :
Pour récupérer les fonctions, nous allons définir les fonctions suivantes :
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); } };
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.
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
.
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; } };
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 :
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); } static void SetButton(int thePort, Button* theButton) { if(thePort == First) mButton = theButton; else _ButtonEvents<_Rest...>::SetButton(thePort, theButton); } };
Et bien entendu, il est nécessaire de ne pas oublier SetButton
dans la fonction terminale même si elle ne fait rien.
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; } static void SetButton(int thePort, Button* theButton) {} };
Que reste-t-il encore à faire ?
typedef _ButtonEvents<0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13> ButtonEvents;
Il est plus facile d'utiliser ButtonEvents
que _ButtonEvents<0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13>
.
Button
pour indiquer chaque fois que l'on crée un bouton qu'un nouveau bouton est associé au port.class Button { private: ... 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); } ... };
Button
les méthodes OnPressed()
, OnReleased()
, OnChanged()
:virtual void OnPressed() {} virtual void OnReleased() {} virtual void OnChanged() {}
Pressed
, Released
et OnChanged
de la classe _ButtonEvents
. Ces méthodes devront être définies après la définition de la classe Button
.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(); }
mButton
de la classe __ButtonEvents
.template<int First, int... _Rest> Button* _ButtonEvents<First, _Rest...>::mButton = NULL;
Voilà c'est tout, il suffit de compiler.
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
.
enum button_event { PRESSED, RELEASED, CHANGED };
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 :
attachInterrupt(m_PortNumber, ButtonEvents.GetPressed(m_PortNumber), RISING);
Compléter le code de la classe.
class Button { public: void MonitorEvents(button_event theEvents) { ... } }
#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
Pour définir un bouton qui implante une action quand l'utilisateur appuie sur le bouton, il suffit de surcharger désormais dans une classe dérivée la méthode OnPressed()
.
Vous pouvez désormais écrire :
class LedButton: Button { private: Led mLed; public: LedButton(uint32_t thePortNumber, Led& theLed): Button(thePortNumber, "LedButton", false) { MonitorEvents(PRESSED); } void OnPressed() { if(mLed.IsSwitchOn()) mLed.SwitchOff(); else mLed.SwitchOn(); } };
Précédent: Jalon 3: Les limites de l'approche synchrone
Suivant: Jalon 5: Ajouter des fonctions supplémentaires