Projet Réveil-Matin (partie IV) - Implémentation de la classe Clock

Dans l'article précédent, l'architecture de l'application relative au projet Réveil-Matin avait été présentée, avec la définition d'une classe Clock dans un fichier Clock.h. Ce fichier doit être utilisé dans une directive #include dans le programme du projet, notamment dans le sketch Arduino pour instancier la classe Clock.
Dans la définition de cette classe, plusieurs méthodes ont été déclarée pour organiser le fonctionnement de l'horloge. L'objet de cet article est de présenter le code de ces méthodes, rassemblé dans un fichier Clock.cpp.

Interface publique de la classe Clock

Pour pouvoir être utilisée dans un projet Arduino, la classe Clock doit au moins exposer à l'extérieur un constructeur pour pouvoir instancier la classe et les méthodes begin() et loop() à utiliser respectivement dans les fonctions setup() et loop() du sketch, comme cela a été présenté dans l'article précédent.

Constructeur de la classe Clock

Clock::Clock(ClockDisplay& clockDisplay, ClockAlarm& clockAlarm, uint8_t pinBtnCmd, uint8_t pinBtnUp, uint8_t pinBtnDown) :
  __clockTime(),            // Construction du générateur de tics.
  __btnCmd(pinBtnCmd),      // Construction du bouton de commande
  __btnUp(pinBtnUp),        // Construction du bouton d'incrémentation des valeurs
  __btnDown(pinBtnDown)     // Construction du bouton de décrémentation des valeurs
  __display(clockDisplay),  // Construction du système d'affichage.
  __alarm(clockAlarm),      // Construction du dispositif d'alarme.
  __alarmTime(),            // Construction de l'heure de déclenchement de l'alarme.
  __setupTime(),            // Construction de l'heure de réglage.
{
  this->__clockStatus = HOUR_DISPLAY;   // Etat courant de l'automate.
  this->__alarmStatus = false;          // Etat de l'alarme.
  this->__clockTime.addListener(this);  // Réception de l'événement onTimeChanged.
  this->__btnCmd.addListener(this);     // Réception de onLongPressButton et onPressButton.
  this->__btnUp.addListener(this);      // Réception de onLongPressButton et onPressButton.
  this->__btnDown.addListener(this);    // Réception de onLongPressButton et onPressButton.
}
Le constructeur d'un composite Arduino doit recevoir en paramètres, la référence des composants externe, et/ou les numéro de pin de l'Arduino utilisés. Le constructeur de la classe Clock reçoit cinq paramètres :
  • clockDisplay est une référence (déclarée avec le caractère &) à l'objet relatif au dispositif d'affichage. Le dispositif d'affichage doit donc être instancié avant l'objet de classe Clock pour pouvoir être passé en paramètre. Le fonctionnement de la classe ClockDisplay et de ses classes dérivées fera l'objet d'un article particulier.
  • clockAlarm est une référence à l'objet relatif au dispositif d'alarme. Ce dispositif doit donc être également instancié avant la classe Clock pour pouvoir être passé en paramètre. Le fonctionnement de la classe ClockAlarm et de ses classes dérivées fera l'objet d'un article particulier.
  • pinBtnCmd est le numéro de pin sur laquelle est connecté le bouton de commande de l'horloge.
  • pinBtnUp est le numéro de pin sur laquelle est connecté le bouton d'incrémentation utilisé pour le réglage de l'horloge.
  • pinBtnDown est le numéro de pin sur laquelle est connecté le bouton de décrémentation utilisé pour le réglage de l'horloge.
