Projet Réveil-Matin (partie VIII) - Gestion de l'alarme (classe ClockAlarm)

Dans l'article traitant de l'implémentation de la classe Clock, le besoin d'une classe implémentant le système d'alarme avait été exprimé. La classe utilisée avait été appelée ClockAlarm. Et elle devait exposer trois méthodes dans son interface publique :
  • On() pour déclencher le signal d'alarme.
  • Off() pour stopper le signal d'alarme.
  • isOn() pour indiquer l'état du signal d'alarme.
Il faut faire ici un précision sémantique. En effet, il y a une confusion sur le concept d'activation de l'alarme :
  • L'activation (ou la désactivation) de l'alarme est une action déclenchée sur l'horloge par une pression longue du bouton de commande. Elle est matérialisée dans la classe Clock par l'attribut __alarmStatus. L'état d'activation de l'alarme peut être manifesté sur le dispositif d'affichage, par exemple par une petite cloche sur l'écran LCD. Ce concept est géré au niveau de la classe Clock et n'intervient en aucun cas dans les objets ClockAlarm.
  • Le déclenchement de l'alarme est une action déclenchée par l'automate de l'horloge lorsque l'alarme est activée (concept précédent) et que l'heure courante est égale à l'heure de déclenchement de l'alarme. Cela est matérialisé d'une part par un changement d'état de l'horloge qui passe dans l'état ALARM_ACTIVE et d'autre part par le changement de la valeur de l'attribut _alarmStatus de la classe ClockAlarm qui passe à true. Changement qui est effectué par l'invocation de la méthode On() de l'objet ClockAlarm. Cela se manifeste par l'émission d'un signal sonore émis par l'objet ClockAlarm et éventuellement par le clignotement de l'heure sur le dispositif d'affichage.
Comme pour le dispositif d'affichage, le développement du système d'alarme dépend étroitement des composants électroniques utilisés. Et, pour assurer le découplage entre la classe relative au dispositif d'alarme et la classe Clock, on va créer une couche d'abstraction intermédiaire sous la forme d'une classe abstraite, déjà nommée ClockAlarm au moment de l'implémentation de la classe Clock. Cette classe va exposer les trois méthodes exigées. Elle pourra alors être dérivée pour gérer des dispositif de déclenchement diverses (buzzer, radio-réveil, etc.). Comme exemple, la classe BuzzerClockAlarm sera présentée pour exploiter un buzzer passif programmé pour émettre un signal deux-tons.

Définition de la classe abstraite ClockAlarm

/* 1 */
#ifndef ClockAlarm_h
#define ClockAlarm_h

/* 2 */
class ClockAlarm : public Component
{
  protected:
/* 3 */
  bool _alarmStatus;

  public:
/* 4 */
  ClockAlarm();
/* 5 */
  virtual void Off();
  virtual void On();
  virtual bool isOn();
};

/* 1 */
#endif // ClockAlarm_h
  
  1. Application de la bonne pratique de définir les classes d'une bibliothèque entre les deux directives #ifndef  et #endif en prévention de définitions multiples dans les projets composites.
  2. Le dispositif d'alarme est un composant Arduino. Il doit implémenter l'interface Component. Ce qui l'oblige à implémenter les méthodes begin() et loop(). La classe ClockAlarm étant abstraite l'implémentation de ces méthodes n'est pas nécessaire. Cependant, elle sera indispensable dans les classe concrètes dérivées pour que ces dernières puisent être instanciées.
  3. La classe ClockAlarm ne possède qu'un seul attribut booléen _alarmStatus indiquant si le signal d'alarme est émis ou non. Il est déclaré dans l'interface protégée de la classe pour pouvoir être exploité dans les classes dérivées. 
  4. La classe ClockAlarm ne possède qu'un seul constructeur sans paramètre. En revanche les classes dérivées devront disposer d'un constructeur permettant de passer en paramètres le numéros de toutes les pins de l'Arduino mobilisées par le  dispositif d'alarme. 
  5. Les trois méthodes exigées pas la classe Clock sont déclarées dans l'interface publique de la classe.

Implémentation de la classe ClockAlarm

Constructeur de la classe ClockAlarm

ClockAlarm::ClockAlarm() {
  this->_alarmStatus = false;
}
La classe ClockAlarm ne dispose que d'un seul constructeur sans paramètre. Il ne sert qu'à initialiser les attributs. L'attribut _alarmStatus est initialisé à false, indiquant ainsi que les signal sonore est pur le moment muet.

Méthode On()

void ClockAlarm::On() {
  this->_alarmStatus = true;
}
Cette méthode est invoquée par la classe Clock lorsque l'alarme est activée et que l'heure courante de l'horloge est égale à l'heure de déclenchement. Le signal sonore du dispositif doit donc être activé en positionnant la valeur de l'attribut _alarmStatus à true.

Méthode Off()

void ClockAlarm::Off() {
  this->_alarmStatus = false;
}
Cette méthode est invoquée par la classe Clock lorsque l'utilisateur appuie un bouton pour arrêter l'alarme. L'attribut _alarmStatus repasse à la valeur false.

