LogClock & Cuckoo

De Wiki LOGre
Aller à : navigation, rechercher


Encore un projet néo-ringard : le LOGreCuckoo

LOGre Cuckoo - un coucou web 3.0 -

Genèse

Dans la tonne de vieilleries données au LOGre, figuraient deux énormes vumètres (un Voltmètre et un Ampèremètre) et toujours de nombreux vumètres Pekly de tailles plus raisonnables. Ayant déjà le GalvaCuckoo à la maison, il fallait trouver un lieu d'accueil pour ces vumètres destinés à la casse. J'ai donc décidé d'offrir une (autre) horloge au LOGre qui possède déjà une JIX Clock. Combat des technologies : le 19ème siècle contre le 21ème !!

Donc les deux gros galvanomètres seront dédiés aux heures et minutes, que faire d'original pour afficher les secondes ? L'idée retenue est de simuler le mieux possible la rotation d'une trotteuse d'une montre. Ce fut rapidement fait en installant deux galva en tête-bêche, l'un ayant une aiguille tournant dans le sens horaire de 0 à 30 secondes, et l'autre la tête en bas, prenant le relais à la trentième seconde avec une aiguille allant de 30 à 60 secondes !

Une fois l'heure affichée, que pouvait-on faire ? j'ai donc repris l'idée du coucou suisse, agrémenté du carillon de Westminster à midi et de BigBen à minuit. J'ai rajout un carillon de vieille horloge à la demie heure.Et comme j'avais un lecteur de fichiers mp3, je complète les fonctions avec divers modes de diffusion de musique d'ambiance (séquentiel sur un répertoire de 150 morceaux, accès direct à un morceau, jouer Joyeux Anniversaire).

Version finale


Côté avec haut parleur


Un Coucou web 3.0

Pas de bouton de mise à l'heure, pas de boutons pour choisir un mode, régler un volume ... Tout se fait via le web !

Le cœur du coucou est un chip ESP8266 (fournisseur WeMos), qui gère le mouvements des aiguilles des vu-mètres, offre un serveur web, dispose d'un client UDP, intègre le protocole zeroconf 'bonjour'), et un client NTP.

En conséquence, le LOGreCuckoo se connecte automatiquement au réseau wifi du LOGre (via le WifiMAnager), puis synchronise l'horloge temps réel avec un serveur du NIST (aux USA) pour récupérer l'heure exacte et offre un serveur web pour recevoir les commandes des utilisateurs du LOGal.

Cherry on the cake : le soft du LOGreCuckoo est modifiable via le wifi !! Merci à Oliv de m'avoir fait découvrir le mode OTA (Over The Air) pour modifier le programme sans connexion USB.

L'horloge et son soft sont donc un projet ouvert, disponible à tous pour être amélioré, pour autant qu'on ne le laisse pas dans un état pire que celui que je livre aujourd'hui.

Matériel nécessaire

  • un ESP8266 WeMos  : 3,50 € chez AliExpress
  • une horloge temps réel Dallas Semiconductor DS1307 (sauvegardée par batterie) : 0,76 € chez AliExpress
  • un module de lecture de fichiers audio (FN-M16P) : 1,95 € chez AliExpress
  • une carte SD 4GB : 1,78 € chez AliExpress
  • quelques composants : résistances, potentiomètres, diode, switch : 0 € (récupération)

Soit une horloge pour moins de 10 € !!

Transformation des galvanomètres

Petit rappel grâce à wikipedia:

Un galvanomètre à cadre mobile, est constitué d'une bobine montée sur pivot, baignant dans le champ magnétique d'un aimant fixe. S ur cette bobine est fixée l'aiguille de visualisation et un ressort chargé de rappeler l'équipage mobile dans la position indiquant le zéro. La bobine de faible impédance est branchée en série dans le circuit dans lequel circule le courant à mesurer (ou le courant de commande dans mon cas). Le courant, en traversant la bobine, induit dans celle-ci un champ électromagnétique, ce qui provoque un pivotement par répulsion des champs magnétiques. Plus le courant est intense plus la bobine bascule. Il ne fonctionne qu'en courant continu.

Dans le stock donné, je trouve des voltmètres et ampèremètres, donc il faut les adapter pour les faire fonctionner avec les sorties PWM de l'Arduino.

