Projet Réveil-Matin (partie III) - Architecture de l'application (classe Clock)

Dans des articles précédents, il avait été présenté un projet de réalisation d'un réveil-matin sur Arduino. Le premier article présentait le cahier des charges de ce projet ainsi que le principe de fonctionnement de celui-ci sous la forme d'automate. Un second article, présentait la partie électronique du projet dont, notamment les schémas de câblage de celui-ci.
Le présent article, le troisième de la série, présente l'architecture de l'application programmée en C++ dans une logique orientée objet. La programmation de l'application se résumera à développer un classe Clock (horloge en anglais) dont le code sera réparti dans deux fichiers, comme pour toutes les classes C++, un fichier de définition Clock.h et un fichier d'implémentation Clock.cpp contenant le code des méthodes de la classe.
Dans la programmation de la classe Clock, plusieurs classes d'objets secondaires seront utilisées. Celles-ci feront l'objet d'article ultérieurs. Par exemple, le système d'affichage de l'horloge sera déporté dans une classe abstraite ClockDisplay. Cette classe sera ultérieurement déclinée dans des classes concrètes relative à des systèmes d'affichage différents. Par exemple, LCDClockDisplay implémentera un système d'affichage pour le module LCD 1602 ou LedClockDisplay pour un système d'affichage avec des blocs digitaux à sept segments. Alors que dans un premier temps, la classe SerialClockDisplay, affichera l'heure sur le port série de l'Arduino, ce qui permettra de tester la logique du programme. Cette manière permet de supprimer les inconvénient générés par le couplage entre la classe principale de l'application Clock et les composants qu'elle peut utiliser.
On procédera de même pour le système d'alarme, dont la première implémentation sera basée sur un buzzer passif, mais dont la classe abstraite ClockAlarm pourra être déclinée par la suite pour un système plus sophistiqué comme une radio par exemple.
La programmation de la classe Clock consistera à traduire l'automate décrit dans le premier article en méthodes C++. Il sera donc fait référence, tout au long de cet article, au schéma de cet automate ou au tableau en décrivant le fonctionnement.

Définition de la classe Clock

Le plus simple est de partir du fichier Clock.h et d'en expliquer le contenu.

Contenu du fichier Clock.h


/* 1 */
#ifndef Clock_h
#define Clock_h

/* 2 */
#include "ClockTime.h"
#include "Button.h"
#include "ClockDisplay.h"
#include "ClockAlarm.h"

/* 3 */
class Clock: public Component, public ClockTimeListener, public LongPressButtonListener
{
/* 4 */
  ClockTime __clockTime;
  Button __btnCmd;
  Button __btnUp;
  Button __btnDown;
/* 5 */
  ClockDisplay& __display;
  ClockAlarm& __alarm;
/* 6 */
  ClockStatus __clockStatus;
  Time __alarmTime;
  Time __setupTime;
  bool __alarmStatus;

/* 7 */
  virtual void onTimeChanged(ClockTime*);
  virtual void onPress(Button*);
  virtual void onLongPress(Button*, unsigned long);
/* 8 */
  void __onButtonCommandPress(Button*);
  void __onButtonCommandLongPress(Button*, unsigned long);
  void __onButtonUpPress(Button*);
  void __onButtonDownPress(Button*);
  void __changeStatus(ClockStatus);

  public:
/* 9 */
  Clock(ClockDisplay&, ClockAlarm&, uint8_t, uint8_t, uint8_t);
/* 10 */
  virtual void begin();
  virtual void loop();
};