En C++, lorsqu'une classe agrège des objets d'autres classes, le constructeur de tous les composants doit être invoqué en y passant les paramètres appropriés avant même d'ouvrir l'accolade encadrant la code du constructeur. La classe Clock agrège plusieurs composants déclarés comme attributs privés dans la définition de la classe.
  • L'attribut __clockTime est une instance de la classe ClockTime dont le constructeur ne prend pas de paramètre. C'est le générateur de tic  qui émet des événements ClockTimeEvent.
  • Les attributs __btnCmd, __btnUp et __btnDown sont de classe Button dont le constructeur a comme paramètre le numéro de pin à laquelle le bouton est physiquement connecté. Les numéros pins pinBtnCmd, pinBtnUp et pinBtnDown sont ceux passés en paramètre du constructeur de la classe Clock.
  • L'attribut __display est déclaré de type ClockDisplay&. C'est une référence sur l'instance du dispositif d'affichage passé en paramètre du constructeur de la classe Clock
  • L'attribut __alarm est déclaré de type ClocAlarm&. C'est une référence sur l'instance du dispositif d'alarme passé en paramètre du constructeur de la classe Clock
  • L'attribut __alarmTime est de classe Time. Il s'agit de l'heure de déclenchement de l'alarme. Sans paramètre dans le constructeur de la casse Time, celui-ci attribue par défaut la valeur  00:00:00. Le fonctionnement de la classe Time fera l'objet d'un article ultérieur.
  • L'attibut __setupTime est également de classe Time. C'est l'attribut utilisé pour le réglage de l'heure de l'horloge. Il est initialisé également à 00:00:00.
Une fois tous les attributs relatifs à des classes d'objets construits, il faut maintenant procéder à l'initialisation des autres attributs.
  • __clockStatus est l'état courant de l'automate de l'horloge. A l'initialisation, il prend l valeur HOUR_DISPLAY qui est l'état normal de l'horloge lorsqu'elle affiche l'heure.
  • __alarmStatus est l'état d'activation de l'alarme. A l'initialisation, il prend la valeur false (alarme inactive). La valeur passe à true lorsqu'une pression longue est effectuée sur le bouton de commande dans l'état normal de l'automate.
Enfin, il ne faut pas oublier que la classe Clock agrège des objets émetteur d'événement. En effet la classe Clock implémente les interfaces ClockTimeListener et LongPressButtonListener. A la construction, les instances de la classe Clock doivent s'enregistrer comme récepteurs auprès des émetteurs en invoquant la méthode addListener() sur chacun d'eux en passant l'instance de Clock elle-même this. Pour rappel, en C++, this est l'adresse de l'instance, donc un pointeur. Pour une description détaillée du fonctionnement des événements en C++, se référer à l'article consacré à ce sujet.

La méthode begin()

Tous les composites d'un projet Arduino doivent implémenter la méthode begin() héritée de la classe abstraite Component. L'implémentation de cette méthode dans la classe Clock consiste à invoquer la méthode begin() de tous les composants agrégés à la classe.
void Clock::begin() {
  this->__display.begin();    // Initialisation du dispositif d'affichage.
  this->__alarm.begin();      // Initialisation du dispositif d'alarme.
  this->__clockTime.begin();  // Initialisation du générateur de tick.
  this->__btnCmd.begin();     // Initialisation du bouton de commande.
  this->__btnUp.begin();      // Initialisation du bouton d'incrémentation.
  this->__btnDown.begin();    // Initialisation du bouton de décrémentation.
}

La méthode loop()

Tous les composites d'un projet Arduino doivent implémenter la méthode loop() héritée de la classe abstraite Component. Commme pour la méthode begin(), l'implémentation de cette méthode dans la classe Clock consiste à invoquer la méthode loop() de tous les composants agrégés à la classe.
void Clock::loop() {
  this->__display.loop();     // Boucle du dispositif d'affichage.
  this->__alarm.loop();       // Boucle du dispositif d'alarme.
  this->__clockTime.loop();   // Boucle du générateur de tick.
  this->__btnCmd.loop();      // Boucle du bouton de commande.
  this->__btnUp.loop();       // Boucle du bouton d'incrémentation.
  this->__btnDown.loop();     // Boucle du bouton de décrémentation.
}
A remarquer que les événements sont émis par les composants émetteurs dans l'exécution de leur méthode loop() respective. En oublier un à cette étape aura pour résultat qu'aucun événement ne sera reçu. En conséquence le handler (la méthode onXXX()) relatif à l'interface d'écoute (le listener) ne sera jamais invoqué.

Gestion des événements