Méthode isOn()

bool ClockAlarm::isOn() {
  return this->_alarmStatus;
}
Cette méthode permet de connaitre l'état du dispositif d'alarme. Elle retourne en résultat tout simplement la valeur de l'attribut _alarmStatus.

Exemple d'implémentation d'une classe concrète du dispositif d'alarme : la classe BuzzerClockAlarm

L'implémentation la plus simple d'un dispositif d'alarme est d'utiliser un buzzer passif. Le câblage buzzer  est très simple. Il suffit de relier le pole + de celui-ci à l'une des pins de l'Arduino et le pole - à la masse.
Pour émettre un son sur le buzzer, on utilise la fonction tone() de l'Arduino qui génère un signal carré sur la pin dont le numéro et la fréquence sont passé en paramètre.
Pour l'horloge, le signal sonore sera constitué de deux fréquences 440 Hz et 880 Hz émises alternativement toutes les 0,5 s.

Définition de la classe BuzzerClockAlarm

#include "ClockAlarm.h"

/* 1 */
class BuzzerClockAlarm : public ClockAlarm, public TimerListener
{
/* 2 */
  static const int TONE_HIGH = 880;   // Fréquence du signal haut.
  static const int TONE_LOW = 440;    // Fréquence du signal bas.
  static const int DURATION = 500;    // Durée de chaque signal.

/* 3 */
  uint8_t __pinBuzzer;
  Timer __timer;
  bool __tone_high;

/* 4 */
  virtual void onTick(Timer*);

  public:
/* 5 */
  BuzzerClockAlarm(uint8_t);
/* 6 */
  virtual void begin();
  virtual void loop();
};
  1. La classe BuzzerClockAlarm dérive la classe ClockAlarm. Elle dérive aussi l'interface TimerListener pour pouvoir recevoir des tics toutes les 500 ms. Ce qui permet de changer de fréquence lorsque le signal d'alarme est déclenché.
  2. La classe définit trois constantes de classes pour ses besoins propres de fonctionnement :
    • TONE_HIGH, dont la  valeur est de 880 Hz, est la fréquence haute du deux-tons.
    • TONE_LOW, dont la valeur est de 440 Hz, est la fréquence basse du deux-ton.
    • DURATION, dont la valeur est de 500 ms, est la durée du son émis par la fonction tone()
  3. Dans l'interface privée de la classe BuzzerClockAlarm, on déclare les trois attributs dont a besoin la classe pour fonctionner :
    • __pinBuzzer contient le numéro de pin de l'Arduino sur laquelle est connecté le buzzer passif. Il est reçu en paramètre du constructeur de la classe. L'archivage de ce numéro dans un attribut privé est nécessaire pour le conserver en attendant que la pin soit physiquement initialisée par la fonction pinMode() par l'invocation de la méthode begin(). Cela permet de découpler, d'une part,  l'initialisation de ce qui est du ressort du langage C++ qui se fait dans le constructeur, et d'autre part ce qui ressort de l'initialisation électronique qui se fait  en différé dans la méthode begin() invoquée par le programme utilisant le composant..
    • __timer est le timer utilisé pour rythmer le deux-tons du signal d'alarme. Il est initialisé et paramétré dans le constructeur de la classe.
    • __tone_high est la valeur booléenne indiquant quelle tonalité est jouée. Si la valeur est true, la fréquence émise est TONE_HIGH (880 Hz). Si la valeur est false, la fréquence émise est TONE_LOW (440 Hz). L'attribut passe d'une valeur à l'autre à chaque réception d'un événement du timer.
  4. La méthode onTick() est héritée de l'interface TimerListener. Elle est invoquée à la fréquence définie par la constante DURATION, soit toutes les 500 millisecondes.
  5. Le constructeur de la classe BuzzerClockAlarm reçoit en paramètre le numéro de la pin de l'Arduino sur laquelle est connecté le buzzer. 
  6. La classe BuzzerClockAlarm doit implémenter les deux méthodes abstraites begin() et loop() héritées de Component qui, contrairement à ce qui a été effectué pour la classe abstraite ClockDisplay, n'ont pas encore été implémentées dans la classe abstraite ClockAlarm

Implémentation de la classe BuzzerClockAlarm

L'implémentation de classe concrète pour un dispositif d'alarme consiste à :
  • Créer un constructeur pour la classe concrète. Celui-ci doit recevoir en paramètre les numéros des pins utilisées par les composants électroniques qui composent le dispositif d'alarme. Tous les numéros de pins doivent être attribués à des attributs internes.
  • Surcharger la méthode begin() pour initialiser les composants électroniques du dispositif d'affichage et notamment comment les pins de l'Arduino sont utilisés. Si des composants C++ sont utilisés, il faut invoquer aussi leur méthode begin() respective.
  • Surcharger la méthode loop() pour implémenter le fonctionnement du dispositif. Sans oublier d'invoquer également la méthode loop() de chacun des composants  C++ utilisés.