C'est en fait très simple, il faut trouver la bonne valeur de la résistance interne à insérer avant la bobine pour que la valeur 255 (Vcc) de l'analogWrite soit au maximum de l'échelle. Je perce le corps du galva et insère (colle) un micro potentiomètre de 47kohms qui sera réglé in situ.

Restait à imprimer les écrans gradués en H, Mn et Secondes. Merci à Pekly, il y avait des écrans nativement gradués en 15 graduations (donc pour les heures) et en 300 pour les 60 minutes. Avec Pixelmator (photoshop pour Mac), ce fut très simple. Quant à celui des secondes, il était déjà gradué de 0 à 60, il ne restait qu’à imprimer les valeurs et l’unité de mesure !!


Migration d'une horloge de base vers un coucou suisse.

Par rapport à mon premier projet, j'ai opté pour un module mp3 plus petit (FN-M16P Embedded MP3 Audio Module), pilotable via une liaison série pilotée utilisant la librairie DFRobotDFPlayerMini.hde http://www.dfrobot.com. Le module est assez fragile, toutes les commandes doivent être testées, car tout ne fonctionne pas as designed.

Horloge temps réel

Module RTC à base de DS1307 et fonctionnant sur BUS I2C. Avec l'utilisation de la bibliothèque adaptée (Wire.h) et les exemples du playground Arduino, pas de problèmes particuliers. Utilisation de la libraire RtcDS1307.h . Dans le version utilisé, j'ai corrigé un bug pour le set de l'heure dans le RTC.

Fonctionnement

A la première mise sous tension dans un environnement nouveau, le wifi manager du Cuckoo le transforme en point d'accès. Il est donc visible avec le SSID "logre-coucou". Une fois connecté au point d'accès, le wifi manager propose la liste des SSID disponibles (dont le réseau wifi officiel du lieu) et des boutons pour choisir quelle action faire. Il suffit de choisir le SSID du LOGal et de rentrer le mot de passe. Si la fenêtre ne s'ouvre pas, l'URL est 192.168.4.1

L'ESP8266 passe en mode client et se connecte au réseau local.

Il n'y a pas de console, donc pour connaitre l'adresse IP sur le réseau hôte, il faut utiliser un scanner d'adresses IP ou attendre quelques secondes que le Cuckoo soit accessible via son nom de host : logre-coucou.local

Le Cuckoo a continué son cycle de démarrage et l'aiguille des heures affiche différents états (heures) à chaque étape.

Une fois que le cycle de démarrage est fini, le coucou sonne l'heure ronde du moment puis le tictac de l'horloge devient audible !!

  • A chaque heure, le coucou chante autant de fois que l'heure ( feature indispensable pour les non voyants)
  • A midi, c’est le carillon de Westminster (1 fois)
  • A minuit, sonnent les 12 coups de BigBen
  • A chaque demie heure, carillon d'horloge comtoise.

Petite singularité d'affichage, l’horloge va de 1 AM à 13:59 heures et de 2PM à 0:59 AM !! C’est ma norme d'affichage !!

Commandes via le web

On peut piloter le Cuckoo depuis le web:

*    http://logre-coucou.local/   réponse = A que Coucou est vivant!
*    http://logre-coucou.local/mp3?t=yy avec yy = numéro de la piste (< 150) 
*    http://logre-coucou.local/vol?v=xx avec xx : volume 0 à 30 
*    http://logre-coucou.local/loop  joue toutes les pistes séquentiellement
*    http://logre-coucou.local/stop  stop la musique et relance le coucou et tictac
*    http://logre-coucou.local/anniv joue HappyBirthday de Stevie Wonder lors d'un anniversaire

Evolution

Le Cuckoo est cèdé au LOGre, ce la devient un projet pour tous. Il peut être amélioré avec de nouvelles fonctionnalités par tout membre du LOGre. Il est accessible via l'IDE Arduino. Dans le menu 'outils', le port de connexion indique 'logre-coucou at 192.168.0.xxx '

Schéma de connexion

Schéma-cuckoo.png

Code Arduino livré

C'est la version 1.0, restent quelques améliorations, simplifications à faire. L'important est que ça marche et que j'ai pris un grand plaisir à le faire.