Comme vu dans le premier article traitant de la conception du projet, l'horloge fonctionne comme un automate à états finis. Le graphe de cet automate y a été présenté. Les événements de l'automate sont déclenchés par les composants de classe ClockTime et Button.
Pour pouvoir recevoir les événements ClockTimeEvent émis par l'objet de classe ClockTime, la classe réceptrice Clock doit dériver l'interface ClockTimeListener. ce qui l'oblige à implémenter la méthode abstraite de l'interface onTimeChanged(). Les événements reçus alors correspondent à l'événement (E) de l'automate.
De même, pour recevoir les événements ButtonEvent émis par les trois boutons de classe Button, la classe Clock doit dériver l'interface LongPressButtonListener, ce qui l'oblige à implémenter les deux méthodes abstraites de l'interface onPress() et onLongPress(). Les événement reçus alors correspondent aux événements (A), (B), (C) et (D) de l'automate. En effet, la même méthode handler onPress() ou onLongPress() est invoquée, quelque soit le bouton à l'origine de l'événement. Il faudra exploiter le paramètre sender de ces méthodes pour connaitre le bouton émetteur afin de distinguer de quel événement de l'automate il s'agit.
Ces handlers sont invoqués par la méthode loop() des composants émetteurs. Or, la bibliothèque Event utilisée pour la gestion des événements est basée sur le design pattern Observer. Ce qui implique un traitement synchrone de leur réception. De ce fait, le code de ces méthodes ne doit prendre que très peu de temps pour ne pas rater d’événement. En effet, si le traitement d'un événement prend plusieurs secondes, la pression d'un bouton par l'utilisateur à ce moment là risque de passer inaperçue. De façon générale, la programmation de boucles est à proscrire.

Méthode onTimeChanged()

Cette méthode est héritée de l'interface ClockTimeListener. Elle est invoquée à chaque fois qu'un événement ClockTimeEvent est émis, toutes les secondes environ, par le composant de classe ClockTime implémenté dans la classe Clock par l'attribut privé __clockTime. Cela correspond au traitement de l'événement (E) de l'automate.
Le traitement à effecteur pour cet événement est à extraire du tableau répertoriant les actions de transition de l'automate. En fonction de l'état de l'horloge, il y aura une action particulière à effectuer, suivie éventuellement d'un changement d'état.
EtatActionEtat suivant
1
Affichage de l'heure
Incrémentation de l'heure courante.
1
Si heure courante = heure alarme
Déclenchement de l'alarme
8
8
Alarme déclenchée
Incrémentation de l'heure courante
8
void Clock::onTimeChanged(ClockTime* sender) {
    switch (this->__clockStatus) {
      case HOUR_DISPLAY:
        this->__display.changeTime(*sender);
        if (this->__alarmStatus && this->__clockTime == this->__alarmTime) {
          this->__alarm.On();
          this->__changeStatus(ALARM_ACTIVE);
        }
        break;
      case ALARM_ACTIVE:
        this->__display.changeTime(*sender);
        break;
    }
}
L'événement n'intervient que dans deux états HOUR_DISPLAY (état 1 de l'automate) et ALARM_ACTIVE (état 8 de l'automate).
Le paramètre sender est l'adresse d'un objet ClockTime. Sa valeur est notifiée au dispositif d'affichage (attribut __display) en invoquant dessus la méthode changeTime(). Cela sous entend que la classe ClockDisplay expose cette méthode dans son interface publique.
Dans l'état 1 (HOUR_DISPLAY), la valeur de l'heure reçue (*sender) est comparée à l'heure de déclenchement de l'alarme (attribut __alarmTime). Si elles sont égales et que l'alarme est activée (__alarmStatus vaut true), alors l'alarme est déclenchée et l'horloge passe à l'état 8 (ALARM_ACTIVE). Cette syntaxe n'est possible que si l'opérateur == a été surchargé dans la classe Time. Cette technique sera traitée dans l'article dédié à la gestion du temps et à l'implémentation des classe Time et ClockTime. Cela sous-entend également que la classe ClockAlarm expose une méthode On() pour déclencher le signal d'alarme.

Méthode onPress()

