//Ordonnanceur de tâches pour arduino
//Bricoleau 2016
//V1.0

#ifndef ordonnanceur_h
#define ordonnanceur_h

#include <Arduino.h>

//Définition du type de fontions associées aux tâches
typedef void (*fonctionVoid)();//correspond à toute fonction du style "void ma_fonction()"

//Une tâche est définie par :
//- une fonction à exécuter
//- une période entre deux exécutions (en millisecondes)
class tache
{
  public :
    //Constructeur
    tache(fonctionVoid fonction, uint32_t periode_ms);

    //Méthodes principales
    void changerFonction(fonctionVoid nouvelle_fonction);
    void changerPeriode(uint32_t nouvelle_periode_ms);
    void recaler(); //Voir notes explicatives plus bas

    //Méthodes secondaires
    void executer();
    uint32_t periode() const {return this->_periode;}
    uint32_t millis_prochaine_exec() const {return this->_millis_prochaine_exec;}

  private :
    fonctionVoid _fonction;
    uint32_t     _periode;
    uint32_t     _millis_prochaine_exec;
};

//L'ordonnanceur intègre la gestion du watchdog.
//Le watchdog est un dispositif hardware
//qui provoque un reset de l'arduino si une tâche a une durée anormale.
const uint8_t WATCHDOG_INACTIF    = 0;
const uint8_t WATCHDOG_1_SECONDE  = 1;
const uint8_t WATCHDOG_2_SECONDES = 2;
const uint8_t WATCHDOG_4_SECONDES = 3;
const uint8_t WATCHDOG_8_SECONDES = 4;

class ordonnanceur_c
{
  public :
    //Constructeur
    ordonnanceur_c();

    //Méthode principale, à appeler en fin de setup(), dont on ne ressort jamais sauf arrêt de l'ordonnanceur
    void lancer(const uint8_t watchdog = WATCHDOG_INACTIF);

    //Méthodes secondaires, appelables depuis une tâche
    void stopper();
    void reboot();
    bool estActif() const          {return this->_statut > 127;}

    void modifierWatchdog(const uint8_t watchdog);
    void desactiverWatchdog()      {this->modifierWatchdog(WATCHDOG_INACTIF);}
    const uint8_t watchdog() const {return this->_statut & 7;}
    bool watchdogInactif() const   {return this->watchdog() == WATCHDOG_INACTIF;}
    bool watchdogActif() const     {return this->watchdog() != WATCHDOG_INACTIF;}

    uint16_t ramDisponible() const {return this->_ramDisponible;} //Ram disponible hors exécution des tâches
    uint8_t  chargeCPU1s() const   {return this->_chargeCPU1s;}   //Pourcentage d'occupation CPU sur 1 seconde
    uint8_t  chargeCPU5s() const   {return this->_chargeCPU5s;}   //Pourcentage d'occupation CPU sur 5 secondes

  private :
    uint8_t  _statut, _chargeCPU1s, _chargeCPU5s;
    uint16_t _ramDisponible;
    uint32_t _tempo1s[2], _tempo5s[2];
    void gererWatchdog();
    void actualiserRamDisponible();
    void actualiserChargeCPU(uint32_t);
};

extern ordonnanceur_c ordonnanceur;

#endif

/* modèle d'implémentation dans le programme principal arduino :
#include "ordonnanceur.h"

... // définir au moins une tâche

void setup()
{
  ...
  ordonnanceur.lancer();
}

void loop() {}
*/


/****************************************************************************************
//Notes :
//
//Ceci est un ordonnanceur coopératif basique : les tâches ne s'interrompent pas entre elles.
//
//La fonction associée à une tâche doit s'exécuter rapidement.
//   ==> Ne jamais utiliser l'instruction delay() !
//   Si une pause est nécessaire, terminer la fonction et gérer la suite du traitement lors de l'appel suivant.
//   Utiliser des variables statiques pour conserver l'état des données entre les appels.
//   Il est aussi possible de modifier la fonction associée à la tâche et/ou sa période, selon le contexte.
//   Voir les exemples fournis avec la bibliothèque.
//
//Utiliser des variables globales pour passer des données depuis une tâche vers une autre.
//   La communication entre tâches est asynchrone.
//
//Au démarrage, le premier appel à chaque tâche est effectué dès que possible.
//   Une fonction de type setup() peut être associée à la tâche pour sa première exécution,
//   puis être remplacée par une fonction de type loop().
//
//L'utilisation du watchdog permet de contourner les cas de bug conduisant à un blocage de l'arduino.
//   Elle est recommandée dans les programmes qui utilisent <Wire.h> ou <Ethernet.h>.
//
//Une tâche peut avoir une période à 0.
//   C'est alors une tâche de fond, exécutée aussi souvent que possible.
//   Exemple type : surveillance des entrées numériques de l'arduino (bouton poussoir, ...).
//   Une tâche de fond doit avoir la durée d'exécution la plus courte possible.
//
//Pour désactiver une tâche, lui associer la fonction NULL.
//
//La méthode tache.executer() est en principe réservée à l'ordonnanceur.
//   Mais elle peut aussi être appelée depuis une autre tâche.
//   Cette possibilité est utile lorsqu'il y a nécessité de fluidifier l'enchaînement entre les tâches,
//   par exemple entre une tâche qui lit un capteur et une tache qui exploite la valeur lue.
//
//L'ordonnanceur essaie de rattraper tout retard, afin de maintenir une période régulière.
//   ==> Le délai entre deux exécutions peut donc être inférieur à la période fixée.
//   Par exemple avec une période de 1000 ms : si la première exécution est à millis()=15,
//   la suivante reste planifiée à millis() = 1000.
//   Si besoin, recaler() permet de forcer la prochaine execution à maintenant+période.
//   Nb : recaler() est automatique si les tâches durent trop longtemps et que l'ordonnanceur est saturé.
//
/****************************************************************************************/