/* LOGre Cuckoo
 * version 1.0
 * by Pja - LOGre - Grenoble
 * April 5, 2017
 *  ESP8266 + RTC DS1307, + MP3 module DFPlayerMP3 + NTP time resynchro
 *  NTP and RTC sync, thanks to Evan Allen
 *  with DST management (through array until 2022)
 *  OTA for further evolution
 *  mDNS for zeroconf DNS management
 *  
 *  First installation, use Wifi manager at 192.168.4.1 to setup network credentials
 *  Second connection, the LOGre Clock connects directly to the network
 *  
 *  web usage : http://logre-coucou.local/ + command with parameters
 *    http://logre-coucou.local/   answer = A que Coucou est vivant!
 *    http://logre-coucou.local/mp3?t=yy with yy = track number (< 150) 
 *    http://logre-coucou.local/vol?v=xx with xx : volume 0 to 30 
 *    http://logre-coucou.local/loop  play the full directory of mp3 tracks
 *    http://logre-coucou.local/stop  stop the music and starts the cuckoo
 *    http://logre-coucou.local/anniv play HappyBirthday on demand
 *   
 */
 
#include "Wire.h"                 // lib used for Real Time Clock
#include "SoftwareSerial.h"       // lib used for DFPlayerMP3
#include "DFRobotDFPlayerMini.h"  // lib for MP3player
#include <ESP8266WiFi.h>          //https://github.com/esp8266/Arduino
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>          // zeroconf protocole
#include "WiFiManager.h"          //https://github.com/tzapu/WiFiManager
#include <RtcDS1307.h>            // lib for RTC (bugs fixed, do no use the lib from repository)
#include <ArduinoOTA.h>           // OTA
 
//#define VERBOSE                 // for display date and time on terminal
//#define miniVERBOSE             // for display service/init messages
 
// define hour for NTP access to resynchronize the RTC (for instance 2:00 am)
#define HEURE_NTP  02
#define MINUTE_NTP 00
 
// define ESP inputs/outputs based on Arduino Pins numbering - 
#define AVAILABLE     A0  // analog input for ADC on WeMos mini D1 board
#define HOURS         5   // D1 on WeMos mini D1 board
#define MINUTES       4   // D2 on WeMos mini D1 board
#define SECONDS0030  13   // D7 on WeMos mini D1 board
#define SECONDS3060  15   // D8 on WeMos mini D1 board
// #define AnteM        16   //  D0: AM/PM led command (thru NPN/PNP switching
 
# define HappyBirthdaySongNumber  76  // happybirthday by Stevie Wonder
 
// const for network
#define HOSTNAME "logre-coucou"
 
char      order ;
String    inString ;
String    numString;
int       valM ;                    // value for meter
int       anaAvailable;
boolean   Afaire        = true;     // flag to sync the RTC
byte      currentTrack  = 0;        // keep track number requested through the webserver (bug)
boolean   LoopIsPlaying  = false;   // SingTime may sing as Loop is not running
 
//rtc timer variables
const unsigned long rtc_time  = 600000;     //timeout for the knob turn in thousandths of a second (600000 = 10 mins)
unsigned long rtc_start_time  = millis();   //used for the count down timer
unsigned long current_time    = millis();
 
 
//ntp stuff
unsigned int    localPort         = 2390;                   // local port to listen for UDP packets
const int       NTP_PACKET_SIZE   = 48;                     // NTP time stamp is in the first 48 bytes of the message  
IPAddress       timeServerIP;                               // time.nist.gov NTP server address  pja
const char*     ntpServerName     = "time.nist.gov";        // domain name for NIST
byte            packetBuffer[ NTP_PACKET_SIZE];             // buffer to hold incoming and outgoing packets
 
WiFiUDP               udp;                                  // A UDP instance to let us send and receive packets over UDP
ESP8266WebServer      server;                               // instance webserver
 
int         VolumeLevel     = 10;                           //  MP3 player intern volume settings
int         VolumeLevelTic  = 8 ;                          // limit tictac level at 10
int         done_once       = 0;
int         RingName;
uint8_t     curSec          = 0;
uint8_t     curHour         = 0;
uint8_t     halfHour        =30;   
int         DST [6][2]      = {26,29,25,28,31,27,29,25,26,31,27,30} ;  // (2017)march, (2017)october, (2018)march, (2018)october....
 