Cette méthode est dérivée de l'interface LongPressButtonListener. Elle est invoquée chaque fois que l'utilisateur effectue une pression brève sur l'un des trois boutons __btnCmd, __btnUp ou __btnDown de classe Button implémentés comme attributs privés dans la classe Clock. Ce qui émet un événement de type ButtonEvent. Le bouton à l'origine peut être connu par le paramètre sender qui est un pointeur une instance de la classe Button. Le bouton pointé par cette adresse peut être comparé avec chaque bouton de la classe  Clock pour déterminer à quel événement de l'automate cela correspond.
void Clock::onPress(Button* sender) {
  if (sender == &this->__btnCmd) {
      this->__onButtonCommandPress(sender);   // Evenement (A)
  } else if (sender == &this->__btnUp) {
      this->__onButtonUpPress(sender);        // Evenement (D)
  } else if (sender == &this->__btnDown) {
      this->__onButtonDownPress(sender);      // Evenement (C)
  }
}

Il est difficile de tester si deux instances d'objet sont égales lorsque la classe de cet objet ne dispose pas d'une surcharge de l'opérateur ==. D'autant que dans le cas présent, il s'agit plutôt de savoir si les deux données comparées sont relative au même objet. C'est pourquoi c'est la valeur sender, qui est un pointeur sur le bouton à l'origine de l'événement, qui est successivement comparée avec les adresses de chaque bouton. Cette adresse étant obtenue grâce à l'opérateur & qui signifie « adresse de ». En fonction du bouton émetteur, la méthode privée est invoquée dans la classe Clock :
  • Une pression brève sur le bouton de commande __btnCmd provoque l'invocation de la méthode __onButtonCommandPress(). Ce qui correspond à l'événement (A) de l'automate.
  • Une pression brève sur le bouton de décrémentation __btnDown provoque l'invocation de la méthode __onButtonDownPress(). Ce qui correspond à l'événement (C) de l'automate.
  • Une pression brève sur le bouton d'incrémentation __btnUp provoque l'invocation de la méthode __onButtonUpPress(). Ce qui correspond à l'événement (D) de l'automate.

Méthode onLongPress()

Cette méthode est dérivée de l'interface LongPressButtonListener. Elle est invoquée chaque fois que l'utilisateur effectue une pression d'une durée supérieure à 1 seconde sur l'un des trois boutons. Seule, la pression longue sur le bouton de commande __btnCmd est traitée dans le projet Horloge. Dans cette méthode il suffit donc de vérifier si c'est bien le bouton de commande pour invoquer la méthode __onButtonCommandLongPress() correspondant au traitement de l'événement (B) de l'automate.
void Clock::onLongPress(Button* sender, unsigned long elapseTime) {
  if (sender == &this->__btnCmd) {
    this->__onButtonCommandLongPress(sender, elapseTime);  // Evenement (B)
  }
}

Méthode __onButtonCommandPress()