/* 1 */
#endif // Clock_h
  1. Le couple de directive #ifndef et #endif  permet d'éviter les définitions multiples dans les projets C++ utilisant plusieurs fichiers (ce qui le cas pour le projet Horloge) en définissant une macro Clock_h
  2. La définition des classes relatives aux composants utilisés par le composite Clock.
    • ClockTime.h contient la définition de la classe ClockTime. Cette classe définit un générateur de tics pour simuler l'événement (E) de l'automate. Des événements ClockTimeEvent sont émis périodiquement, qui doivent être écoutés par un récepteur dérivé de ClockTimeListener. Ce qui oblige d'implémenter une méthode onTimeChanged() dans celui-ci. La gestion du temps fera l'objet d'un article ultérieur.
    • Button.h contient la définition de la classe Button. Cette classe définit le fonctionnement d'un bouton poussoir. Les boutons agrégés à la classe Clock génèrent les événements (A), (B), (C) et (D) de l'automate. Des événements ButtonEvent sont émis à chaque interaction de l'utilisateur sur les boutons. Le choix a été fait de dériver l'interface LongPressButtonListener pour distinguer les pressions brèves des pressions longues comme l'exige le cahier des charge du projet. Ce qui oblige d’implémenter les méthodes onPress() et onLongPress() dans la classe.
    • ClockDisplay.h contient la définition de la classe abstraite ClockDisplay qui établit une couche d'abstraction du fonctionnement d'un dispositif d'affichage quelconque. Le détail de cette classe et la programmation d'une classe concrète SerialClockDisplay affichant l'heure sur le port série de l'Arduino seront présenté dans un prochain article.
    • ClockAlarm.h contient la définition de la classe abstraite ClockAlarm qui établit une couche d'abstraction du fonctionnement d'un dispositif d'alarme quelconque. Le détail de cette classe et la programmation d'une classe concrète BuzzerClockAlarm utilisant un signal émis par un buzzer passif seront présenté dans un prochain article.
  3. A la déclaration de la classe Clock, il faut indiquer quelles sont les classes parentes dérivées  :
    • Tout composite doit dériver la classe abstraite Component. Cela oblige à implémenter les méthodes  begin() et loop() commune à tous les composants.
    • La classe Clock agrège un composant de classe ClockTime. Ce composant émet des événement ClockTimeEvent. Il faut donc dériver l'interface ClockTimeListener pour pouvoir traiter ces événements. Ce qui oblige à implémenter la méthode onTimeChanged().
    • La classe Clock agrège trois boutons de classe Button. On dérive l'interface LongPressButtonListener qui permet de distinguer les pressions brèves de pression longue. Il faut donc implémenter les méthodes onPress() et onLongPress().
  4. La classe Clock est un composite qui agrège quatre composants qu'il faut déclarer comme attributs privés :
    • L'attribut privé __clockTime est de type ClockTime. Cette classe définit le générateur de tics pour simuler l'événement (E) de l'automate. Des événements ClockTimeEvent sont émis périodiquement, qui doivent être écoutés par un récepteur dérivé de ClockTimeListener. Il sont pris en charge dans la méthode onTimeChanged().
    • L'attribut privé __btnCmd est de type Button. Ce bouton de commande permet de passer d'un mode à l'autre en changeant l'état de l'horloge. Une pression brève de ce bouton génère l'événement (A) de l'automate passant l'horloge d'un mode à l'autre. Une pression longue (supérieure à 1 s) génère l'événement (B) valide le mode de fonctionnement de l'horloge. Ces événements sont de type ButtonEvent. Ils sont pris en charge respectivement par les méthodes onPress() et onLongPress().
    • L'attribut privé __btnUp est de type Button. Ce bouton permet d'incrémenter les valeurs lors du réglage de l'horloge ou de l'alarme. Une pression brève de ce bouton génère l'événement (D) de l'automate de type ButtonEvent. Ils est pris en charge par la méthode onPress().
    • L'attribut privé __btnDown est de type Button. Ce bouton permet de décrémenter les valeurs lors du réglage de l'horloge ou de l'alarme. Une pression brève de ce bouton génère l'événement (C) de l'automate de type ButtonEvent. Ils est pris en charge par la méthode onPress().
  5. En plus des quatre composants ci-dessus, la classe Clock intègre deux composants externes, passés en paramètres du constructeurs. Ces composants sont déclarés en attributs privés, mais, à la différence des précédents, ils sont déclarés par référence et non pas par valeur. Ce qui est signalé par un & dans la syntaxe du C++.  En effet, comme ils sont instanciés en dehors de la classe Clock et passés en paramètres dans le constructeur de celle-ci, il ne doivent pas être ré-instanciés au moment de la construction de l'instance de l'horloge comme c'est le cas pour les boutons par exemple.
    • L'attribut __display est une référence sur un objet d'une classe dérivée de ClockDisplay. Il correspond à l'instance d'un système d'affichage.
    • L'attribut __alarm est une référence sur un objet d'une classe dérivée de ClockAlarm. Il correspond à l'instance d'un système d'alarme.
  6. Pour fonctionner, la classe Clock a besoin d'attributs privés supplémentaires :
    • __clockStatus est l'état de l'automate de l'horloge qui détermine le mode de fonctionnement de celle-ci. Il est de type enum et peut prendre les valeurs HOUR_DISPLAY, CLOCK_SETUP, ALARM_SETUP, HOUR_SETUP, MINUTE_SETUP, ALARM_HOUR_SETUP, ALARM_MINUTE_SETUP ou ALARM_ACTIVE.
    • __alarmTime est l'heure de déclenchement de l'alarme. Elle est de type Time. La gestion de l'heure en C++ et la définition de cette classe sera traitée dans un article ultérieur.
    • __setupTime est l'heure de réglage de l'horloge. Elle est également de type Time.
    • __alarmStatus est un indicateur booléen pour savoir si l'alarme sonore est en train de fonctionner. Par défaut, il a la valeur 0 (FALSE). Il passe à 1 (TRUE) lorsque l'alarme est activée et que l'heure courante est égale à l'heure de déclenchement.
  7. Les trois méthodes ci-dessous correspondent aux implémentations des méthodes abstraites des listeners. En effet la classe Clock dérive les listeners ClockTimeListener et LongPressButtonListener. pour recevoir les événement ClockTimeEvent et ButtonEvent émis par les composants de classe ClockTime et Button. Ces méthodes doivent impérativement être surchargées dans les classes réceptrices, donc dans la classe Clock pour que celle-ci puisse être instanciée.
    • onTimeChanged() est invoquée à chaque fois qu'un événement ClockTimeEvent est émis par le composant ClockTime intégré. Cet événement correspond à l'événement (E) de l'automate.
    • onPress() est invoquée à chaque fois qu'une pression brève d'un bouton est effectuée par l'utilisateur. La même méthode est invoquée par les trois boutons. Mais le bouton émetteur est passé en paramètre de la méthode afin de distinguer celui qui a été appuyé. 
    • onLongPress() est invoquée lorsqu'une pression longue est effectuée sur un bouton par l'utilisateur. La même méthode est invoquée pour les trois boutons. La méthode reçoit en paramètre le bouton émetteur ainsi que la durée de la pression.
  8. Les cinq méthodes ci-dessous correspondent aux événements de l'automate de l'horloge et au passage d'un état à l'autre. Ces méthodes sont invoquées par les méthodes onPress()  ou onLongPress() selon le bouton émetteur de l'événement ButtonEvent passé en paramètre.
    • __onButtonCommandPress() est invoquée lorsqu'une pression brève du bouton de commande est effectuée. Ce qui correspond à l'événement (A) de l'automate.
    • __onButtonCommandLongPress() est invoquée lorsqu'une pression longe (> 1 s) du bouton de commande est effectuée. Ce qui correspond à l'événement (B) de l'automate.
    • __onButtonDownPress() est invoquée lorsqu'une pression brève du bouton de décrémentation est effectuée. Ce qui correspond à l'événement (C) de l'automate.
    • __onButtonUpPress() est invoquée lorsqu'une pression brève du bouton d'incrémentation est effectuée. Ce qui correspond à l'événement (D) de l'automate.
    • __changeStatus() détermine un changement d'état de l'horloge. Elle permet de notifier ce changement aux dispositifs d'affichage et d'alarme.