// init DFPlayerMP3
SoftwareSerial mySoftwareSerial(14, 12, false, 256);      // pin 14 = D5 > TX (dfplayer), pin 12= D6 > RX (dfplayer)
DFRobotDFPlayerMini myDFPlayer;
 
// init DS1307 RTC
RtcDS1307 Rtc;
RtcDateTime now;
 
void setRTC();
void setVolume(int volum);
void playTrack(byte num);
void ForceTicking();
bool isAvailable();
 
void printDateTime(const RtcDateTime& dt)
{
  Serial.print(returnDateTime(dt));
}
 
String returnDateTime(const RtcDateTime& dt)
{
  String datestring = "";
  if (dt.Hour() < 10) {
    datestring += "0";
  }
  datestring += dt.Hour();
  datestring += ":";
  if (dt.Minute() < 10) {
    datestring += "0";
  }
  datestring += dt.Minute();
  datestring += ":";
  if (dt.Second() < 10) {
    datestring += "0";
  }
  datestring += dt.Second();
  return datestring;
}
 
void configModeCallback (WiFiManager *myWiFiManager) {
  Serial.println("Entered config mode");
  Serial.println(WiFi.softAPIP());
  //if you used auto generated SSID, print it
  Serial.println(myWiFiManager->getConfigPortalSSID());
}
 
void InitTimeRTC()
{
  RtcDateTime compiled = RtcDateTime(__DATE__, __TIME__);
    #ifdef VERBOSE
      Serial.print("heure de la compilation : ");printDateTime(compiled);Serial.println();
    #endif
 
 if (!Rtc.IsDateTimeValid())
  {
      #ifdef VERBOSE
        Serial.println("RTC lost confidence in the DateTime!");
      #endif
    Rtc.SetDateTime(compiled);
  }
 
  now = Rtc.GetDateTime();
    #ifdef VERBOSE
       Serial.print("Heure de maintenant : ");printDateTime(now);Serial.println();
    #endif
 
  if (now < compiled)
    {
      #ifdef VERBOSE
        Serial.println("RTC is older than compile time!  (Updating DateTime)");
      #endif
    Rtc.SetDateTime(compiled);
    now = Rtc.GetDateTime();
    #ifdef VERBOSE
      Serial.print("heure après set de l\'heure compilée : ");printDateTime(now);Serial.println();
    #endif
    }
    else if (now > compiled)
       {
        #ifdef VERBOSE
          Serial.println("RTC is newer than compile time. (this is expected)");
        #endif
       }
       else if (now == compiled)
            {
            #ifdef VERBOSE
              Serial.println("RTC is the same as compile time! (not expected but all is fine)");
            #endif
            }
 
   setRTC();  
}
 
void MDNSConnect() {  
  if (!MDNS.begin("logre-coucou")) {
    #ifdef VERBOSE
      Serial.println("Error setting up MDNS responder!");
    #endif
    while (1) {
      delay(1000);
    }
  }
  #ifdef VERBOSE
      Serial.println("mDNS responder started");
  #endif
  MDNS.addService("http", "tcp", 80);
}
 