Cette méthode est invoquée par la méthode OnPress() lorsque l'utilisateur effectue un pression brève sur le bouton de commande __btnCmd. Cela correspond au traitement de l'événement (A) de l'automate.
Le traitement à effecteur pour cet événement est à extraire du tableau répertoriant les actions de transition de l'automate. En fonction de l'état de l'horloge, il y aura une action particulière à effectuer, suivie éventuellement d'un changement d'état.
EtatActionEtat suivant
1
Affichage de l'heure
Rien
2
2
Mise à l'heure
Rien
3
3
Réglage Alarme
Rien
1
4
Réglage HH
Rien
5
5
Réglage MN
Rien
4
6
Réglage HH Alarme
Rien
7
7
Réglage MN Alarme
Rien
6
8
Alarme déclenchée
Arrêt de l'alarme
1
void Clock::__onButtonCommandPress(Button* sender) {
  switch (this->__clockStatus) {
    case HOUR_DISPLAY:
      this->__changeStatus(CLOCK_SETUP);
      this->__display.changeTime(this->__clockTime);
      break;
    case CLOCK_SETUP:
      this->__changeStatus(ALARM_SETUP);
      this->__display.changeTime(this->__alarmTime);
      break;
    case ALARM_SETUP:
      this->__changeStatus(HOUR_DISPLAY);
      this->__display.changeTime(this->__clockTime);
      break;
    case HOUR_SETUP:
      this->__changeStatus(MINUTE_SETUP);
      this->__display.changeTime(this->__setupTime);
      break;
    case MINUTE_SETUP:
      this->__changeStatus(HOUR_SETUP);
      this->__display.changeTime(this->__setupTime);
      break;
    case ALARM_HOUR_SETUP:
      this->__changeStatus(ALARM_MINUTE_SETUP);
      this->__display.changeTime(this->__alarmTime);
      break;
    case ALARM_MINUTE_SETUP:
      this->__changeStatus(ALARM_HOUR_SETUP);
      this->__display.changeTime(this->__alarmTime);
      break;
    case ALARM_ACTIVE:
      this->__changeStatus(HOUR_DISPLAY);
      this->__alarm.Off();
      break;
  }
}
Pour l'événement (A), aucune action n'est à effectuer, sauf dans l'état 8 (ALARM_ACTIVE) où il faut arrêter le signal d'alarme. Il suffira donc de passer à l'état suivant et d'adapter l'affichage à nouvel état. 
  • Dans l'état 1 (HOUR_DISPLAY), l'horloge change d'état pour passer à l'état 2 (CLOCK_SETUP). Le dispositif d'affichage continue à afficher l'heure courante (attribut __clockTime).
  • Dans  l'état 2 (CLOCK_SETUP), l'horloge change d'état et passe à l'état 3 (ALARM_SETUP). Le dispositif d'affichage affiche l'heure de déclenchement de l'alarme (attribut __alarmTime).
  • Dans l'état 3  (ALARM_SETUP), l'horloge revient à l'état 1 (HOUR_DISPLAY) pour afficher l'heure courante. 
  • Dans l'état 4 (HOUR_SETUP),  l'horloge passe à l'état 5 (MINUTE_SETUP). L'heure affichée sur le dispositif d'affichage est l'heure en cours de réglage (attribut __setupTime).
  • Dans l'état 5 (MINUTE_SETUP), l'horloge revient à l'état 4 (HOUR_SETUP). L'heure affichée est toujours l'heure en cours de réglage. 
  • Dans l'état 6 (ALARM_HOUR_SETUP),  l'horloge passe à l'état 7 (ALARM_MINUTE_SETUP). L'heure affichée sur le dispositif d'affichage est l'heure de déclenchement de l'alarme (attribut __alarmTime).
  • Dans l'état 7 (ALARM_MINUTE_SETUP), l'horloge revient à l'état 6 (ALARM_HOUR_SETUP). L'heure affichée est toujours l'heure de déclenchement de l'alarme en cours de réglage.
  • Dans l'état 8 (ALARM_ACTIVE), le signal sonore de l'alarme est audible. Il faut donc l'éteindre en invoquant la méthode Off() du dispositif d'alarme. Cela sous-entend donc que la classe ClockAlarm expose cette méthode dans son interface publique. L'horloge revient alors à l'état 1 (HOUR_DISPLAY).

Méthode __onButtonCommandLongPress()

Cette méthode est invoquée par la méthode onLongPress() lorsque l'utilisateur effectue une pression longue (> 1s) sur le bouton de commande. Ce qui correspond au traitement de l'événement (B) de l'automate.
Comme pour l'événement (A), le traitement à effectuer dans cette méthode est extrait du tableau répertoriant les actions de transition de l'automate. En fonction de l'état de l'horloge, il y aura une action particulière à effectuer, suivie d'un changement d'état.