Tous ce qui est défini ci-dessus est déclaré private dans la classe Clock. Ce qui en interdit l'accès de l'extérieur de l'objet. Cette caractéristique de la programmation par objets est appelée encapsulation. Elle permet de cacher ce qui relève du fonctionnement interne de la classe aux programmes qui l'utilisent. Reste néanmoins maintenant à définir l'interface publique de la classe. Ce que peut ou doit invoquer le programme qui utilise la classe Clock, notamment ce que devra invoquer le sketch Arduino.
  1. Clock() est le constructeur de la classe. Il  doit être invoqué par un programme pour créer une instance de la classe Clock. De façon générale, le constructeur d'une classe composite dois avoir comme paramètres les numéros des pins de l'Arduino utilisées et éventuellement les références aux composants externe intégrés. Pour la classe Clock, les paramètres du constructeur sont les suivants :
    • clockDisplay est une référence sur un objet externe d'une classe dérivée de ClockDisplay pour définir quel dispositif d'affichage est utilisé.
    • clockAlarm est une référence sur un objet d'une classe dérivée de ClockAlarm pour définir que dispositif d'alarme est utilisé.
    • pinBtnCmd est le numéro de pin sur laquelle est connecté le bouton de commande.
    • pinBtnUp est le numéro de pin sur laquelle est connecté le bouton d'incrémentation.
    • pinBtnDown est le numéro de pin sur laquelle est connecté le bouton de décrémentation.
  2. Les méthodes suivantes sont héritées de la classe abstraite Component obligeant à les implémenter.
    • begin() permet d'initialiser le composite Clock. Elle doit être invoquée dans la fonction setup() du sketch Arduino. L'implémentation de cette méthode doit invoquer la méthode begin() sur tous les composants agrégés.
    • loop() est la boucle du composite Clock. Elle doit être invoquée dans la fonction loop() du sketch Arduino. L'implémentation de cette méthode doit invoquer la méthode loop() sur tous les composants agrégés.