void setup() {
  // init pins for meters
  pinMode(MINUTES,      OUTPUT);  
  pinMode(HOURS,        OUTPUT);  
  pinMode(SECONDS0030,  OUTPUT);  
  pinMode(SECONDS3060,  OUTPUT);
  pinMode(AVAILABLE,    INPUT);
 // pinMode(AnteM,        OUTPUT);
 
  // set meters at zero
  analogWrite(HOURS,0);  
  analogWrite(MINUTES,0);      
  analogWrite(SECONDS0030, 0);         
  analogWrite(SECONDS3060, 1023);  
  //---
  Serial.begin(115200);           // comm with terminal
  delay(500);
  Wire.begin(0,2);                // bus i2c for TinyRTC  Serial.begin@9600,  0 -> SDA, 2 -> SCL
  delay(500);
 
  mySoftwareSerial.begin(9600);   // serial comm with DFPlayer
  delay(500);                     // delay because DFplayer is low to execute
 
  //WiFiManager
  WiFiManager wifiManager;
  wifiManager.setAPCallback(configModeCallback);
  if(!wifiManager.autoConnect(HOSTNAME)) {
      #ifdef VERBOSE
        Serial.println("failed to connect and hit timeout");
      #endif VERBOSE
      ESP.reset();
      delay(1000);
      }
 
  #ifdef miniVERBOSE
    Serial.print("\n\r \n\r Connected !");
  #endif
 
  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    #ifdef miniVERBOSE
      Serial.print(".");
    #endif
  }
  analogWrite(HOURS,(map(1,0,13,0,1020)));   // using H meter to display init phases : step#1, Wifi connected
  #ifdef miniVERBOSE
    Serial.print("script compiled at : ");
    Serial.print(__DATE__);
    Serial.println(__TIME__);
    Serial.println("Starting UDP");
  #endif
  udp.begin(localPort);
  MDNSConnect();
  analogWrite(HOURS,(map(2,0,13,0,1020)));   // step#2, mdns connected
  #ifdef miniVERBOSE
    Serial.print("Local port: ");
    Serial.println(udp.localPort());
    Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)"));
  #endif
 
  #ifdef VERBOSE
    Serial.println(F("GLOGre Clock w ESP - Patrick version 1.0 "));
  #endif
  curSec   = 61;                // dummy value for action in routine
  curHour  = 25;               // dummy value for action in routine
 
  //--------RTC SETUP ------------
  Wire.begin(0, 2);
  InitTimeRTC();
  Rtc.SetSquareWavePin(DS1307SquareWaveOut_1Hz);    // 1HZ square wave
  analogWrite(HOURS,(map(3,0,13,0,1020)));          // step#3, TinyRTC initialized
 
   // Start OTA server.
  ArduinoOTA.setHostname(HOSTNAME); 
  ArduinoOTA.begin(); 
 
  analogWrite(HOURS,(map(4,0,13,0,1020)));          // step#4, OTA started
 
 //--------Start webserver ------------ 
  server.on("/",[](){server.send(200,"text/plain","A que Coucou est vivant! (commandes = vol?v=xx, mp3?t=yy, loop, rand, stop, anniv)");});
  server.on("/vol",[](){setVolume(server.arg("v").toInt());});
  server.on("/mp3",[](){playWebTrack(server.arg("t").toInt());});
  server.on("/loop",LoopFolder);
  server.on("/stop",resetPlayer);
  server.on("/rand",Random);
  server.on("/anniv",PlayHappyBirthday);
  server.begin();
  analogWrite(HOURS,(map(5,0,13,0,1020)));   // step#5, web server started
 
 //  init DFPlayer
  if (!myDFPlayer.begin(mySoftwareSerial)) {  //Use softwareSerial to communicate with mp3.
    delay(500);                     // delay because DFplayer is low to execute
    #ifdef VERBOSE
      Serial.println(F("Unable to begin:"));
      Serial.println(F("1.Please recheck the connection!"));
      Serial.println(F("2.Please insert the SD card!"));
    #endif
    while(true);
  }
  #ifdef VERBOSE
    Serial.println(F("DFPlayer Mini online."));
  #endif
  analogWrite(HOURS,(map(6,0,13,0,1020)));   // step#6, DFPlayer started
  myDFPlayer.volume(5);                     //Set volume value. From 0 to 30
  delay(500);                               // delay because DFplayer is low to execute
}    // end setup
 
void loop() 
{
  // Handle OTA server.
  ArduinoOTA.handle();         // OTA
  yield();   
 
  server.handleClient();       // webserver running
  now = Rtc.GetDateTime();     // get current time
  yield();
  reSyncRTC();
  if(curSec!=now.Second())  // update of meters only once per second
    {        
    DisplayOnMeter();                         // display time on meters
    #ifdef VERBOSE
      Serial.print("isAvailable for Playing (test at each second) : ");Serial.println(isAvailable());
    #endif
    yield();
    SingTime();             // COUCOU will sing each round Hour
    SyncTicking();          // Seconds are Ticking if requested
    curSec=now.Second();          // change flag to sync display only once per Second
    #ifdef VERBOSE
       printDateTime(now);Serial.println();       // display each Second on terminal when verbose
    #endif
    }
}
 