EtatActionEtat suivant
1
Affichage de l'heure
Activation/Désactivation de l'alarme
1
2
Mise à l'heure
Rien
4
3
Réglage Alarme
Rien
6
4
Réglage HH
Enregistrement de HH:MN
1
5
Réglage MN
Enregistrement de HH:MN
1
6
Réglage HH Alarme
Enregistrement Alarme HH:MN
1
7
Réglage MN Alarme
Enregistrement Alarme HH:MN
1
void Clock::__onButtonCommandLongPress(Button*, unsigned long elapseTime) {
  switch (this->__clockStatus) {
    case HOUR_DISPLAY:
      this->__alarmStatus = !this->__alarmStatus;
      this->__display.changeAlarm(this->__alarmStatus);
      break;
    case CLOCK_SETUP:
      this->__setupTime = this->__clockTime;
      this->__changeStatus(HOUR_SETUP);
      this->__display.changeTime(this->__setupTime);
      break;
    case ALARM_SETUP:
      this->__changeStatus(ALARM_HOUR_SETUP);
      this->__display.changeTime(this->__alarmTime);
      break;
    case HOUR_SETUP:
    case MINUTE_SETUP:
      this->__clockTime.reset(this->__setupTime);
      this->__changeStatus(HOUR_DISPLAY);
      break;
    case ALARM_HOUR_SETUP:
    case ALARM_MINUTE_SETUP:
      this->__changeStatus(HOUR_DISPLAY);
      break;
  }
}
  • Dans l'état 1 (HOUR_DISPLAY) , la pression longue du bouton de commande fonctionne comme une bascule en inversant l'état d'activation de l'alarme (attribut __alarmStatus) sans modifier l'état de l'horloge elle-même. Le changement de l'état d'activation de l'alarme est notifie au dispositif d'affichage pour que celui-ci l'indique à l'utilisateur (en affichant une petite cloche sur l'écran LCD, par exemple, lorsque l'alarme est activée). Cela sous-entend que la classe ClockDisplay expose une méthode changeAlarm() dans son interface publique.
  • Dans l'état 2 (CLOCK_SETUP), une pression longue sur le bouton de commande signifie que l'horloge passe en mode réglage de l'heure. L'horloge passe à l'état 4 (HOUR_SETUP). Ce qui permet de modifier la valeur HH de l'attribut __setupTime. Pour cela l'heure courante de l'attribut __clockTime est recopiée dans l'attribut __setupTime dont la valeur, pendant toute la durée du réglage sera affichée sur le dispositif d'affichage.
  • Dans l'état 3 (ALARM_SETUP), une pression longue sur le bouton de commande signifie que l'horloge passe en mode réglage de l'heure de déclenchement de l'alarme. L'horloge passe à l'état 6 (ALARM_HOUR_SETUP). Ce qui permet de modifier la valeur HH de l'attribut __alarmTime. Pendant toute la durée du réglage, c'est l'attribut __alarmTime qui est affiché sur le dispositif d'affichage.
  • Dans les états 4 et 5 (HOUR_SETUP et MINUTE_SETUP), l'horloge revient à l'état 1 (HOUR_DISPLAY) après avoir enregistré dans l'attribut __clockTime la nouvelle valeur de l'heure contenue dans l'attribut __setupTime en invoquant la méthode reset(). Cette méthode met à jour l'origine des temps afin que les message ClockTimeEvent contient une valeur réglée sur l'heure nouvellement mise à jour. Cela sous entend que la classe ClockTime expose une méthode reset() dans son interface publique.
  • Dans les états 6 et 7 (ALARM_HOUR_SETUP et ALARM_MINUTE_SETUP), l'horloge revient à l'état 1 (HOUR_DISPLAY). Aucune action n'est particulièrement nécessaire, puisque les pressions sur les boutons de réglage modifient directement l'attribut __alarmTime.

Méthode __onButtonDownPress()

