User Tools

Site Tools


in202:seance_6:jalon_4

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
in202:seance_6:jalon_4 [2021/05/02 11:56]
bmonsuez [Question n°4]
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. 
 + 
 +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''​. 
 + 
 +<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>​
  
-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éé.+Maintenant, ​chaque fois que nous créeons ​un boutonil faut mettre ​à jour la table des fonctions interruptions ​pour associer l'objet au port.
  
-Typiquementnous allons avoir un constructeur ​de type suivant ​:+Pour ce faireil 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>​
  
in202/seance_6/jalon_4.1619956606.txt.gz · Last modified: 2021/05/02 11:56 by bmonsuez