void reSyncRTC()    // sync RTC with NTP once a day at given hour (check #define)
{
  uint8_t Heure = now.Hour();
  uint8_t Minute = now.Minute();
  if ((Heure == HEURE_NTP) && (Minute == MINUTE_NTP) && ( Afaire== true))
    {
    #ifdef VERBOSE
      Serial.println("Mise à l'heure NTP : ");
    #endif
    yield();
    setRTC();
    yield();
    Afaire = false;
    }
    if ((Heure == HEURE_NTP) && (Minute == (MINUTE_NTP+1)) && ( Afaire== false))  // reset of flag
      {
      #ifdef VERBOSE
        Serial.println("Reset du flag Afaire ");
      #endif
      Afaire = true;
      }    
}
 
void playWebTrack(byte track)   // limited to LSB = 255 mp3 tracks
{
  if(currentTrack != track){
    myDFPlayer.playFolder(01, (int)track);
    currentTrack = track;
    }
}
 
void DisplayOnMeter()
{   // specific case to reset meters at their zero (especially for the lower meter)
    if(now.Second()==0) 
      {
      analogWrite(SECONDS0030,0);
      analogWrite(SECONDS3060,0);
      }
    // display Seconds
    if(now.Second()<=30)
      {
      analogWrite(SECONDS0030,map(now.Second(),0,30,0,1023));
      analogWrite(SECONDS3060,0);
      }
    else
      {
      analogWrite(SECONDS3060,map(now.Second()-30,0,30,0,1023));
      analogWrite(SECONDS0030,1023);
      }
      yield();
    // display Minutes
    analogWrite(MINUTES,map(now.Minute(),0,60,0,1023));
    // display HOURS with 0-13 format
    int temp_HOURS =  now.Hour();
    if(temp_HOURS > 12 & temp_HOURS <= 23)
       { 
       //digitalWrite(AnteM,LOW);
       //digitalWrite(PostM,HIGH);
       temp_HOURS = temp_HOURS - 12; 
       } 
    else
       {
        //digitalWrite(AnteM,HIGH); // my choice is to display until 13 for 1PM
        }
    if(temp_HOURS == 0)  // trick for midnight, display 12 instead of 0 !
       {
        temp_HOURS = 12;
        //digitalWrite(AnteM,LOW);
        }
     analogWrite(HOURS,(map(temp_HOURS,0,13,0,1020)+(now.Minute()*78/90)));
}
 
# define tictac             1   //001.mp3
# define chime              2   //002.mp3
# define s_coucou           3   //003a.mp3
# define s_2coucou          4   //003b.mp3
# define s_3coucou          5   //003c.mp3
# define s_4coucou          6   //003d.mp3
# define s_5coucou          7   //003e.mp3
# define s_6coucou          8   //003f.mp3
# define s_7coucou          9   //003g.mp3
# define s_8coucou          10  //003h.mp3
# define s_9coucou          11  //003i.mp3
# define s_10coucou         12  //003j.mp3
# define s_11coucou         13  //003k.mp3
# define Chime30mn          14  //004a.mp3
# define bigbenmidnight     15  //005.mp3
 
void SingTime()             // Cuckoo will sing at each Hour
{
  if(LoopIsPlaying != true)
  {
  switch (now.Hour()) 
  {
    case 0:   // this is midnight and Big Ben bells are ringing 12 strikes
     RingName = bigbenmidnight;
     break;
    case 1:
     RingName = s_coucou;
      break;
    case 2:
     RingName = s_2coucou;
      break;
    case 3:
     RingName = s_3coucou;
      break;
    case 4:
     RingName = s_4coucou;
      break;
    case 5:
     RingName = s_5coucou;
      break;
    case 6:
     RingName = s_6coucou;
     break;
    case 7:
     RingName = s_7coucou;
     break;
    case 8:
     RingName = s_8coucou;
     break;
    case 9:
     RingName = s_9coucou;
     break;
    case 10:
     RingName = s_10coucou;
     break;
    case 11:
     RingName = s_11coucou;
     break;
    case 12:
     RingName = chime;   // Midday, special Westmister chime
     break;
    case 13:
     RingName = s_coucou;
     break;
    case 14:
     RingName = s_2coucou;
     break;
    case 15:
     RingName = s_3coucou;
     break;
    case 16:
     RingName = s_4coucou;
      break;
    case 17:
     RingName = s_5coucou;
     break;
    case 18:
     RingName = s_6coucou;
      break;
    case 19:
     RingName = s_7coucou;
     break;
    case 20:
     RingName = s_8coucou;
     break;
    case 21:
     RingName = s_9coucou;
     break;
    case 22:
     RingName = s_10coucou;
     break;
    case 23:
     RingName = s_11coucou;
     break; 
    default: 
      delay(1); // if nothing else matches, do the default
    }
 if(curHour!= now.Hour())   // in order to ring only once
    { 
    playTrack(RingName);      // start playing RingName 
    curHour=now.Hour();       // flag to sing only once per hour
    #ifdef miniVERBOSE
       printDateTime(now);Serial.print(" sonnerie :");Serial.println(RingName);       // display each Second on terminal when verbose
    #endif
    halfHour=30;              // reinit flag at 30
    }
 if(halfHour== now.Minute())   // in order to ring at half hour
    {   
    playTrack(Chime30mn);      // start playing RingName 
    halfHour=60;               // flag to sing only once per hour at xx:30
    #ifdef miniVERBOSE
       printDateTime(now);Serial.println(" sonnerie @demie heure");    
    #endif
    } 
  } // end of endif
}
 