Cette méthode est invoquée par la méthode onPress() lorsque l'utilisateur effectue une pression brève sur le bouton de décrémentation. Ce qui correspond au traitement de l'événement (C) de l'automate. Comme pour les événements précédents, le traitement à effectuer dans cette méthode est extrait du tableau répertoriant les actions de transition de l'automate. En fonction de l'état de l'horloge, l'action consiste à décrémenter la valeur considérée. Sauf pour l'état 8, l'horloge reste dans l'état où elle est.
EtatActionEtat suivant
4
Réglage HH
Décrémentation de HH
4
5
Réglage MN
Décrémentation de MN
5
6
Réglage HH Alarme
Décrémentation Alarme HH
6
7
Réglage MN Alarme
Décrémentation Alarme MN
7
8
Alarme Déclenchée
Arrêt de l'alarme
1
void Clock::__onButtonDownPress(Button*) {
  switch (this->__clockStatus) {
    case HOUR_SETUP:
      this->__setupTime.decHH();
      this->__display.changeTime(this->__setupTime);
      break;
    case MINUTE_SETUP:
      this->__setupTime.decMN();
      this->__display.changeTime(this->__setupTime);
      break;
    case ALARM_HOUR_SETUP:
      this->__alarmTime.decHH();
      this->__display.changeTime(this->__alarmTime);
      break;
    case ALARM_MINUTE_SETUP:
      this->__alarmTime.decMN();
      this->__display.changeTime(this->__alarmTime);
      break;
    case ALARM_ACTIVE:
      this->__changeStatus(HOUR_DISPLAY);
      this->__alarm.Off();
      break;
  }
}
  • Dans l'état 4 (HOUR_SETUP), la valeur à décrémenter est la valeur HH de l'attribut __setupTime en invoquant la méthode decHH(). La valeur de l'attribut modifiée est notifiée au dispositif d'affichage.
  • Dans l'état 5 (MINUTE_SETUP), la valeur à décrémenter est la valeur MN de l'attribut __setupTime en invoquant la méthode decMN(). La valeur de l'attribut modifiée est notifiée au dispositif d'affichage.
  • Dans l'état 6 (ALARM_HOUR_SETUP), la valeur à décrémenter est la valeur HH de l'attribut __alarmTime en invoquant la méthode decHH(). La valeur de l'attribut modifiée est notifiée au dispositif d'affichage.
  • Dans l'état 7 (ALARM_MINUTE_SETUP), la valeur à décrémenter est la valeur MN de l'attribut __alarmTime en invoquant la méthode decMN(). La valeur de l'attribut modifiée est notifiée au dispositif d'affichage.
  • Dans l'état 8 (ALARM_ACTIVE), le signal sonore de l'alarme est audible.  Comme pour la pression brève du bouton de commande, il faut l'éteindre en invoquant la méthode Off() du dispositif d'alarme pour revenir à l'état 1 (HOUR_DISPLAY). 

Méthode __onButtonUpPress()

Cette méthode est invoquée par la méthode onPress() lorsque l'utilisateur effectue une pression brève sur le bouton d'incrémentation. Ce qui correspond au traitement de l'événement (D) de l'automate. Comme pour les événements précédents, le traitement à effectuer dans cette méthode est extrait du tableau répertoriant les actions de transition de l'automate. En fonction de l'état de l'horloge, l'action consiste à décrémenter la valeur considérée. Sauf pour l'état 8, l'horloge reste dans l'état où elle est.
EtatActionEtat suivant
4
Réglage HH
Incrémentation de HH
4
5
Réglage MN
Incrémentation de MN
5
6
Réglage HH Alarme
Incrémentation Alarme HH
6
7
Réglage MN Alarme
Incrémentation Alarme MN
7
8
Alarme Déclenchée
Arrêt de l'alarme
1
void Clock::__onButtonUpPress(Button*) {
  switch (this->__clockStatus) {
    case HOUR_SETUP:
      this->__setupTime.incHH();
      this->__display.changeTime(this->__setupTime);
      break;
    case MINUTE_SETUP:
      this->__setupTime.incMN();
      this->__display.changeTime(this->__setupTime);
      break;
    case ALARM_HOUR_SETUP:
      this->__alarmTime.incHH();
      this->__display.changeTime(this->__alarmTime);
      break;
    case ALARM_MINUTE_SETUP:
      this->__alarmTime.incMN();
      this->__display.changeTime(this->__alarmTime);
      break;
    case ALARM_ACTIVE:
      this->__changeStatus(HOUR_DISPLAY);
      this->__alarm.Off();
      break;
  }
}
  • Dans l'état 4 (HOUR_SETUP), la valeur à incrémenter est la valeur HH de l'attribut __setupTime en invoquant la méthode incHH(). La valeur de l'attribut modifiée est notifiée au dispositif d'affichage.
  • Dans l'état 5 (MINUTE_SETUP), la valeur à incrémenter est la valeur MN de l'attribut __setupTime en invoquant la méthode incMN(). La valeur de l'attribut modifiée est notifiée au dispositif d'affichage.
  • Dans l'état 6 (ALARM_HOUR_SETUP), la valeur à incrémenter est la valeur HH de l'attribut __alarmTime en invoquant la méthode incHH(). La valeur de l'attribut modifiée est notifiée au dispositif d'affichage.
  • Dans l'état 7 (ALARM_MINUTE_SETUP), la valeur à incrémenter est la valeur MN de l'attribut __alarmTime en invoquant la méthode incMN(). La valeur de l'attribut modifiée est notifiée au dispositif d'affichage.
  • Dans l'état 8 (ALARM_ACTIVE), le signal sonore de l'alarme est audible.  Comme pour la pression brève du bouton de commande, il faut l'éteindre en invoquant la méthode Off() du dispositif d'alarme pour revenir à l'état 1 (HOUR_DISPLAY). 