Utilisation de la classe Clock dans un sketch Arduino

Toute l'intelligence du projet Horloge est implémentée dans la classe Clock. Faire fonctionner le projet se résumera à quelques instructions consistant à instancier les composants nécessaires, à invoquer la méthode begin() de l'objet Clock dans la fonction setup(), puis à invoquer la méthode loop() de l'objet Clock dans la fonction loop().
/* 1 */
#include "LCDClockDisplay.h"
#include "ClockAlarm.h"
#include "Clock.h"

/* 2 */
// Déclaration des pins de connexion du LCD sur l'Arduino.
#define LCD_RS 7
#define LCD_E 8
#define LCD_D4 9
#define LCD_D5 10
#define LCD_D6 11
#define LCD_D7 12
/* 3 */
// Déclaration des pins de connexion des boutons de commande.
#define BTN_CMD 5
#define BTN_UP 3
#define BTN_DOWN 4
/* 4 */
// Déclaration de la pin de connexion du buzzer passif.
#define BUZZER 2


/* 5 */
LCDClockDisplay clockDisplay(LCD_RS, LCD_E, LCD_D4, LCD_D5, LCD_D6, LCD_D7);
BuzzerClockAlarm clockAlarm(BUZZER);
Clock myclock(clockDisplay, clockAlarm, BTN_CMD, BTN_UP, BTN_DOWN);

/* 6 */
void setup() {
  myclock.begin();
}

/* 7 */
void loop() {
  myclock.loop();
}
  1. Le sketch utilise les classes LCDClockDisplay, BuzzerClockAlarm et Clock. Les fichiers où ces classes sont définies doivent être insérés par la directive #include.
  2. Une bonne pratique veut que l'on définisse le numéro de pin de l'Arduino dans des constantes. Ce qui fait, que lorsqu'on modifie le câblage de celui-ci, il suffit de modifier la valeur de la constante sans avoir à faire une revue de code de tous les fichiers du projet. Dans cet exemple, le système d'affichage est basé sur le module LCD 1602. Celui-ci exigent quatre pin pour les données (D4 à D7) et deux pins pour les commandes E et RS.
  3. L'horloge utilise trois boutons. Chaque bouton prend une pin de l'Arduino.
  4. Le dispositif d'alarme est basé sur un buzzer passif. Celui-ci prend une pin de l'Arduino.
  5. Tous les composants utilisés doivent être instanciés.
    • clockDisplay est le dispositif d'affichage utilisé. Dans l'exemple, c'est la classe LCDClockDisplay qui est instanciée en passant en paramètres les six numéros de pin nécessaire dans le constructeur de celle-ci.
    • clockAlarm est le dispositif d'alarme utilisé. Dans l'exemple, c'est la classe BuzzerClockAlarm qui est instanciée en passant en paramètre du constructeur le numéro de pin sur lequel le buzzer passif est connecté.
    • myClock est l'horloge proprement dite. Sa construction nécessite, une référence sur le dispositif d'affichage utilisé, une référence sur le dispositif d'alarme et les trois numéro de pins de bouton utilisés.
  6. Dans la fonction setup() du sketch, il suffit d'invoquer la méthode begin() sur l'instance de l'horloge myClock, celle-ci se chargeant de propager l’initialisation de tous les composants quelle agrège en invoquant leur méthode begin() respective.
  7. Dans la fonction loop() du sketch, il suffit d'invoquer la méthode loop() sur l'instance de l'horloge myClock, celle-ci se chargeant de propager l'invocation de la méthode loop() sur tous les composants qu'elle agrège.

Conclusion

Ainsi ce termine le présent article consacré à l'architecture de l'application. Cette architecture est basée sur la classe Clock pour laquelle une revue de tous les éléments qui la composent a été effectuée.
L'article suivant sera consacré à l'implémentation de cette classe. C'est-à-dire à la programmation de chaque méthode définie dans celle-ci.
Plusieurs autres articles, nécessaires pour présenter l'exhaustivité du code du projet, seront également proposés ultérieurement, notamment ceux consacrés aux dispositifs d'affichage et d'alarme.

Commentaires

Posts les plus consultés de ce blog

Afficheur à LED 7 segments (classe SegmentLedDisplay)

Piloter un clavier matriciel sur le bus I2C avec un PCF8574

Utiliser Visual Studio Code pour développer sur Arduino