int whichTrackIsPlayed()
{
  int trackPlayed = myDFPlayer.readCurrentFileNumber(DFPLAYER_DEVICE_SD);
  #ifdef VERBOSE
    Serial.print("Force Ticking track to be played : ");Serial.println(myDFPlayer.readCurrentFileNumber(DFPLAYER_DEVICE_SD));
  #endif
  return trackPlayed;
}
 
void ForceTicking()
{
   yield();
  if ((whichTrackIsPlayed() < 2 ) || (isAvailable()==true)) {     //forceTicking ONLY if tictac, coucou are played or nothing)
      #ifdef VERBOSE
         Serial.print("Force Ticking requested at  : ");printDateTime(now);Serial.println();
      #endif
    playTrack(tictac);                // pulsing the Second is prioritary against other sound
    setVolume(VolumeLevelTic);
 }
}
 
void SyncTicking()
{
   if(now.Second() == 0 && done_once ==0)   // tictac not already started, will start at sec==0
     {
     ForceTicking();                         
     done_once=1;               // boolean to ForceTicking only once per minute
     }
   if(now.Second()>0 && done_once ==1)    // reset boolean, ready for next start ticking when sec==0
     done_once=0; 
}
 
void setVolume(int level)
{
  myDFPlayer.volume(level);      //Set volume value. From 0 to 30
  VolumeLevel = level;
  String msg = "Volume = ";
  msg += (String)level;
  server.send(200,"text/plain",msg);
}
 
void playTrack(byte track)   // limited to LSB = 255 mp3 tracks
{
    myDFPlayer.play((int)track);  //Play mp3,
    delay(200);
}
 
void PlayHappyBirthday()
{
  playTrack(HappyBirthdaySongNumber);     
  server.send(200,"text/plain","Joyeux Anniversaire");
  delay(200);
  setVolume(VolumeLevel);
}
 
void LoopFolder() 
{ 
   myDFPlayer.loopFolder(01); 
   server.send(200,"text/plain","Loop sur repertoire, fin avec 'stop'");
   LoopIsPlaying = true;   // when true, cuckoo and chimes will not cut the music
}
 
void Random() 
{ 
   myDFPlayer.randomAll(); 
   server.send(200,"text/plain","Random all");  // to be checked, does not work
}
 
 
void resetPlayer() 
{ 
   myDFPlayer.stop(); 
   server.send(200,"text/plain","Player reset");
   LoopIsPlaying = false;
   ForceTicking();
}
 
bool isAvailable()
{
  anaAvailable = (unsigned int) analogRead(AVAILABLE);
  if (anaAvailable > 512) return true;
  else return false;
  }
 
String getTime()
{
  if (!Rtc.IsDateTimeValid()){return "RTC lost confidence in the DateTime!";}
  RtcDateTime now = Rtc.GetDateTime();
  return returnDateTime(now);
}
 