Méthode __changeStatus()

La plupart du temps, le traitement d'un  événement d'un automate entraîne un changement d'état. En principe, modifier l'attribut __clockStatus devrait suffire. Cependant, la modification de cet attribut doit aussi être notifiée au dispositif d'affichage pour adapter l'affichage à l'état de l'horloge. La méthode __changeStatus() permet de factoriser toutes les opérations accompagnant les changements d'état. Elle est invoquée dans tous les traitements des événements qui entraînent un changement d'état.
void Clock::__changeStatus(ClockStatus newStatus) {
  this->__clockStatus = newStatus;
  this->__display.changeStatus(newStatus);
}

Conclusion

Pour faire pendant au fichier Clock.h contenant la définition de la classe Clock, le code présenté dans cet article, constituant l’implémentation  de la classe, c'est à dire la programmation de celle-ci en C++, doit être rassemblé dans un fichier Clock.cpp. Dans le cas ou cette classe serait publiée comme une bibliothèque compilée pour être utilisé dans un autre projet, seul le fichier Clock.h est à communiquer aux programmeurs. Le code détaillé de la classe peut rester confidentiel.
Ici s'achève l'implémentation de la classe Clock. On constate que la conception objet  que permet le langage C++ permet d'organiser le code de façon claire et compacte.
Néanmoins, la classe Clock, telle que codée ici, fait référence à de nombreux objets qu'elle utilise. Chacun d'eux sera présenté dans un article dédié, dans la suite de la collection relative au projet Horloge.
  • La classe Time, utilisée pour les attributs __setupTime et __alarmTime doit implémenter un objet doté de trois valeurs entières HH, MN et SS qu'il faut pouvoir régler avec les méthodes incHH(), decHH(), incMN() et decMN() exposées dans son interface publique.  La classe Time devra aussi exposer un opérteur == permettant de comparer la valeur du temps dans deux objets différents.
  • La classe ClockTime utilisée pour l'attribut __clockTime, doit offrir les mêmes caractéristiques que la classe Time, avec en outre l'implémentation du design pattern Observer pour émettre des événements ClockTimeEvent recevables par la classe Clock pour générer les tics relatifs à l'événement (E) de l'automate de l'horloge. La classe ClockTime doit en outre exposer un méthode reset() pour remettre à l'heure en fonction des réglage effectués.
  • La classe Button permet de gérer de bouton poussoir. Elle a déjà fait l'objet d'un article complet, comme exemple de l'exploitation de la bibliothèque Event pour la gestion des événements en C++ sur un Arduino.
  • La classe ClockDisplay, utilisée pour l'attribut __clockDisplay, est une classe générique permettant de gérer un dispositif d'affichage pour l'horloge. Plusieurs déclinaison de cette classe seront proposées dans des articles particulier pour prendre en charge des composants électroniques différents, comme un écran LCD ou un affichage LED 7 segments. Elle doit exposer les méthodes suivantes dans son interface publique :
    • changeTime() pour notifier le dispositif d'affichage du changement de l'affichage de l'heure.
    • changeStatus() pour notifier le dispositif d'affichage du changement d'état de l'horloge et adapter l'affiche en fonction.
    • changeAlarm() pour notifier le dispositif d'affichage du changement d'activation/désactivation de l'alarme (pour afficher une petite cloche sur l'écran LCD lorsque l'alarme est activée par exemple). 
  • La classe ClockAlarm, utilisée pour l'attribut __clockAlarm, est une classe générique permettant de gérer un dispositif d'alarme sonore. Une déclinaison de cette classe, exploitant un petit buzzer passif, sera proposée dans un article ultérieur comme exemple. Mais elle pourra être déclinée pour un radio-réveil pour déclencher un récepteur radio.  Elle doit exposer les méthodes suivantes dans son interface publique :
    • On() pour déclencher le signal sonore de l'alarme.
    • Off() pour l'éteindre.
    • La méthode isOn() peut être ajoutée pour retourner l'état du dispositif pour savoir si le signal est émis ou non.

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