Dans le cas de la classe BuzzerClockAlarm, il faudra également implémenter la méthode onTick() héritée e l'interface TimerListener.
#include "ClockAlarm.h"

/* 1 */
BuzzerClockAlarm::BuzzerClockAlarm(uint8_t buzzer_pin) :
  ClockAlarm(), __timer(DURATION)
{
  this->__pinBuzzer = buzzer_pin;
  this->__tone_high= false;
  this->__timer.addListener(this);
}

/* 2 */
void BuzzerClockAlarm::begin() {
  this->__timer.begin();
}

/* 3 */
void BuzzerClockAlarm::loop() {
  this->__timer.loop();
}

/* 4 */
void BuzzerClockAlarm::onTick(Timer* sender) {
  if (this->_alarmStatus) {
    this->__tone_high = !this->__tone_high;
    tone(this->__pinBuzzer, this->__tone_high ? TONE_HIGH : TONE_LOW, DURATION);
  }
}
  1. Le constructeur reçoit le paramètre buzzer_pin qui est le numéro de pin de l'Arduino auquel est connecté le buzzer. Puis il effectue les opérations suivantes :
    • Invocation du constructeur de la classe parente ClockAlarm.
    • Invocation du constructeur de l'attribut __timer en passant en paramètre la constante DURATION (500 ms).
    • Initialisation de l'attribut __pinBuzzer avec la valeur de buzzer_pin passée en paramètre.
    • Initialisation de l'attribut __tone_high à false. Ce qui fait que, comme au déclenchement du signal d'alarme, on inverse la valeur de __tone_high (voir méthode onTick()), la première fréquence émise par le deux-ton est 880 Hz. 
    • Inscription de l'instance de la classe BuzzerClockAlarm créée comme récepteur du timer __timer
  2. Dans la méthode begin() de la classe BuzzerClockAlarm, il n'y a qu'une seule chose à faire. C'est d'invoquer la méthode begin() du timer. L'utilisation de la fonction tone() de l'Arduino ne nécessite aucune configuration particulière de la pin par  pinMode(). A partir de ce moment le timer __timer émet des événements TimerEvent toutes les 500  millisecondes. Ce qui déclenche à chaque fois l'invocation de la méthode onTick().
  3. La méthode loop() est invoquée en boucle pendant le fonctionnement du projet Arduino. Dans la classe BuzzerClockAlarm, il suffit d'invoquer la méthode loop() du __timer. Ce qui déclenche l'envoi d'un événement TimerEvent et entraîne l'exécution de la méthode onTick().
  4. La méthode onTick() de la classe BuzzerCLockEvent est invoquée toutes les 500 millisecondes, du fait de l'enregistrement des instances créée comme récepteurs de __timer.
    le signal sonore n'est émis que si l'attribut _alarmStatus hérité de la classe ClockAlarm a la valeur true. Ce qui arrive lorsque la méthode On() est invoquée. Dans ce cas, on bascule la valeur de l'attribut __tone_high. Et, si sa valeur est true, la fréquence du signal émis sur la pin __pinBuzzer est TONE_HIGH (880 Hz) sinon c'est TONE_LOW (440Hz). L'effet obtenu est le fameux pin-pon des pompiers.

Utilisation de la classe BuzzerClockDisplay dans un sketch Arduino

#include "SerialClockDisplay.h"
#include "ClockAlarm.h"
#include "Clock.h"

// Déclaration des pins de connexion des boutons de commande.
#define BTN_CMD 5
#define BTN_UP 3
#define BTN_DOWN 4
// Déclaration de la pin de connexion du buzzer passif.
#define BUZZER 2

SerialClockDisplay clockDisplay;
BuzzerClockAlarm clockAlarm(BUZZER);
Clock myclock(clockDisplay, clockAlarm, BTN_CMD, BTN_UP, BTN_DOWN);


void setup() {
  myclock.begin();
}

void loop() {
  myclock.loop();
}
L'utilisation de la classe BuzzerClockAlarm dans un sketch Arduino est relativement simple. Elle consiste à déclarer une instance clockAlarm en passant en paramètre le numéro de pin de l'Arduino sur laquelle est connecté le buzzer (ici la pin n° 2) et de passer cette instance comme deuxième paramètre du constructeur de la classe Clock lors de l'instanciation de celle-ci dans la variable myclock.

Conclusion

Nous disposons maintenant de tous les composants nécessaire au projet Horloge, une classe Clock pour la logique générale du projet, une classe concrète du dispositif d'affichage SerialClockDisplay pour afficher l'heure sur le canal Série de l'Arduino et d'une classe concrète BuzzerClockAlarm pour déclencher un signal d'alarme. Le projet est donc opérationnel et peut être testé dans toutes ses fonctionnalités, y compris le déclenchement du signal d'alarme.
Cependant le projet ne sera complètement achevé que lorsque l'on pourra disposer d'un dispositif d'affichage sur un écran LCD conformément à ce qui a été présenté dans le cahier des charge du projet. Dans le prochain article, qui clôturera la série, la classe LCDClockDisplay sera présentée pour implémenter un tel dispositif.

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