void setRTC()
{
  rtc_start_time = current_time;
  WiFi.hostByName(ntpServerName, timeServerIP);    //pja
  sendNTPpacket(timeServerIP);                     // send an NTP packet to a time server (modif pja)
  delay(1000);
 
  int cb = udp.parsePacket();
  if (!cb)
    {
     #  ifdef VERBOSE
       Serial.println("no packet yet");
     #endif
     delay(1);}
  else {
     #ifdef VERBOSE
       Serial.print("packet received, length="); Serial.println(cb);
     #endif
    udp.read(packetBuffer, NTP_PACKET_SIZE);     // read the packet into the buffer
    unsigned long t1, t2, t3, t4;
    t1 = t2 = t3 = t4 = 0;
    for (int i = 0; i < 4; i++)
    {
      t1 = t1 << 8 | packetBuffer[16 + i];
      t2 = t2 << 8 | packetBuffer[24 + i];
      t3 = t3 << 8 | packetBuffer[32 + i];
      t4 = t4 << 8 | packetBuffer[40 + i];
    }
    float f1, f2, f3, f4;
    f1 = ((long)packetBuffer[20] * 256 + packetBuffer[21]) / 65536.0;
    f2 = ((long)packetBuffer[28] * 256 + packetBuffer[29]) / 65536.0;
    f3 = ((long)packetBuffer[36] * 256 + packetBuffer[37]) / 65536.0;
    f4 = ((long)packetBuffer[44] * 256 + packetBuffer[45]) / 65536.0;
    // convert NTP to UNIX time, differs seventy years = 2208988800 seconds, NTP starts Jan 1, 1900, Unix time starts on Jan 1 1970.
#define SECONDS_FROM_1970_TO_2000 946684800
    const unsigned long seventyYears = 2208988800UL + 946684800UL; //library differences, it wants seconds since 2000 not 1970
    t1 -= seventyYears;
    t2 -= seventyYears;
    t3 -= seventyYears;
    t4 -= seventyYears;
    // adjust for DST switching date summer time<> winter time
    t4 += (getDSTFactor() * 3600L);     // *1 during winter time, *2 durng summer time
    t4 += 1;               // adjust the delay(1000) at begin of loop!
    if (f4 > 0.4) t4++;    // adjust fractional part, see above
    #ifdef VERBOSE
      Serial.print("la date et l'heure qui vont etre poussés dans the RTC : "); printDateTime(t4); Serial.println();
    #endif
    Rtc.SetDateTime(t4);
    #ifdef VERBOSE
      Serial.print("RTC after : ");printDateTime(Rtc.GetDateTime()); Serial.println();
    #endif
  }
}
 
unsigned long sendNTPpacket(IPAddress& address)    // send an NTP request to the time server at the given address
{
  #ifdef VERBOSE
    Serial.println("sending NTP packet...");
  #endif
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;
  udp.beginPacket(address, 123); //NTP requests are to port 123
  udp.write(packetBuffer, NTP_PACKET_SIZE);
  udp.endPacket();
}  
 
uint8_t getDaysInMonth( const uint8_t month, const uint16_t year )
{
  if ( month == 2 ) return isLeapYear( year ) ? 29 : 28;
    else if ( month == 4 || month == 6 || month == 9 || month == 11 ) return 30;
  return 31;
}
 
bool isLeapYear( const uint16_t year )
{
  return ( (year % 400 == 0) || (year % 4 == 0 && year % 100 != 0) );
}
 
uint16_t getDaysInYear( const uint16_t year )
{
  return isLeapYear( year ) ? 366 : 365;
}
 
uint16_t getDayOfYear( const uint8_t day, const uint8_t month, const uint16_t year )
{
  uint8_t d = day;
  uint8_t m = month;
  while ( --m ) d += getDaysInMonth( m, year );
  return d;
}
 
uint8_t getDSTFactor()
{
  uint8_t index_year;
  uint8_t DST_factor;
  if(now.Year()>2016)
      index_year = now.Year() - 2017 ;
    else
      index_year = now.Year() - 17 ;
  uint8_t numberOfTheDay  = getDayOfYear(now.Day(),now.Month(),now.Year());
  uint8_t limitLow        = getDayOfYear(DST [index_year][0], 3,now.Year());
  uint8_t limitHigh       = getDayOfYear(DST [index_year][1],10,now.Year());
  if((numberOfTheDay >= limitLow) || (numberOfTheDay < limitHigh)) DST_factor = 2;
    else DST_factor = 1;
  return DST_factor ;
}