EEPROM.h avec une Mega 2560

Bonjour,
j'essaye de configurer une EPPROM sur une Mega 2560 afin de ne pas perdre des donnés critique lors d'un débranchement de la Mega 2560.

j'ai la configuration d'un EPPROM qui a été fait pour une Arduino Nano / UNO :

#include <EEPROM.h>
#define                   PVR_EEPROM_START            550   // Adresse de base des données PVR
#define                   PVR_EEPROM_SIZE              34   // Taille des données PVR dans EEPROM
#define                   PVR_EEPROM_INDEX_ADR       1000   // Adresse de base des donnés
const unsigned long       DATAEEPROM_MAGIC   =  370483670;  // UID de signature de la configuration
const byte                DATAEEPROM_VERSION =          1;  // Version du type de configuration

j'enregistre un total de 33 bytes.

Pourtant, en modifiant les variables qui ont été enregistrés dans l'EEPROM.
Les variable reviennent sur leur valeur d'initialisation lorsque je débranche puis re-branche ma Mega 2560.

Est-je oublié un paramètre important pour la config de l'EEPROM?

Merci d'avance

Le code pour que l'on sache ce que tu fais des définitions que tu nous montres, par exemple.

Bonsoir simonlere1

Mets ton programme en entier, c'est mieux pour investiguer.

Cordialement
jpbbricole

Voilà le code en entier, merci pour votre aide

/*
  EcoPV.ino - Arduino program that maximizes the use of home photovoltaïc production
  by monitoring energy consumption and diverting power to a resistive charge
  when needed.
  Copyright (C) 2019 - Bernard Legrand and Mickaël Lefebvre.
  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU Lesser General Public License as published
  by the Free Software Foundation, either version 2.1 of the License, or
  (at your option) any later version.
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU Lesser General Public License for more details.
  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

// STF 23.04.2020 - Ajout fonction affichage courbes avec arduino IDE

/*************************************************************************************
**                                                                                  **
**        Ce programme fonctionne sur ATMEGA 328P @ VCC = 5 V et clock 16 MHz       **
**        comme l'Arduino Uno et l'Arduino Nano                                     **
**        La compilation s'effectue avec l'IDE Arduino                              **
**        Site Arduino : https://www.arduino.cc                                     **
**                                                                                  **
**************************************************************************************/

// ***********************************************************************************
// ******************            OPTIONS DE COMPILATION                ***************
// ***********************************************************************************

// ***********************************************************************************
// ****************** Affichage et configuration interactive           ***************
// ****************** par terminal liaison série                       ***************
// ****************** Dé-commenter une ligne pour activer une fonction ***************
// ***********************************************************************************

#define PV_STATS             // Active la sortie d'informations statistiques
#define PV_MOD_CONFIG        // Accès interactif à la modification de la configuration

// ***********************************************************************************
// ****************** Dé-commenter pour activer l'affichage            ***************
// ****************** des données sur écran oled 128 x 64  I2C         ***************
// ****************** Voir :                                           ***************
// ***********************************************************************************

//#define OLED_128X64

// *** Note : l'écran utilise la connexion I2C
// *** sur les pins A4 (SDA) et A5 (SCK)
// *** Ces pins ne doivent pas être utilisées comme entrées analogiques
// *** si OLED_128X64 est activé.
// *** La bibliothèque SSD1306Ascii doit être installée dans l'IDE Arduino.
// *** Voir les définitions de configuration OLED_128X64
// *** dans la suite des déclarations, en particulier l'adresse de l'écran

// ***********************************************************************************
// ****************** Dé-commenter pour activer la communication       ***************
// ****************** des données par MYSENSORS                        ***************
// ****************** Voir : www.mysensors.org                         ***************
// ***********************************************************************************

//#define MYSENSORS_COM

// *** Note : MYSENSORS utilise les pins D2 D9 D10 D11 D12 et D13
// *** pour la connexion et l'utilisation de la radio NRF24
// *** Ces pins ne doivent pas être utilisées pour une autre fonction
// *** si MYSENSORS est activé.
// *** La bibliothèque MYSENSORS doit être installée dans l'IDE Arduino.
// *** Voir les définitions de configuration MYSENSORS
// *** dans la suite des déclarations, en particulier l'ID du node

// ***********************************************************************************
// ****************** Dé-commenter pour activer la communication       ***************
// ****************** des données par ETHERNET avec shield ENC28J60    ***************
// ***********************************************************************************

//#define ETHERNET_28J60

// *** Note : la connexion ethernet nécessite un shield ENC28J60 pour Arduino nano
// *** Il utilise les pins  D10 D11 D12 et D13
// *** pour la communication Arduino <> shield
// *** Ces pins ne doivent pas être utilisées pour une autre fonction
// *** si ETHERNET_28J60 est activé.

// *** Les bibliothèques etherShield et ETHER_28J60 doivent installées
// *** dans l'IDE Arduino.
// *** L'installation se fait manuellement.

// *** ADRESSAGE DU LIEN TCP/IP pour les requêtes HTTP :
// *** Voir les définitions de configuration dans la suite des déclarations :
//     adresse mac, adresse IP et port

// *** Structure des requêtes :
 
// *** http://adresseIP:port/GetXX
// *** où XX est compris en 01 et 99
// *** Réponse au format json : {"value":"xxxxx"}
// *** où xxxxx sera une valeur entière
// *** XX = 01 : Vrms (V)
// *** XX = 02 : Irms (A)
// *** XX = 03 : Pact (W)
// *** XX = 04 : Papp (VA)
// *** XX = 05 : Prouted (W)
// *** XX = 06 : Pimp (W)
// *** XX = 07 : Pexp (W)
// *** XX = 08 : cosinus phi * 1 000
// *** XX = 09 : index d'énergie routée (kWh) (estimation)
// *** XX = 10 : index d'énergie importée (kWh)
// *** XX = 11 : index d'énergie exportée (kWh)
// *** XX = 12 : index d'impulsions
// *** XX = 20 : bits d'erreur et de statut (byte)
// *** XX = 21 : temps de fonctionnement ddd:hh:mm:ss
// *** XX = 90 : mise à 0 des 4 index d'énergie (réponse : "ok")
// *** XX = 91 : enregistrement des 4 index d'énergie (réponse : "ok")
// *** XX = 92 : redémarrage du routeur (réponse : "ok")
// *** XX = 93 : formatage EEPROM (réponse : "ok")
// *** XX = 94 : sauvegarde des paramètres en EEPROM (réponse : "ok")
// *** XX = 99 : version logicielle

// *** http://adresseIP:port/ParXX
// *** où XX est compris en 01 et 14
// *** Réponse au format json : {"value":"xxxxx"}
// *** où xxxxx sera une valeur entière
// *** XX = 01 : V_CALIB * 1 000 000
// *** XX = 02 : P_CALIB * 1 000 000
// *** XX = 03 : PHASE_CALIB
// *** XX = 04 : P_OFFSET
// *** XX = 05 : P_RESISTANCE
// *** XX = 06 : P_MARGIN
// *** XX = 07 : GAIN_P
// *** XX = 08 : GAIN_I
// *** XX = 09 : E_RESERVE
// *** XX = 10 : P_DIV2_ACTIVE
// *** XX = 11 : P_DIV2_IDLE
// *** XX = 12 : T_DIV2_ON
// *** XX = 13 : T_DIV2_OFF
// *** XX = 14 : T_DIV2_TC

// *** http://adresseIP:port/SetXX=yyyy.zzz
// *** où XX est compris en 01 et 14
// *** Ecrit la valeur yyyy.zzz pour le paramètre XX
// *** Réponse au format json : {"value":"ok"} si succès de l'opération
// *** XX = 01 : V_CALIB
// *** XX = 02 : P_CALIB
// *** XX = 03 : PHASE_CALIB
// *** XX = 04 : P_OFFSET
// *** XX = 05 : P_RESISTANCE
// *** XX = 06 : P_MARGIN
// *** XX = 07 : GAIN_P
// *** XX = 08 : GAIN_I
// *** XX = 09 : E_RESERVE
// *** XX = 10 : P_DIV2_ACTIVE
// *** XX = 11 : P_DIV2_IDLE
// *** XX = 12 : T_DIV2_ON
// *** XX = 13 : T_DIV2_OFF
// *** XX = 14 : T_DIV2_TC

//                   **************************************************
//                   **********   A  T  T  E  N  T  I  O  N   *********
//                   **************************************************
//                   ***** INCOMPATIBILITE :                  *********
//                   ***** NE PAS ACTIVER MYSENSORS_COM       *********
//                   ***** et/ou ETHERNET_28J60               *********
//                   ***** et/ou OLED_128X64 SIMULTANEMENT    *********
//                   **************************************************

// ***********************************************************************************
// ******************         FIN DES OPTIONS DE COMPILATION           ***************
// ***********************************************************************************

// ***********************************************************************************
// ************************       DEFINITIONS GENERALES        ***********************
// ***********************************************************************************

#define VERSION            "1.2"      // Version logicielle
#define SERIAL_BAUD       500000      // Vitesse de la liaison port série
#define SERIALTIMEOUT      30000      // Timeout pour les interrogations sur liaison série en ms

#define ON                     1
#define OFF                    0
#define POSITIVE            true
#define YES                 true
#define NO                 false

#define NB_CYCLES             50      // nombre de cycles / s du secteur AC (50 ou 60 Hz) : 50 ou 60
#define SAMP_PER_CYCLE       166      // Nombre d'échantillons I,V attendu par cycle : 166
// Dépend du microcontrôleur et de la configuration de l'ADC
#define BIASOFFSET_TOL        20      // Tolérance en bits sur la valeur de biasOffset par rapport
// au point milieu 511 pour déclencher une remontée d'erreur


// ***********************************************************************************
// *******************   Déclarations des pins d'entrée/sortie   *********************
// ***********************************************************************************

// ***   I/O analogiques   ***
#define voltageSensorMUX       A3    // IN ANALOG   = PIN A3, lecture tension V sur ADMUX 3
#define currentSensorMUX       A5    // IN ANALOG   = PIN A0, lecture courant I sur ADMUX 0
// ATTENTION PIN A4 et A5 incompatibles avec activation de OLED_128X64

// ***   I/O digitales     ***
#define pulseExternalPin       2    // IN DIGITAL  = PIN D2, Interrupt sur INT0, signal d'impulsion externe (falling)
#define synchroACPin           3    // IN DIGITAL  = PIN D3, Interrupt sur INT1, signal de détection du passage par zéro (toggle)
#define synchroOutPin          4    // OUT DIGITAL = PIN D4, indique un passage par zéro détecté par l'ADC (toggle)
#define pulseTriacPin          5    // OUT DIGITAL = PIN D5, commande Triac/Solid State Relay
#define ledPinStatus           6    // OUT DIGITAL = PIN D6, LED de signalisation fonctionnement / erreur
#define ledPinRouting          7    // OUT DIGITAL = PIN D7, LED de signalisation routage de puissance
#define relayPin               8    // OUT DIGITAL = PIN D8, commande On/Off du relais de délestage secondaire

//                   **************************************************
//                   **********   A  T  T  E  N  T  I  O  N   *********
//                   **************************************************

// A4 et A5 constituent le port I2C qui est utilisé pour l'affichage écran
// Si OLED_128X64 est activé, ne pas utiliser A4 et A5 comme entrées analogiques ADC pour I ou V

// D0 et D1 sont utilisés par la liaison série de l'Arduino et pour sa programmation
// D2 est l'entrée d'impulsion externe INT0 et doit être absolument affecté à pulseExternalPin
// D3 est l'entrée d'interruption INT1 et doit être absolument affecté à synchroACPin
// D9 sont utilisés pour la radio NRF24 pour communication MYSENSORS (option)
// D10, D11, D12, D13 sont utilisés par MYSENSORS ou la communication ETHERNET

// !! choisir impérativement synchroOutPin et pulseTriacPin parmi D4, D5, D6, D7 (port D) !!

//                   **************************************************
//                   **********          N  O  T  E           *********
//                   **************************************************

// *** Fonction d'autotrigger de la détection du passage par zéro
// *** en reliant physiquement synchroACPin IN et synchroOutPin OUT
// *** PAR DEFAUT : RELIER ELECTRIQUEMENT D3 ET D4


// ***********************************************************************************
// ***************   Définition de macros de manipulation des pins   *****************
// ***************   pour la modification rapide des états           *****************
// ***********************************************************************************

// *** Définitions valables pour le PORTD, pins possibles :
// *** 0 = PIN D0 à 7 = PIN D7
// *** Cela concerne les pins pulseTriacPin et synchroOutPin
// *** Utilisation dans les interruptions pour réduire le temps de traitement

//#define TRIAC_ON               PORTD |=  ( 1 << pulseTriacPin )
//#define TRIAC_OFF              PORTD &= ~( 1 << pulseTriacPin )
//#define SYNC_ON                PORTD |=  ( 1 << synchroOutPin )
//#define SYNC_OFF               PORTD &= ~( 1 << synchroOutPin )
#define SYNC_ON PORTE |= ( 1 << PORTE5 ) //PIN 3 Digital PE5 High
#define SYNC_OFF PORTE &= ~( 1 << PORTE5 ) //PIN 3 Digital PE5 Low I
#define TRIAC_ON PORTE |= ( 1 << PORTE3 ) // 4 ième port E //PIN 5 Digital PE3 High
#define TRIAC_OFF PORTE &= ~( 1 << PORTE3 ) // 4 ième port E //PIN 5 Digital PE3 low


// ***********************************************************************************
// ************************ Declaration des variables globales ***********************
// ***********************************************************************************

// ************* Définition du calibrage du système (valeurs par défaut)
// NOTE : Ces valeurs seront remplacées automatiquement
// par les valeurs lues en EEPROM si celles-ci sont valides

float V_CALIB     =     0.800;        // Valeur de calibration de la tension du secteur lue (Volt par bit)
// 0.823 = valeur par défaut pour Vcc = 5 V
float P_CALIB     =     0.111;        // Valeur de calibration de la puissance (VA par bit)
// Implicitement I_CALIB = P_CALIB / V _CALIB
int   PHASE_CALIB =    13;            // Valeur de correction de la phase (retard) de l'acquisition de la tension
// Entre 0 et 32 :
// 16 = pas de correction
// 0  = application d'un retard = temps de conversion ADC
// 32 = application d'une avance = temps de conversion ADC
int   P_OFFSET     =    0;            // Correction d'offset de la lecture de Pactive en Watt
int   P_RESISTANCE = 1500;            // Valeur en Watt de la résistance contrôlée

// ************* Définition des paramètres de régulation du routeur de puissance (valeurs par défaut)
// NOTE : Ces valeurs seront remplacées automatiquement
// par les valeurs lues en EEPROM si celles-ci sont valides

int  P_MARGIN      =   15;            // Cible de puissance importée en Watt
int  GAIN_P        =   10;            // Gain proportionnel du correcteur
// Permet de gérer les transitoires
int  GAIN_I        =   60;            // Gain intégral du correcteur
byte E_RESERVE     =    5;            // Réserve d'énergie en Joule avant régulation

// ***********************************************************************************************************************
// energyToDelay [ ] = Tableau du retard de déclenchement du SSR/TRIAC en fonction du routage de puissance désiré.
// 256 valeurs codées pour envoyer linéairement 0 (0) à 100 % (255) de puissance vers la résistance.
// Délais par pas de 64 us (= pas de comptage de CNT1) calculés pour une tension secteur 50 Hz.
// Les valeurs de DELAY_MIN (8) et de DELAY_MAX (140) sont fixées :
// Soit pour la fiabilité du déclenchement, soit parce que l'energie routée sera trop petite.
// La pente de la montée de la tension secteur à l'origine est de 72 V / ms, une demi-alternance fait 10 ms.
// Pour DELAY_MIN = 8 par pas de 64 us, soit 512 us, le secteur a atteint 36 V, le triac peut se déclencher correctement.
// On ne déclenche plus au delà de DELAY_MAX = 140 par pas de 64 us, soit 9 ms, pour la fiabilité du déclenchement
// DELAY_MAX doit être inférieur à PULSE_END qui correspond à l'instant où on arrêtera le pulse de déclenchement SSR/TRIAC
// ***********************************************************************************************************************

byte energyToDelay [ ] = {
  144, 143, 141, 140, 139, 137, 136, 135, 134, 133, 132, 131, 130, 129, 128, 128,
  127, 127, 126, 125, 124, 123, 123, 122, 122, 121, 121, 120, 119, 119, 118, 118,
  117, 117, 116, 116, 115, 115, 114, 114, 114, 113, 113, 112, 112, 112, 111, 111,
  110, 110, 109, 109, 109, 108, 108, 108, 107, 107, 107, 106, 106, 106, 105, 105,
  104, 104, 104, 103, 103, 103, 102, 102, 102, 101, 101, 101, 100, 100, 100,  99,
  99,  99,  99,  98,  98,  98,  97,  97,  97,  96,  96,  95,  95,  95,  95,  94,
  94,  94,  93,  93,  93,  93,  92,  92,  91,  91,  91,  91,  90,  90,  90,  89,
  89,  89,  88,  88,  88,  87,  87,  87,  86,  86,  86,  85,  85,  84,  84,  84,
  84,  83,  83,  83,  82,  82,  82,  82,  81,  81,  80,  80,  80,  80,  79,  79,
  79,  78,  78,  78,  77,  77,  77,  76,  76,  76,  75,  75,  74,  74,  74,  73,
  73,  73,  72,  72,  72,  71,  71,  71,  70,  70,  70,  69,  69,  69,  69,  68,
  68,  67,  67,  67,  66,  66,  65,  65,  65,  64,  64,  63,  63,  63,  62,  62,
  62,  61,  61,  61,  60,  60,  60,  59,  59,  58,  58,  58,  57,  57,  56,  56,
  56,  55,  55,  54,  54,  53,  53,  52,  52,  52,  51,  51,  50,  50,  50,  49,
  49,  48,  48,  47,  46,  45,  44,  44,  43,  43,  42,  42,  41,  41,  40,  39,
  39,  38,  37,  36,  35,  33,  32,  31,  27,  25,  20,  15,  12,  10,   9,   8
};

#define PULSE_END              148    // Instant d'arrêt du pulse triac après le passage à 0
// par pas de 64 us (9.5 ms = 148)


// ************* Définition des paramètres de délestage secondaire de puissance (DIV2)
// ************* Fonctionnement pour un relais en tout ou rien
// NOTE : Ces valeurs seront remplacées automatiquement
// par les valeurs lues en EEPROM si celles-ci sont valides

int  P_DIV2_ACTIVE       =    1000;        // Valeur de puissance routée en Watt qui déclenche le relais de délestage
int  P_DIV2_IDLE         =       0;        // Puissance active importée en Watt qui désactive le relais de délestage
byte T_DIV2_ON           =       5;        // Durée minimale d'activation du délestage en minutes
byte T_DIV2_OFF          =       5;        // Durée minimale d'arrêt du délestage en minutes
byte T_DIV2_TC           =       1;        // Constante de temps de moyennage des puissance routées et active
// NOTE : Il faut une condition d'hystérésis pour un bon fonctionnement :
// P_DIV2_ACTIVE + P_DIV2_IDLE > à la puissance de la charge de délestage secondaire

// ************* Définition du type d'affichage
// >>> STF 23.04.2020 - Ajout des infos supplémentaires dans l'eeprom
byte STF_TRACEUR   = 0;       // 0 : affichage standard jetblack , 1 : Series de données pour affichage sur traceur arduino
// <<< STF 23.04.2020


// ************* Variables globales pour le fonctionnement du régulateur
// ************* Ces variables permettent de communiquer les informations entre les routines d'interruption

volatile int     biasOffset    = 511;       // pour réguler le point milieu des acquisitions ADC,
// attendu autour de 511
volatile long    periodP       =   0;       // Samples de puissance accumulés
// sur un demi-cycle secteur (période de la puissance)
#define          NCSTART           5
volatile byte    coldStart     =   NCSTART; // Indicateur de passage à 0 après le démarrage
// Atteint la valeur 0 une fois le warm-up terminé
// Attente de NCSTART passage à 0 avant de démarrer la régulation

// ************* Variables globales utilisées pour les calcul des statistiques de fonctionnement
// ************* Ces variables permettent de communiquer les informations entre les routines d'interruption

volatile long          sumP           = 0;
volatile unsigned long PVRClock       = 0;  // horloge interne par pas de 20 ms
volatile unsigned long sumV           = 0;
volatile unsigned long sumI           = 0;
volatile unsigned long sumVsqr        = 0;
volatile unsigned long sumIsqr        = 0;
volatile unsigned int  routed_power   = 0;
volatile unsigned int  samples        = 0;
volatile byte          error_status   = 0;
// Signification des bits du byte error_status
// bits 0..3 : informations
// bit 0 (1)   : Routage en cours
// bit 1 (2)   : Routage à 100 %
// bit 2 (4)   : Relais secondaire de délestage activé
// bit 3 (8)   : Exportation de puissance
// bits 4..7 : erreurs
// bit 4 (16)  : Anomalie signaux analogiques : ADC I/V overflow, biasOffset
// bit 5 (32)  : Anomalie taux d'acquisition
// bit 6 (64)  : Anomalie furtive Détection passage à 0 (bruit sur le signal)
// bit 7 (128) : Anomalie majeure Détection passage à 0 (sur 2 secondes de comptage)


// ************* Variables utilisées pour le transfert des statistiques de fonctionnement
// ************* des routines d'interruption vers le traitement dans la LOOP tous les NB_CYCLES

volatile long          stats_sumP         = 0;   // Somme des échantillons de puissance
volatile unsigned long stats_sumVsqr      = 0;   // Somme des échantillons de tension au carré
volatile unsigned long stats_sumIsqr      = 0;   // Somme des échantillons de courant au carré
volatile long          stats_sumV         = 0;   // Somme des échantillons de tension
volatile long          stats_sumI         = 0;   // Somme des échantillons de courant
volatile unsigned int  stats_routed_power = 0;   // Evaluation de la puissance routée vers la charge
volatile unsigned int  stats_samples      = 0;   // Nombre d'échantillons total
volatile byte          stats_error_status = 0;
volatile int           stats_biasOffset   = 0;   // Valeur de la correction d'offset de lecture ADC
volatile byte          stats_ready_flag   = 0;
// 0 = Données traitées, en attente de nouvelles données
// 1 = Nouvelles données disponibles
// 9 = Données statistiques en cours de transfert

// ************* Variables globales utilisées pour les statistiques de fonctionnement et d'information
// ************* Ces variables n'ont pas d'utilité directe pour la régulation du PV routeur
// ************* Elles ne sont pas utilisées dans les interruptions

float                  VCC_1BIT         = 0.0049;   // valeur du bit de l'ADC sous Vcc = 5 V
float                  indexKWhRouted   = 0;        // compteur d'énergie
float                  indexKWhImported = 0;        // compteur d'énergie
float                  indexKWhExported = 0;        // compteur d'énergie
volatile long          indexImpulsion   = 0;        // compteur d'impulsions externes
// modifié par interruption

float                  Prouted          = 0;        // puissance routée en Watts
float                  Vrms             = 0;        // tension rms en V
float                  Irms             = 0;        // courant rms en A
float                  Papp             = 0;        // puissance apparente en VA
float                  Pact             = 0;        // puissance active en Watts
float                  cos_phi          = 0;        // cosinus phi

byte                   secondsOnline    = 0;        // horloge interne
byte                   minutesOnline    = 0;        // basée sur la période secteur
byte                   hoursOnline      = 0;        // et mise à jour à chaque fois que les données
byte                   daysOnline       = 0;        // statistiques sont disponibles

byte                   ledBlink         = 0;        // séquenceur de clignotement pour les LEDs, période T
// bit 0 (1)   : T = 40 ms
// bit 1 (2)   : T = 80 ms
// bit 2 (4)   : T = 160 ms
// bit 3 (8)   : T = 320 ms
// bit 4 (16)  : T = 640 ms
// bit 5 (32)  : T = 1280 ms
// bit 6 (64)  : T = 2560 ms
// bit 7 (128) : T = 5120 ms

// ***********************************************************************************
// ********  Définitions pour l'accès à la configuration stockée en EEPROM  **********
// ***********************************************************************************

#include <EEPROM.h>
#define                   PVR_EEPROM_START            550   // Adresse de base des données PVR
#define                   PVR_EEPROM_SIZE              34   // Taille des données PVR dans EEPROM
#define                   PVR_EEPROM_INDEX_ADR       1000   // Adresse de base des compteurs de kWh
const unsigned long       DATAEEPROM_MAGIC   =  370483670;  // UID de signature de la configuration
const byte                DATAEEPROM_VERSION =          1;  // Version du type de configuration

//                   **************************************************
//                   **********   A  T  T  E  N  T  I  O  N   *********
//                   **************************************************

//  *** MYSENSORS utilise la partie basse des adresses de l'EEPROM pour son fonctionnement
//  *** C'est pourquoi les adresses EEPROM sont > à 500

struct dataEeprom {                         // Structure des données pour le stockage en EEPROM
  unsigned long         magic;              // Magic Number
  byte                  struct_version;     // Structure version
  float                 v_calib;
  float                 p_calib;
  int                   phase_calib;
  int                   p_offset;
  unsigned int          p_resistance;
  int                   p_margin;
  int                   gain_p;
  int                   gain_i;
  byte                  e_reserve;
  int                   p_div2_active;
  int                   p_div2_idle;
  byte                  t_div2_on;
  byte                  t_div2_off;
  byte                  t_div2_tc;
  // fin des données eeprom V1
  // taille totale : 33 bytes (byte = 1 byte, int = 2 bytes, long = float = 4 bytes)

  // >>> STF 23.04.2020 - Ajout des infos supplémentaires dans l'eeprom
  byte                  stf_traceur;
  // avec cette modification la taille totale de l'eeprom passe a 33+1 = 34 bytes
  // <<< STF 23.04.2020
  
};

// ***********************************************************************************
// ********* Définitions pour la modification de la configuration en EEPROM **********
// ***********************************************************************************

#define NB_PARAM            15          // Nombre de paramètres dans la configuration, 14 en EEPROM V1

struct paramInConfig {                  // Structure pour la manipulation des données de configuration
  byte dataType;                        // 0 : int, 1 : float, 4 : byte
  int  minValue;                        // valeur minimale que peut prendre le paramètre (-16383)
  int  maxValue;                        // valeur maximale que peut prendre le paramètre (16384)
  bool advancedParameter;               // signale un paramètre de configuration avancée
  void *adr;                            // pointeur vers le paramètre
};

const paramInConfig pvrParamConfig [ ] = {
  // Tableau utilisé pour la manipulation des données de configuration
  // dataType  min      max  advanced   adr
  { 1,        0,       5,   false,  &V_CALIB },        // V_CALIB
  { 1,        0,      25,   false,  &P_CALIB },        // P_CALIB
  { 0,      -16,      48,   true,   &PHASE_CALIB },    // PHASE_CALIB
  { 0,     -100,     100,   true,   &P_OFFSET },       // P_OFFSET
  { 0,      100,   10000,   false,  &P_RESISTANCE },   // P_RESISTANCE
  { 0,     -200,    2000,   false,  &P_MARGIN },       // P_MARGIN
  { 0,        0,    1000,   true,   &GAIN_P },         // GAIN_P
  { 0,        0,    1000,   true,   &GAIN_I },         // GAIN_I
  { 4,        0,     100,   true,   &E_RESERVE },      // E_RESERVE
  { 0,        0,    9999,   false,  &P_DIV2_ACTIVE },  // P_DIV2_ACTIVE
  { 0,        0,    9999,   false,  &P_DIV2_IDLE },    // P_DIV2_IDLE
  { 4,        0,     240,   false,  &T_DIV2_ON },      // T_DIV2_ON
  { 4,        0,     240,   false,  &T_DIV2_OFF },     // T_DIV2_OFF
  { 4,        0,      60,   false,  &T_DIV2_TC }       // T_DIV2_TC
  
  // >>> STF 23.04.2020
  , { 4,        0,       1,  false,  &STF_TRACEUR }    // STF_TRACEUR
  // <<< STF 23.04.2020
};

const char string_0 []   PROGMEM = "Calibrage de la tension\t\t\t";                 // V_CALIB
const char string_1 []   PROGMEM = "Calibrage de la puissance\t\t\t";               // P_CALIB
const char string_2 []   PROGMEM = "Calibrage de la phase\t\t\t";                   // PHASE_CALIB
const char string_3 []   PROGMEM = "Décalage de puissance active (W)\t\t";          // P_OFFSET
const char string_4 []   PROGMEM = "Résistance commandée (W)\t\t\t";                // P_RESISTANCE
const char string_5 []   PROGMEM = "Consigne de régulation (W)\t\t\t";              // P_MARGIN
const char string_6 []   PROGMEM = "Gain proportionnel\t\t\t\t";                    // GAIN_P
const char string_7 []   PROGMEM = "Gain intégral\t\t\t\t";                         // GAIN_I
const char string_8 []   PROGMEM = "Tolérance (J)\t\t\t\t";                         // E_RESERVE
const char string_9 []   PROGMEM = "Excédent de production pour relais ON (W)\t";   // P_DIV2_ACTIVE
const char string_10 []  PROGMEM = "Importation mini pour relais OFF (W)\t";        // P_DIV2_IDLE
const char string_11 []  PROGMEM = "Relais : durée mini ON (min)\t\t";              // T_DIV2_ON
const char string_12 []  PROGMEM = "Relais : durée mini OFF (min)\t\t";             // T_DIV2_OFF
const char string_13 []  PROGMEM = "Relais : constante de lissage (min)\t\t";       // T_DIV2_TC
// >>> STF 23.04.2020
const char string_14 []  PROGMEM = "Traceur : 0=OFF 1=ON\t\t\t";                    // STF_TRACEUR
// <<< STF 23.04.2020

const char *const pvrParamName [ ] PROGMEM = {
  string_0, string_1, string_2, string_3, string_4,
  string_5, string_6, string_7, string_8, string_9,
  string_10, string_11, string_12, string_13, string_14
};


// ***********************************************************************************
// ****************** Définitions pour la communication OLED_128X64    ***************
// ***********************************************************************************

// *** OLED_128X64 utilise les pins A4 et A5
// *** pour la connexion I2C de l'écran

#if defined (OLED_128X64)

#include "SSD1306Ascii.h"
#include "SSD1306AsciiAvrI2c.h"

#define I2C_ADDRESS 0x3C                    // adresse I2C de l'écran oled
#define OLED_128X64_REFRESH_PERIOD  6       // période de raffraichissement des données à l'écran

SSD1306AsciiAvrI2c oled;

#endif

// ***********************************************************************************
// ****************** Définitions pour la communication MYSENSORS      ***************
// ****************** Voir : www.mysensors.org                         ***************
// ***********************************************************************************

// *** MYSENSORS utilise les pins D2 D9 D10 D11 D12 et D13
// *** pour la connexion et l'utilisation de la radio NRF24

#if defined (MYSENSORS_COM)

//#define MY_DEBUG                          // Debug Mysensors sur port série
#define MY_NODE_ID                 30       // Adresse du noeud de capteurs MYSENSORS
#define MY_RADIO_RF24                       // Type de module radio
#define MY_BAUD_RATE               SERIAL_BAUD
#define MY_TRANSPORT_WAIT_READY_MS 15000

#include <MySensors.h>

// Définition de 2 capteurs dans le noeud
#define CHILD_ID_POWER             0        // capteur 0 = Power meter
#define CHILD_ID_MULTIMETER        1        // capteur 1 = Multimètre

#define MYSENSORS_TRANSMIT_PERIOD 20        // Période de transmission des données en secondes
// valeurs possibles pour une transmission régulière :
// 1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60

MyMessage msg_pwr       ( CHILD_ID_POWER, V_WATT );           // puissance active
MyMessage msg_pva       ( CHILD_ID_POWER, V_VA );             // puissance apparente
MyMessage msg_cosphi    ( CHILD_ID_POWER, V_POWER_FACTOR );   // cosinus phi
MyMessage msg_kwh       ( CHILD_ID_POWER, V_KWH );            // énergie routée
MyMessage msg_pimp      ( CHILD_ID_POWER, V_VAR1 );           // puissance importée
MyMessage msg_pexp      ( CHILD_ID_POWER, V_VAR2 );           // puissance exportée
MyMessage msg_prt       ( CHILD_ID_POWER, V_VAR3 );           // puissance routée
MyMessage msg_error     ( CHILD_ID_POWER, V_VAR4 );           // byte d'erreur / statut
MyMessage msg_vrms      ( CHILD_ID_MULTIMETER, V_VOLTAGE );   // tension
MyMessage msg_irms      ( CHILD_ID_MULTIMETER, V_CURRENT );   // courant

#endif


// ***********************************************************************************
// ****************** Définitions pour la communication ETHERNET       ***************
// ****************** Nécessite un shield ENC28J60                     ***************
// ***********************************************************************************

// *** Note : La connexion ethernet utilise les pins D10 D11 D12 et D13
// *** pour la communication Arduino <-> shield ENC28J60

#if defined (ETHERNET_28J60)

#include "etherShield.h"
#include "ETHER_28J60.h"
// *** Les bibliothèques etherShield et ETHER_28J60 fournies
// *** doivent installées dans l'IDE Arduino.
// *** L'installation se fait manuellement.

byte ethMac [6] = { 0x10, 0x12, 0x16, 0xB0, 0x63, 0x24 };
// La mac address doit être unique sur le réseau local : 10:12:16:B0:63:24
byte ethIp [4] = { 192, 168, 1, 250 };
// Adresse IP correspondant à une adresse libre sur le réseau local : 192.168.1.250
unsigned int ethPort = 80;
// Port IP pour l'accès aux requêtes HTTP : 80

ETHER_28J60 ethernet;

#endif

// ***********************************************************************************
// ****************** Fonction traceur serie arduino IDE               ***************
// ****************** STF 23.04.2020                                   ***************
// ***********************************************************************************
//>>> STF 23.04.2020
#define NB_CPTPERIODES   5                        // Nbr de periode entre chaque affichage (ex: 5 x 20 ms = 100 ms )

volatile byte          RealPower_flag   = 0;      // 0 : ras | 1 : Données pour le traceur série prêtes
volatile long          RealPower        = 0;      // Puissance active pour le traceur série
volatile unsigned int  FdCtrlCmd        = 0;      // Puissance de routage calculée
//<<< STF 23.04.2020

// ***********************************************************************************
// ************************   FIN DES DEFINITIONS GENERALES    ***********************
// ***********************************************************************************


// ***********************************************************************************
// *******************   DEFINITIONS DES FONCTIONS ET PROCEDURES   *******************
// ***********************************************************************************

/////////////////////////////////////////////////////////
// presentation                                        //
// Routine d'initialisation  MYSENSORS                 //
// Voir : www.mysensors.org                            //
/////////////////////////////////////////////////////////

#if defined (MYSENSORS_COM)

void presentation ( ) {

  // Envoi des informations de version
  sendSketchInfo ( "EcoPV", VERSION );
  // Enregistrement des capteurs du noeud de capteur
  present ( CHILD_ID_POWER, S_POWER );
  present ( CHILD_ID_MULTIMETER, S_MULTIMETER );
}

#endif



/////////////////////////////////////////////////////////
// setup                                               //
// Routine d'initialisation générale                   //
/////////////////////////////////////////////////////////

void setup ( ) {

  // Initialisation des pins
  pinMode      ( pulseExternalPin,  INPUT_PULLUP  );   // Entrée de comptage impulsion externe
  pinMode      ( synchroACPin,  INPUT  );   // Entrée de synchronisation secteur
  pinMode      ( pulseTriacPin, OUTPUT );   // Commande Triac
  pinMode      ( synchroOutPin, OUTPUT );   // Détection passage par zéro émis par l'ADC
  pinMode      ( ledPinStatus,  OUTPUT );   // LED Statut
  pinMode      ( ledPinRouting, OUTPUT );   // LED Routage puissance
  pinMode      ( relayPin,      OUTPUT );   // Commande relais de délestage tout ou rien

  digitalWrite ( pulseTriacPin, OFF    );
  digitalWrite ( synchroOutPin, OFF    );
  digitalWrite ( ledPinStatus,  ON     );
  digitalWrite ( ledPinRouting, ON     );
  digitalWrite ( relayPin,      OFF    );

  // Le délai suivant de 1500 ms est important lors du reboot après programmation
  // pour éviter des problèmes d'écriture en EEPROM
  // + Clignotement des leds (power on)
  delay ( 500 );
  digitalWrite ( ledPinStatus,  OFF    );
  digitalWrite ( ledPinRouting, OFF    );
  delay ( 500 );
  digitalWrite ( ledPinStatus,  ON     );
  digitalWrite ( ledPinRouting, ON     );
  delay ( 500 );
  digitalWrite ( ledPinStatus,  OFF    );
  digitalWrite ( ledPinRouting, OFF    );
  // Fin du délai de 1500 ms

  // Activation de l'écran oled si défini
#if defined (OLED_128X64)
  oled.begin( &Adafruit128x64, I2C_ADDRESS );
  oled.setFont ( System5x7 );
  oled.clear ( );
  oled.set2X ( );
  oled.print ( F("EcoPV V") );
  oled.println ( F(VERSION) );
  oled.println ( );
  oled.println ( F("Starting...") );
#endif

  // Activation de la connexion ethernet si définie
#if defined (ETHERNET_28J60)
  ethernet.setup ( ethMac, ethIp, ethPort );
#endif

  // Chargement de la configuration EEPROM si existante
  // Sinon écriture des valeurs par défaut en EEPROM
  if ( eeConfigRead ( ) == false ) eeConfigWrite ( );

  // Chargement des l'index d'énergie kWh
  indexRead ( );

  // Lecture de la tension d'alimentation de l'arduino
  analogReadReference ( );                       // Première lecture à vide
  VCC_1BIT = ( 1.1 / analogReadReference ( ) );  // Calcul de la valeur en V d'un bit ADC

  // Activation de la sortie sur liaison série & affichage du message de démarrage
  Serial.begin (SERIAL_BAUD);
  while ( !Serial ) { };
  Serial.setTimeout ( SERIALTIMEOUT );

  if ( STF_TRACEUR == 0 ) {  //STF 23.04.2020
     clearScreen ( );
     // Affichage de la version
     versionPrint ( );

     // Affichage des options de compilation activées
     optionPrint ( );
  }

  // Affichage de la configuration si mode PV_STATS seul
#if ( defined (PV_STATS) ) && ( !defined (PV_MOD_CONFIG) )
  if ( STF_TRACEUR == 0 ) {  //STF 23.04.2020
     configPrint ( );
     Serial.println ( );
  }
#endif

  // Accès à la modification de la configuration si autorisé
#if defined (PV_MOD_CONFIG)
  Serial.print ( F("Set-up-->Entrée") );
  for ( int i = 0; i <= 4; i++ ) {
    delay ( 800 );
    Serial.print ( F(".") );
  }
  Serial.println ( );
  if ( Serial.available ( ) > 0 ) configuration ( );
  clearSerialInputCache ( );
#endif

  // Séquence de démarrage du PV routeur
  if ( STF_TRACEUR == 0 ) Serial.println ( F("\nInitialisation...") );
  startPVR ( );
  if (( coldStart == 0 ) && ( STF_TRACEUR == 0 )) Serial.println ( F("\nEcoPV actif !\n") );

  // Si le graphe est activé, on affiche le nom des axes
  // STF 24.04.2020   - les infos pour les axes doivent être ajoutées ici
  if ( STF_TRACEUR == 1 ) Serial.println ( F("\nP_active(W) SSR_delay/10(ms) P_routée(W)") );
  
#if defined (OLED_128X64)
  oled.clear ( );
  oled.set2X ( );
  oled.print ( F("EcoPV V") );
  oled.println ( F(VERSION) );
  oled.println ( );
  oled.println ( F("Running !") );
#endif

}


///////////////////////////////////////////////////////////////////
// loop                                                          //
// Loop routine exécutée en boucle                               //
///////////////////////////////////////////////////////////////////

void loop ( ) {

  static unsigned long refTime        = millis ( );
  static float         routedEnergy   = 0;
  static float         exportedEnergy = 0;
  static float         importedEnergy = 0;

  static float         inv_NB_CYCLES  = 1 / float ( NB_CYCLES );
  static float         inv_255        = 1 / float ( 255 );
  float                inv_stats_samples = 1 / float ( int ( SAMP_PER_CYCLE ) * int ( NB_CYCLES ) );

  float                Filter_param = 1 / float ( 1 + ( int ( T_DIV2_TC ) * 60 ) );
  static float         Pact_filtered = 0;
  static float         Prouted_filtered = 0;
  static unsigned int  Div2_On_cnt = 0;
  static unsigned int  Div2_Off_cnt = 0;

  unsigned int         OCR1A_tmp;
  byte                 OCR1A_byte;
  static byte          OCR1A_min = 255;
  static byte          OCR1A_max = 0;
  static unsigned long OCR1A_avg = 0;
  static unsigned long OCR1A_cnt = 0;

  long indexImpulsionTemp = 0;
    
  // STF 23.04.2020
  long                 tmp_RealPower;
  unsigned int         tmp_FdCtrlCmd;
  static float         RealPower_cal = ( P_CALIB / float ( int ( SAMP_PER_CYCLE ) * int ( NB_CPTPERIODES ) ) );

  // *** Vérification perte longue de synchronisation secteur
  if ( ( millis ( ) - refTime ) > 2010 ) {
    // Absence de remontée d'informations depuis plus de 2 secondes = absence de synchronisation secteur
    refTime = millis ( );
    noInterrupts ( );
    error_status |= B10000000;            // On reporte l'erreur majeure au système de régulation
    stats_error_status |= error_status;   // On transfère tous les bits de statut et d'erreur
    interrupts ( );
  }

  // *** Statistiques du délai de déclenchement du TRIAC / SSR
  // Note : information approximative basée sur la lecture du registre OCR1A
  noInterrupts ( );
  OCR1A_tmp = OCR1A;
  interrupts ( );
  if ( OCR1A_tmp < 256 ) {
    OCR1A_byte = byte ( OCR1A_tmp );
    OCR1A_avg += OCR1A_byte;
    OCR1A_cnt++;
    if ( OCR1A_byte > OCR1A_max ) OCR1A_max = OCR1A_byte;
    if ( OCR1A_byte < OCR1A_min ) OCR1A_min = OCR1A_byte;
  }
  else OCR1A_byte = 156;

  // *** Traitement des informations statistiques lorsqu'elles sont disponibles
  // *** tous les NB_CYCLES sur flag, soit toutes les secondes pour NB_CYCLES = 50 @ 50 Hz
  if ( stats_ready_flag == 1 ) {          // Les données statistiques sont disponibles après NB_CYCLES
    // Si le flag est différent de 0, elles ne seront pas modifiées
    refTime = millis ( );                 // Mise à jour du temps de passage dans la boucle

    // *** Vérification du routage                                        ***
    if ( stats_routed_power > 0 )
      stats_error_status |= B00000001;
    else
      stats_error_status &= B11111110;

    // *** Vérification du routage pleine puissance                       ***
    if ( ( stats_routed_power / ( 2 * NB_CYCLES ) ) >= 254 )
      // Note : on teste la pleine puissance si on atteint 254 alors que le max possible est 255.
      // Ceci pour plus de fiabilité de la détection de la pleine puissance routée.
      stats_error_status |= B00000010;
    else
      stats_error_status &= B11111101;

    // *** Vérification du nombre de samples à +/- 2 %                    ***
    if ( ( stats_samples >= int ( NB_CYCLES + 1 ) * SAMP_PER_CYCLE )
         || ( stats_samples <= int ( NB_CYCLES - 1 ) * SAMP_PER_CYCLE ) )
      stats_error_status |= B00100000;

    // *** Calcul des valeurs statistiques en unités réelles              ***
    inv_stats_samples = 1 / float ( stats_samples );
    Vrms    = V_CALIB * sqrt ( stats_sumVsqr * inv_stats_samples );
    Papp    = P_CALIB * sqrt ( ( stats_sumVsqr * inv_stats_samples )
                               * ( stats_sumIsqr * inv_stats_samples ) );
    Pact    = - ( P_CALIB * stats_sumP * inv_stats_samples + P_OFFSET );
    // le signe - sur le calcul de Pact permet d'avoir Pact < 0 en exportation
    Prouted = float ( P_RESISTANCE ) * float ( stats_routed_power )
              * 0.5 * inv_NB_CYCLES * inv_255;
    Irms    = Papp / Vrms;
    cos_phi = Pact / Papp;

    // *** Vérification de l'exportation d'énergie                        ***
    if ( Prouted < 0 )
      stats_error_status |= B00001000;
    else
      stats_error_status &= B11110111;

    // *** Calcul des valeurs filtrées de Pact et Prouted                 ***
    // *** Usage : déclenchement du relais secondaire de délestage        ***
    Filter_param = 1 / float ( 1 + ( int ( T_DIV2_TC ) * 60 ) );
    Pact_filtered = Pact_filtered + Filter_param * ( Pact - Pact_filtered );
    Prouted_filtered = Prouted_filtered + Filter_param * ( Prouted - Prouted_filtered );

    // *** Déclenchement et gestion du relais secondaire de délestage     ***
    if ( ( Prouted_filtered >= float ( P_DIV2_ACTIVE ) ) && ( Div2_Off_cnt == 0 )
         && ( digitalRead ( relayPin ) == OFF ) ) {
      digitalWrite ( relayPin, ON );    // Activation du relais de délestage
      Div2_On_cnt = 60 * T_DIV2_ON;     // Initialisation de la durée de fonctionnement minimale en sevondes
    }
    else if ( Div2_On_cnt > 0 ) {
      Div2_On_cnt --;                     // décrément d'une seconde
    }
    else if ( ( Pact_filtered >= float ( P_DIV2_IDLE ) ) && ( digitalRead ( relayPin ) == ON ) ) {
      digitalWrite ( relayPin, OFF );     // Arrêt du délestage
      Div2_Off_cnt = 60 * T_DIV2_OFF;     // Initialisation de la durée d'arrêt minimale en secondes
    }
    else if ( Div2_Off_cnt > 0 ) {
      Div2_Off_cnt --;                    // décrément d'une seconde
    }

    // *** Vérification de l'état du relais de délestage                  ***
    if ( digitalRead ( relayPin ) == ON )
      stats_error_status |= B00000100;
    else
      stats_error_status &= B11111011;

    // *** Accumulation des énergies routée, importée, exportée           ***
    // *** Les calculs sont faits toutes les secondes                     ***
    // *** La puissance x 1s = énergie en Joule                           ***
    routedEnergy += Prouted;
    if ( Pact < 0 )     exportedEnergy -= Pact;
    else                importedEnergy += Pact;
    if ( routedEnergy >= 3600 ) {     // On a accumulé 3600 J = 1 Wh)
      routedEnergy -= 3600;           // On suppose qu'on ne peut pas router plus de 3600 Watts
      indexKWhRouted += 0.001;
    }
    if ( exportedEnergy >= 3600 ) {   // On a accumulé 3600 J = 1 Wh)
      exportedEnergy -= 3600;         // On suppose qu'on ne peut pas exporter plus de 3600 Watts
      indexKWhExported += 0.001;
    }
    if ( importedEnergy >= 18000 ) {   // On a accumulé 18000 J = 5 Wh)
      importedEnergy -= 18000;         // On suppose qu'on ne peut pas importer plus de 18000 Watts
      indexKWhImported += 0.005;
    }

    // *** Mise à jour du temps de fonctionnement UpTime                  ***
    upTime ( );

    // *** Appel du scheduler                                             ***
    // *** Gestion des actions régulières et tâches planifiées            ***
    PVRScheduler ( );

    // *** Calcul des statistiques du déclenchement du TRIAC              ***
    OCR1A_avg /= OCR1A_cnt;

    // *** Affichage des donnéees statistiques si mode PV_STATS           ***
#if defined (PV_STATS)
  if ( STF_TRACEUR == 0 ) {  //STF 23.04.2020
    clearScreen ( );
    Serial.println ( F("\n***\t\tStatistiques\t\t***\n") );
    Serial.print ( F("Up time\t: ") );
    Serial.print ( daysOnline );
    Serial.print ( F(" jours ") );
    Serial.print ( hoursOnline );
    Serial.print ( F(" h ") );
    Serial.print ( minutesOnline );
    Serial.print ( F(" min ") );
    Serial.print ( secondsOnline );
    Serial.println ( F(" s") );
    Serial.print ( F("Vrms\t: ") );
    Serial.print ( Vrms, 1 );
    Serial.println ( F(" V") );
    Serial.print ( F("Irms\t: ") );
    Serial.print ( Irms, 3 );
    Serial.println ( F(" A") );
    Serial.print ( F("Cos phi\t: ") );
    Serial.println ( cos_phi, 3 );
    Serial.print ( F("Sin phi\t: ") );
    Serial.println ( sqrt ( ( 1 - cos_phi * cos_phi ) ), 3 );
    Serial.print ( F("Pappar\t: ") );
    Serial.print ( Papp, 0 );
    Serial.println ( F(" VA") );
    Serial.print ( F("Pactive\t: ") );
    Serial.print ( Pact, 1 );
    Serial.print ( F(" W - Valeur filtrée : ") );
    Serial.print ( Pact_filtered , 0 );
    Serial.print ( F(" W ") );
    if ( Pact < 0 )
      Serial.println ( F(" (export)") );
    else
      Serial.println ( F(" (import)") );
    Serial.print ( F("Proutée\t: ") );
    Serial.print ( Prouted , 0 );
    Serial.print ( F(" W - Valeur filtrée : ") );
    Serial.print ( Prouted_filtered , 0 );
    Serial.println ( F(" W") );
    Serial.print ( F("Index 1\t: ") );
    Serial.print ( indexKWhRouted, 3 );
    Serial.println ( F(" kWh routés") );
    Serial.print ( F("Index 2\t: ") );
    Serial.print ( indexKWhExported, 3 );
    Serial.println ( F(" kWh exportés") );
    Serial.print ( F("Index 3\t: ") );
    Serial.print ( indexKWhImported, 3 );
    Serial.println ( F(" kWh importés") );
    noInterrupts ( );
    indexImpulsionTemp = indexImpulsion;
    interrupts ( );
    Serial.print ( F("Index 4\t: ") );
    Serial.print ( indexImpulsionTemp );
    Serial.println ( F(" impulsions") );
    Serial.print ( F("Vbias\t: ") );
    Serial.print ( float ( VCC_1BIT * stats_biasOffset ), 3 );
    Serial.println ( F(" V") );
    Serial.print ( F("Nb éch.\t: ") );
    Serial.println ( stats_samples );
    Serial.print ( F("SSR Dly\t: ") );
    if ( OCR1A_min < 255 ) {
      Serial.print ( float ( OCR1A_min * 0.064 ), 2 );
      Serial.print ( F(" / ") );
      Serial.print ( float ( OCR1A_avg * 0.064 ), 2 );
      Serial.print ( F(" / ") );
      Serial.print ( float ( OCR1A_max * 0.064 ), 2 );
      Serial.println ( F(" (min / avg / max [ms])") );
    }
    else {
      Serial.println ( F("N/A") );
    }
    Serial.print ( F("Status\t: ") );
    Serial.println ( stats_error_status, BIN );
    if ( digitalRead ( relayPin ) == ON ) Serial.println ( F("Relais ON") );
    else Serial.println ( F("Relais OF") );
    Serial.println ( );
  } //STF 23.04.2020
#endif

    // *** Initialisation des statistique OCR1A                           ***
    OCR1A_avg = 0;
    OCR1A_max = 0;
    OCR1A_min = 255;
    OCR1A_cnt = 0;

    // *** Reset du Flag pour indiquer que les données ont été traitées   ***
    stats_ready_flag = 0;

    // *** Affichage de l'invite de configuration si mode PV_MOD_CONFIG   ***
#if defined (PV_MOD_CONFIG)
    if ( STF_TRACEUR == 0 ) {  //STF 23.04.2020
      Serial.println ( F("Configuration --> Entrée") );
    } //STF 23.04.2020
    // Appel au menu de configuration
    if ( Serial.available ( ) > 0 ) {
      delay ( 200 );
      configuration ( );
    }
    clearSerialInputCache ( );
    refTime = millis ( );
#endif

  }
  // *** Fin du Traitement des informations statistiques                  ***

  // *** La suite est exécutée à chaque passage dans loop                 ***
  
  //>>> STF 23.04.2020 -
  if ( STF_TRACEUR == 1 ) {
    if ( RealPower_flag == 1 ) {
      noInterrupts ( );
      tmp_RealPower = RealPower;
      tmp_FdCtrlCmd = FdCtrlCmd;
      interrupts ( );

      Serial.print ( F("") );
      Serial.print ( - ( tmp_RealPower * RealPower_cal + P_OFFSET ) );

      Serial.print ( F(",") ) ;
      Serial.print ( OCR1A_byte * 6.4 );

      Serial.print ( F(",") );
      Serial.print ( float ( P_RESISTANCE ) * float ( tmp_FdCtrlCmd ) * inv_255 );

      // Fin de chaine CRLF
      Serial.println ( F("") );
      // *** Reset du Flag pour indiquer que les données ont été traitées   ***
      RealPower_flag = 0;
    }
  }
  //<<< STF 23.04.2020

  // *** Mise à jour de l'état des LEDs de signalisation                  ***
  PVRLed ( );

  // *** Traitement des requêtes HTTP ETHERNET                            ***
#if defined (ETHERNET_28J60)
  if ( ethernetProcess ( ) ) refTime = millis ( );
#endif

  // *** Traitement de la perte longue de synchronisation, erreur majeure ***
  if ( stats_error_status >= B10000000 ) {
#if defined (MYSENSORS_COM)
    mySensorsTransmit ( );
#endif
    fatalError ( );
    refTime = millis ( );
  };
}


///////////////////////////////////////////////////////////////////////
//////////////////// ROUTINES d'INTERRUPTIONS /////////////////////////
///////////////////////////////////////////////////////////////////////

// Quand une interruption est appelée, les autres sont désactivées
// mais gardées en mémoire.
// Elles seront appelées à la fin de l'exécution de l'interruption en cours
// dans un ordre de priorité : INT0, INT1, COUNT1, ADC.


///////////////////////////////////////////////////////////////////////
// pulseExternalInterrupt                                            //
// Interrupt service routine de comptage de pulse externe INT0       //
///////////////////////////////////////////////////////////////////////

void pulseExternalInterrupt ( void ) {

#define               PULSE_MIN_INTERVAL  80
  // Intervalle en ms à respecte entre 2 impulsions valides
  // Traitement de l'antirebond
  static unsigned long  refTime = 0;

  if ( ( millis ( ) - refTime ) > PULSE_MIN_INTERVAL ) {
    indexImpulsion ++;
    refTime = millis ( );
  }
}

///////////////////////////////////////////////////////////////////////
// zeroCrossingInterrupt                                             //
// Interrupt service routine de passage à zéro du secteur            //
// Temps mesuré de traitement de l'interruption : entre 36 et 40 us  //
///////////////////////////////////////////////////////////////////////

void zeroCrossingInterrupt ( void ) {

#define ERROR_BIT_SHIFT        6        // Valeur de décimation des données pour le calcul de la régulation PI
#define COMMAND_BIT_SHIFT     14        // Valeur de décimation pour la commande de puissance

  static bool           periodParity   = POSITIVE;
  static unsigned long  last_time;
  static byte           numberOfCycle  = 0;
  static long           P_SETPOINT     = long ( float ( ( P_MARGIN + P_OFFSET ) )
                                         * float ( SAMP_PER_CYCLE ) * ( 0.5 / P_CALIB ) );
  // Setpoint du régulateur : Valeur de P_MARGIN transféré dans le système d'unité du régulateur
  // Prise en compte de l'offset de mesure P_OFFSET sur la puissance active
  static long           controlError        = P_SETPOINT >> ERROR_BIT_SHIFT;
  static long           lastControlError    = P_SETPOINT >> ERROR_BIT_SHIFT;
  static long           controlIntegral     = 0;
  static long           controlIntegralMinValue = - ( long ( ( long ( NB_CYCLES ) * long ( SAMP_PER_CYCLE ) * long ( E_RESERVE ) )
      / P_CALIB ) >> ERROR_BIT_SHIFT );
  static long           controlCommand      = 0;
  unsigned long         present_time;

//>>> STF 23.04.2020
  static byte          cptperiodes          = 0;      // Compteur de periode secteur
  static long          sumP1                = 0;
//<<< STF 23.04.2020


  present_time = micros ( );

  if ( coldStart > 0 ) {
    // Phase de Warm-up : initialisation jusque coldStart = 0;
    coldStart --;                               // on décrémente coldStart

    TCCR1B           = 0x00;
    TRIAC_OFF;
    TCNT1            = 0x00;
    OCR1A            = 30000;                   // on charge à un délai inatteignable

    periodParity     = POSITIVE;                // signe arbitraire de l'alternance
    periodP          = 0;
    P_SETPOINT       = long ( float ( ( P_MARGIN + P_OFFSET ) )
                              * float ( SAMP_PER_CYCLE ) * ( 0.5 / P_CALIB ) );
    controlError     = P_SETPOINT >> ERROR_BIT_SHIFT;
    lastControlError = P_SETPOINT >> ERROR_BIT_SHIFT;
    controlIntegral  = 0;
    controlIntegralMinValue = - ( long ( ( long ( NB_CYCLES ) * long ( SAMP_PER_CYCLE ) * long ( E_RESERVE ) )
                                         / P_CALIB ) >> ERROR_BIT_SHIFT );
    controlCommand   = 0;
    numberOfCycle    = 0;
    last_time        = present_time;

    // Initialisation pour les statistiques

    samples          = 0;
    sumVsqr          = 0;
    sumIsqr          = 0;
    sumP             = 0;
    routed_power     = 0;
    stats_ready_flag = 0;
    error_status     = 0;
    
    //STF 23.04.2020
    sumP1 = 0;
    cptperiodes = 0;
  }

  else if ( ( present_time - last_time ) > 8000 ) {
    // *** PASSAGE PAR ZERO - ON A TERMINE UNE DEMI PERIODE ***
    // gestion de l'antirebond du passage à 0 en calculant de temps
    // entre 2 passages à 0 qui doivent être séparés de 8 ms au moins

    TCCR1B = 0x00;                        // arrêt du Timer par sécurité
    TRIAC_OFF;
    TCNT1  = 0x00;                        // on remet le compteur à 0 par sécurité
    TCCR1B = 0x05;                        // on démarre le Timer par pas de 64 us
    OCR1A  = 30000;                       // on charge à un délai inatteignable en attendant les calculs
    // ATTENTION, le Timer1 commence à compter ici !!

    // *** Calculs de fin de période (demi-cycle secteur)
    // *** Convention : si injection sur le réseau (PV excédentaire), periodP est positif

    controlError = ( periodP + P_SETPOINT ) >> ERROR_BIT_SHIFT;
    // calcul de l'erreur du régulateur
    // signe + lié à la définition de P_SETPOINT
    // P_SETPOINT prend en compte la correction de l'offset P_OFFSET de lecture de Pact
    // réduction de résolution de ERROR_BIT_SHIFT bits
    // = division par 2 puissance ERROR_BIT_SHIFT
    // pour éviter de dépasser la capacité des long dans les calculs
    // et donner de la finesse au réglage du gain

    controlIntegral += controlError;
    // Note : l'erreur ne sera intégrée que si on est en régime linéaire de régulation
    // pour éviter le problème d'integral windup et de débordement de controlCommand
    // Le régime linéaire de régulation est observé sur la dernière commande SSR controlCommand
    // Voir le traitement fait plus bas

    // on calcule la commande à appliquer (correction PI)
    // note : lissage de l'erreur sur 2 périodes pour l'action proportionnelle pour corriger le bruit systématique
    controlCommand = long ( GAIN_I ) * controlIntegral + long ( GAIN_P ) * ( controlError + lastControlError );

    // application du gain fixe de normalisation : réduction de COMMAND_BIT_SHIFT bits
    controlCommand = controlCommand >> COMMAND_BIT_SHIFT;

    if ( controlCommand <= 0 ) {  // équilibre ou importation, donc pas de routage de puissance
      controlCommand = 0;
      TCCR1B = 0;                 // arrêt du Timer = inhibition du déclenchement du triac pour cette période
      TCNT1  = 0;                 // compteur à 0
      if ( controlIntegral <= controlIntegralMinValue ) {   // fonction anti integral windup
        controlIntegral = controlIntegralMinValue;
      }
    }

    else {  // controlCommand est strictement positif
      if ( controlCommand > 255 ) {   // Saturation de la commande en pleine puissance
        controlCommand = 255;         // Pleine puissance
        controlIntegral -= controlError;     // gel de l'accumulation de l'intégrale, fonction anti integral windup

      }
      // *** Régime linéaire de régulation : initialisation du comparateur de CNT1
      // *** pour le déclenchement du SSR/TRIAC géré par interruptions Timer1
      OCR1A = energyToDelay [ byte ( controlCommand ) ];
    }

    // Calcul pour les statistiques
    routed_power += controlCommand;
    sumP += periodP;

    // >>> STF 23.04.2020
    if ( STF_TRACEUR == 1 ) {
      sumP1 += periodP;
      FdCtrlCmd = controlCommand;
    }
    // <<< STF 23.04.2020

    // Initialisation pour la période suivante
    periodP = 0;
    lastControlError = controlError;

    // changement de parité de la période (pour le demi-cycle suivant)
    periodParity = !periodParity;

    // opérations réalisées toutes les 2 périodes (à chaque cycle secteur)
    if ( periodParity ) {   // La demi-période suivante est positive

      // incrément du nombre de cycles
      numberOfCycle ++;

      // Opérations réalisées tous les NB_CYCLES cycles soit toutes les secondes pour NB_CYCLES = 50
      if ( numberOfCycle == NB_CYCLES ) {

        numberOfCycle = 0;

        if ( stats_ready_flag == 0 ) {        // La LOOP a traité les données précédentes
          // Transfert des données statistiques pour utilisation par la LOOP (Partie 1)
          stats_routed_power = routed_power;  // Evaluation de la puissance routée vers la charge
          routed_power = 0;
          stats_biasOffset = biasOffset;      // Dernière valeur de la correction d'offset de lecture ADC
          stats_error_status &= B00001111;    // RAZ des bits d'ERREUR 4..7
          stats_error_status |= error_status; // Transfert des bits d'ERREUR 4..7 uniquement
          error_status = 0;                   // Les bits signalant les erreurs sont remis à 0
          // à chaque traitement statistique

          stats_ready_flag = 9;               // Flag pour le prochain appel de l'interruption ADC
          // qui poursuivra le transfert des statistiques (Partie 2)

        }
        else {                                // La LOOP n'a pas (encore) traité les données précédentes :
          // Les données courantes sont perdues.
          routed_power = 0;
          error_status = 0;
          sumP = 0;
          sumVsqr = 0;
          sumIsqr = 0;
          sumV    = 0;
          sumI    = 0;
          samples = 0;
        }
      }
    }

    else {   // La demi-période suivante est négative
      // Incrément de l'horloge interne de temps de fonctionnement par pas de 20 ms
      PVRClock ++;
      // incrément du séquenceur pour le clignotement des leds
      ledBlink ++;

      // >>> STF 23.04.2020   a chaque 1/2 periode negative on met a jour pour affichage graphe arduino IDE
      if ( STF_TRACEUR == 1 ) {
        cptperiodes++;
        if ( cptperiodes == NB_CPTPERIODES ) {
          cptperiodes = 0;
          RealPower = sumP1;
          sumP1 = 0;
          RealPower_flag = 1;  // On affiche les valeurs lues dans cette boucle pour tracer sur graphe arduino IDE
        }
      }
      // <<< STF 23.04.2020

      // Détection des erreurs de biasOffset
      if ( abs ( biasOffset - 511 ) >= BIASOFFSET_TOL ) {
        error_status |= B00010000;
      }
    }

    last_time = present_time;
  }
  else error_status |= B01000000;  // Fausse détection d'un passage à 0, on signale l'évènement
}

//////////////////////////////////////////////////////////////////////
// ISR ( ADC_vect )                                                 //
// Interrupt service routine pour la conversion ADC de I et V       //
// Temps mesuré de traitement de l'interruption : 20 à 24 us        //
//////////////////////////////////////////////////////////////////////

ISR ( ADC_vect ) {

  // Note : pour des raisons de retard de lecture analogique I et V, on convertit d'abord I, puis V
  // La charge de calcul est répartie entre les 2 phases de conversion pour optimiser les temps d'interruption

  // caractéristique du filtrage de détermination de biasOffset
#define FILTERSHIFT           15  // constante de temps de 4s
#define FILTERROUNDING        0b100000000000000

  static byte   readFlagADC = 0;
  // readFlagADC = 0 pour la conversion du courant
  // readFlagADC = 9 pour la conversion de la tension
  // Variable locale. Ne peut pas prendre d'autres valeurs que 0 ou 9
  static long   fBiasOffset = ( 511L << FILTERSHIFT );
  // pré-chargement du filtre passe-bas du biasOffset au point milieu de l'ADC
  static int    lastSampleVcorr = 0;
  static int    lastSampleIcorr = 0;
  int           analogVoltage;
  int           analogCurrent;
  int           sampleVcorr;
  int           sampleVcorrDelayed;
  int           sampleIcorr;
  static int    sampleIcorrDelayed;
  long          sampleP;

  // conversion du courant disponible
  if ( readFlagADC == 0 ) {

    // Configuration pour l'acquisition de la tension
    ADMUX &= B11110000;
    ADMUX |= voltageSensorMUX;
    // Démarrage de la conversion
    ADCSRA |= B01000000;
    //Flag pour indiquer une conversion de tension
    readFlagADC = 9;

    // Si fin d'un cycle secteur, transfert de la suite des données statistiques
    if ( stats_ready_flag == 9 ) {
      stats_sumP = sumP;                  // Somme des échantillons de puissance
      sumP = 0;
      stats_sumVsqr = sumVsqr;            // Somme des échantillons de tension au carré
      sumVsqr = 0;
      stats_sumIsqr = sumIsqr;            // Somme des échantillons de courant au carré
      sumIsqr = 0;
      stats_sumV = sumV;                  // Somme des échantillons de tension
      sumV = 0;
      stats_sumI = sumI;                  // Somme des échantillons de courant
      sumI = 0;
      stats_samples = samples;            // Nombre d'échantillons total
      samples = 0;
      // Flag : toutes les données statistiques ont été transférées
      stats_ready_flag = 1;
    }

    analogCurrent = ADCL | ( ADCH << 8 );

    // Calculs après la conversion du courant
    sampleIcorr = analogCurrent - biasOffset;
    // Echantillon de courant sur lequel on applique un délai
    // pour corriger la phase en fonction d'une estimation linéaire d'évolution
    sampleIcorrDelayed = int ( ( 16 - PHASE_CALIB ) * ( sampleIcorr - lastSampleIcorr ) + ( sampleIcorr << 4  ) ) >> 4;
    // Calcul pour les statistiques
    sumIsqr += long ( sampleIcorr ) * long ( sampleIcorr );
    sumI += long ( sampleIcorr );

    // Calcul pour la mise à jour de biasOffset
    biasOffset = int ( ( fBiasOffset + FILTERROUNDING ) >> FILTERSHIFT );

    // détection des erreurs
    if ( ( analogCurrent == 0 ) || ( analogCurrent == 1023 ) ) error_status |= B00010000;

    lastSampleIcorr = sampleIcorr;
  }

  // Sinon conversion de tension disponible
  else if ( readFlagADC == 9 ) {

    //Configuration pour l'acquisition du courant
    ADMUX &= B11110000;
    ADMUX |= currentSensorMUX;
    //Démarrage de la conversion du courant
    ADCSRA |= B01000000;
    //Flag pour indiquer une conversion de courant
    readFlagADC = 0;

    // Si fin d'un cycle secteur, transfert de la suite des données statistiques
    if ( stats_ready_flag == 9 ) {
      stats_sumP = sumP;                  // Somme des échantillons de puissance
      sumP = 0;
      stats_sumVsqr = sumVsqr;            // Somme des échantillons de tension au carré
      sumVsqr = 0;
      stats_sumIsqr = sumIsqr;            // Somme des échantillons de courant au carré
      sumIsqr = 0;
      stats_sumV = sumV;                  // Somme des échantillons de tension
      sumV = 0;
      stats_sumI = sumI;                  // Somme des échantillons de courant
      sumI = 0;
      stats_samples = samples;            // Nombre d'échantillons total
      samples = 0;
      // Flag : toutes les données statistiques ont été transférées
      stats_ready_flag = 1;
    }

    analogVoltage = ADCL | ( ADCH << 8 );

    // Calculs après la conversion de tension
    sampleVcorr = analogVoltage - biasOffset;
    // Calcul de l'échantillon de tension sur lequel on applique un délai
    // pour corriger la phase en fonction d'une estimation linéaire d'évolution
    sampleVcorrDelayed = int ( ( PHASE_CALIB - 16 ) * ( sampleVcorr - lastSampleVcorr ) + ( sampleVcorr << 4 ) ) >> 4;
    // somme des échantillons de puissance pour le calcul de la puissance active
    sampleP = long ( sampleVcorrDelayed ) * long ( sampleIcorrDelayed );
    periodP += sampleP;

    // détection du passage à 0 et génération du signal de synchronisation (changement d'état)
    if ( sampleVcorr >= 0 )  SYNC_ON;         //digitalWrite ( synchroOutPin, ON  );
    else                     SYNC_OFF;        //digitalWrite ( synchroOutPin, OFF );

    // détection des erreurs
    if ( ( analogVoltage == 0 ) || ( analogVoltage == 1023 ) ) error_status |= B00010000;

    // Calcul pour les statistiques
    sumVsqr += long ( sampleVcorr ) * long ( sampleVcorr );
    sumV += long ( sampleVcorr );

    // Calcul pour la mise à jour de biasOffset
    fBiasOffset += sampleVcorr;

    lastSampleVcorr = sampleVcorr;
    samples ++;
  }
}

//////////////////////////////////////////////////////////////////////////////////////
// ISR ( TIMER1_COMPA_vect )  et  ISR ( TIMER1_OVF_vect )                           //
// Interrupt service routine Timer1 pour générer le pulse de déclenchement du TRIAC //
//////////////////////////////////////////////////////////////////////////////////////

ISR ( TIMER1_COMPA_vect ) {   // TCNT1 = OCR1A : instant de déclenchement du SSR/TRIAC
  TRIAC_ON;
  // chargement du compteur pour que le pulse SSR/TRIAC s'arrête à l'instant PULSE_END
  // relativement au passage à 0 (nécessite PULSE_END > OCR1A)
  TCNT1 = 65535 - ( PULSE_END - OCR1A );
}

ISR ( TIMER1_OVF_vect ) {     // TCNT1 overflow, instant PULSE_END
  TRIAC_OFF;
  TCCR1B = 0x00;              // arrêt du Timer
  TCNT1 = 0;                  // on remet le compteur à 0 par sécurité
}

///////////////////////////////////////////////////////////////////////
///////////// FIN DES ROUTINES d'INTERRUPTIONS ////////////////////////
///////////////////////////////////////////////////////////////////////


///////////////////////////////////////////////////////////////////////////////////////
/////////////////////////// DEFINITION DES FONCTIONS //////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////


//////////////////////////////////////////////////////////////////////////////////////
// configADC                                                                        //
// Fonction de configuration du convertisseur ADC                                   //
//////////////////////////////////////////////////////////////////////////////////////

void configADC ( void ) {

  // Configuration : voir documention ATMEGA328P
  // Fonctionnement sur interruption et choix de la référence
  ADMUX &= B11011111;
  ADMUX &= B00111111;
  ADMUX |= B01000000;
  ADCSRA &= B00000000;
  ADCSRA |= B10000000;
  ADCSRA |= B00001000;
  ADCSRA |= B00000110; // Prescaler to 64 (==> 166 échantillons par cycle)

  // désactivaton des I/O digitales branchées sur les ports ADC utilisés
  DIDR0 |= ( B00000001 << voltageSensorMUX );
  DIDR0 |= ( B00000001 << currentSensorMUX );

  // Sélection du port analogique correspondant au courant
  ADMUX &= B11110000;
  ADMUX |= currentSensorMUX;

  // La conversion démarrera en mettant à 1 le bit ADSC de ADCSRA
  // après avoir configuré ADMUX pour le choix de l'entrée analogique
  // Pour démarrer : ADCSRA |= B01000000;
}

//////////////////////////////////////////////////////////////////////////////////////
// configTimer1                                                                     //
// Fonction de configuration du Timer 1, gestion du pulse SSR/TRIAC                 //
//////////////////////////////////////////////////////////////////////////////////////

void configTimer1 ( void ) {

  TIMSK1 = 0x03;     // activation des interruptions sur comparateur et overflow
  TCCR1A = 0x00;     // fonctionnement normal,
  TCCR1B = 0x00;     // timer arrêté
  OCR1A  = 30000;    // comparateur initialisé à 30000
  TCNT1  = 0;        // compteur initialisé à 0

  /*******************  Pour information : *****************************
    //TCCR1B=0x05; // démarrage du compteur par pas de 64 us
    //TCCR1B=0x00; // arrêt du compteur
  ********************************************************************/
}


//////////////////////////////////////////////////////////////////////////////////////
// startPVR                                                                         //
// Fonction de démarrage                                                            //
// Premier démarrage ou re-démarrage                                                //
//////////////////////////////////////////////////////////////////////////////////////

void startPVR ( void ) {

  delay ( 200 );
  noInterrupts ( );
  // Configuration du convertisseur ADC pour travailler sur interruptions
  configADC ( );
  // Configuration du Timer1
  configTimer1 ( );
  // Configuration de l'entrée d'interruption synchroACPin
  attachInterrupt ( digitalPinToInterrupt ( synchroACPin ), zeroCrossingInterrupt, CHANGE );
  // Configuration de l'entrée d'interruption pulseExternalPin
  attachInterrupt ( digitalPinToInterrupt ( pulseExternalPin ), pulseExternalInterrupt, FALLING );
  // Initialisations pour le premier cycle
  coldStart = NCSTART;
  stats_error_status = 0;
  stats_ready_flag = 0;
  interrupts ( );
  delay ( 200 );
  // Démarrage de la première conversion ADC = démarrage du routeur
  ADCSRA |= B01000000;

  // *** Si période de warm-up, affichage des ..... pendant 100 ms
  // *** avant de redonner la main au programme
  unsigned long refTime = millis ( );
  while ( ( coldStart > 0 ) && ( ( millis ( ) - refTime ) < 100 ) ) {
    delay ( 1 );
    Serial.print ( F(".") );
  };
}


//////////////////////////////////////////////////////////////////////////////////////
// stopPVR                                                                          //
// Fonction d'arrêt                                                                 //
//////////////////////////////////////////////////////////////////////////////////////

void stopPVR ( void ) {

  // arrêt ADC et arrêt interruption ADC
  ADCSRA = 0x00;
  // arrêt interruption détection passage par zéro
  detachInterrupt ( digitalPinToInterrupt ( synchroACPin ) );
  // arrêt interruption comptage des impulsions externes
  detachInterrupt ( digitalPinToInterrupt ( pulseExternalPin ) );
  // arrêt du Timer 1
  TCCR1B = 0x00;
  // arrêt du SSR
  digitalWrite ( pulseTriacPin, OFF );
  // arrêt du relais secondaire de délestage
  digitalWrite ( relayPin, OFF );
}

//////////////////////////////////////////////////////////////////////////////////////
// configPrint                                                                      //
// Fonction d'affichage de la configuration courante                                //
//////////////////////////////////////////////////////////////////////////////////////

void configPrint ( void ) {

  int i = 0;
  char buffer [50];
  clearScreen ( );

  Serial.println ( F("***\t\tConfiguration\t\t***\n") );

  while ( i < NB_PARAM ) {
    printTab ( );
    Serial.print ( ( i + 1 ) );
    Serial.print ( F(". ") );
    strcpy_P ( buffer, (char *)pgm_read_word(&(pvrParamName[i])) );
    Serial.print ( buffer );
    printTab ( );
    switch ( pvrParamConfig [i].dataType ) {
      case 0: {
          int *tmp_int = (int *) pvrParamConfig [i].adr;
          Serial.println ( *tmp_int );
          break;
        }
      case 1: {
          float *tmp_float = (float *) pvrParamConfig [i].adr;
          Serial.println ( *tmp_float, 6 );
          break;
        }
      case 4: {
          byte *tmp_byte = (byte *) pvrParamConfig [i].adr;
          Serial.println ( *tmp_byte );
          break;
        }
    }
    i++;
  }
}

//////////////////////////////////////////////////////////////////////////////////////
// configChange                                                                     //
// Fonction de modification de la configuration courante                            //
//////////////////////////////////////////////////////////////////////////////////////

void configChange ( void ) {

  long  valueInt = 0;
  float valueFloat = 0;
  int   minValue;
  int   maxValue;

  configPrint ( );

  char  buffer [50];

  clearSerialInputCache ( );
  Serial.println ( F("\n\
\tParamètre ? (0 pour sortir)\t") );

  int choice = Serial.parseInt ( );

  // Cas général
  if ( ( choice > 0) && ( choice <= NB_PARAM ) ) {
    int index = choice - 1;
    if ( pvrParamConfig [index].advancedParameter )
      Serial.println ( F("\tATTENTION, PARAMETRE AVANCE !") );
    strcpy_P ( buffer, (char *)pgm_read_word ( &(pvrParamName[index]) ) );
    Serial.print ( F("  Valeur courante de ") );
    Serial.print ( buffer );
    Serial.print ( F("= ") );
    byte dataType = pvrParamConfig [index].dataType;
    switch ( dataType ) {
      case 0: {
          int *tmp_int = (int *) pvrParamConfig [index].adr;
          Serial.print ( *tmp_int );
          break;
        }
      case 1: {
          float *tmp_float = (float *) pvrParamConfig [index].adr;
          Serial.print ( *tmp_float, 6 );
          break;
        }
      case 4: {
          byte *tmp_byte = (byte *) pvrParamConfig [index].adr;
          Serial.print ( *tmp_byte );
          break;
        }
    }
    Serial.print ( F("  |  min = ") );
    minValue = pvrParamConfig [index].minValue;
    Serial.print ( minValue );
    Serial.print ( F("  |  max = ") );
    maxValue = pvrParamConfig [index].maxValue;
    Serial.println ( maxValue );

    clearSerialInputCache ( );
    Serial.print ( F("  Valeur ? ") );

    if ( dataType == 1 ) {
      valueFloat = Serial.parseFloat ( );
      valueFloat = constrain ( valueFloat, float ( minValue ), float ( maxValue ) );
    }
    else {
      valueInt = Serial.parseInt ( );
      valueInt = constrain ( valueInt, minValue, maxValue );
    }

    Serial.print ( F("  Nouvelle valeur de ") );
    Serial.print ( buffer );
    Serial.print ( F("= ") );

    switch ( dataType ) {
      case 0: {
          int *tmp_int = (int *) pvrParamConfig [index].adr;
          noInterrupts ( );
          *tmp_int = int ( valueInt );
          interrupts ( );
          Serial.println ( *tmp_int );
          break;
        }
      case 1: {
          float *tmp_float = (float *) pvrParamConfig [index].adr;
          noInterrupts ( );
          *tmp_float = float ( valueFloat );
          interrupts ( );
          Serial.println ( *tmp_float, 6 );
          break;
        }
      case 4: {
          byte *tmp_byte = (byte *) pvrParamConfig [index].adr;
          noInterrupts ( );
          *tmp_byte = byte ( valueInt );
          interrupts ( );
          Serial.println ( *tmp_byte );
          break;
        }
    }
    clearSerialInputCache ( );
    Serial.println ( F("\nEnregistrez les modifications en EEPROM !") );
  }
}


//////////////////////////////////////////////////////////////////////////////////////
// configuration                                                                    //
// Fonction de menu de configuration du PV routeur                                  //
//////////////////////////////////////////////////////////////////////////////////////

void configuration ( void ) {

#define MENU0 "\
    ************************************\n\
    *****    EcoPV set-up menu     *****\n\
    ************************************\n\n"
#define MENU1 "\t0.\tQuitter\n\
\t1.\tVersion\n\n"
#define MENU2 "\t11.\tConfiguration courante\n\
\t12.\tCharger la configuration\n\
\t13.\tSauvegarder la configuration\n\
\t14.\tModifier la configuration\n\n"
#define MENU3 "\t21.\tAfficher les index\n\
\t22.\tSauvegarder les index\n\
\t23.\tMettre à zéro des index\n\
\t24.\tModifier des index\n\n"
/*#define MENU4 "\t81.\tDump EEPROM\n\*/
#define MENU4 "\t82.\tFormatage EEPROM\n\n\
\t99.\tRedémarrage\n\n\
Choix (+ entrée) ? \t"

  while ( true ) {
    clearScreen ( );
    clearSerialInputCache ( );
    Serial.print ( F(MENU0) );
    Serial.print ( F(MENU1) );
    Serial.print ( F(MENU2) );
    Serial.print ( F(MENU3) );
    Serial.print ( F(MENU4) );
    int choice = Serial.parseInt ( );
    Serial.println ( );

    switch ( choice ) {
      case 0: {
          // Serial.println ( F("  >>>> Démarrage du PV routeur <<<<") );
          clearSerialInputCache ( );
          return;
          break;
        }
      case 1: {
          clearScreen ( );
          versionPrint ( );
          optionPrint ( );
          break;
        }
      case 11: {
          clearScreen ( );
          configPrint ( );
          break;
        }
      case 12: {
          clearScreen ( );
          if ( eeConfigRead ( ) )
            Serial.println ( F("  >>>> Configuration chargée ! <<<<") );
          else
            Serial.println ( F("  >>>> EEPROM vierge ! <<<<\n>>>> Configuration par défaut <<<<") );
          break;
        }
      case 13: {
          clearScreen ( );
          eeConfigWrite ( );
          Serial.println ( F("  >>>> Configuration sauvegardée ! <<<<") );
          break;
        }
      case 14: {
          clearScreen ( );
          configChange ( );
          break;
        }
      case 21: {
          long indexImpulsionTemp = 0;
          noInterrupts ( );
          indexImpulsionTemp = indexImpulsion;
          interrupts ( );
          clearScreen ( );
          Serial.println ( F("  >>>> Valeur des index <<<<") );
          Serial.print ( F("  Index d'énergie ") );
          Serial.print ( F("routée   : ") );
          Serial.print ( indexKWhRouted, 3 );
          Serial.println ( F(" kWh") );
          Serial.print ( F("  Index d'énergie ") );
          Serial.print ( F("exportée : ") );
          Serial.print ( indexKWhExported, 3 );
          Serial.println ( F(" kWh") );
          Serial.print ( F("  Index d'énergie ") );
          Serial.print ( F("importée : ") );
          Serial.print ( indexKWhImported, 3 );
          Serial.println ( F(" kWh") );
          Serial.print ( F("  Index d'impulsions : ") );
          Serial.print ( indexImpulsionTemp );
          Serial.println ( F(" impulsions") );
          break;
        }
      case 22: {
          clearScreen ( );
          indexWrite ( );
          Serial.println ( F("  >>>> Index sauvegardés ! <<<<") );
          break;
        }
      case 23: {
          clearScreen ( );
          indexKWhRouted = 0;
          indexKWhExported = 0;
          indexKWhImported = 0;
          noInterrupts ( );
          indexImpulsion = 0;
          interrupts ( );
          indexWrite ( );
          Serial.println ( F("  >>>>  Index mis à zéro ! <<<<") );
          break;
        }
      case 24: {
          long indexImpulsionTemp = 0;
          noInterrupts ( );
          indexImpulsionTemp = indexImpulsion;
          interrupts ( );
          clearScreen ( );
          Serial.println ( F("  >>>> Valeur des index <<<<") );
          Serial.print ( F("1. Index d'énergie ") );
          Serial.print ( F("routée   : ") );
          Serial.print ( indexKWhRouted, 3 );
          Serial.println ( F(" kWh") );
          Serial.print ( F("2. Index d'énergie ") );
          Serial.print ( F("exportée : ") );
          Serial.print ( indexKWhExported, 3 );
          Serial.println ( F(" kWh") );
          Serial.print ( F("3. Index d'énergie ") );
          Serial.print ( F("importée : ") );
          Serial.print ( indexKWhImported, 3 );
          Serial.println ( F(" kWh") );
          Serial.print ( F("4. Index d'impulsions : ") );
          Serial.print ( indexImpulsionTemp );
          Serial.println ( F(" impulsions") );

          clearSerialInputCache ( );
          Serial.println ( F("\n\
  >>>>> Index + entrée ? (0 pour sortir)\t") );
          int choice = Serial.parseInt ( );
          if ( ( choice > 0 ) && ( choice < 5 ) ) {
            Serial.print ( F("  Valeur ? ") );
            float valueFloat = Serial.parseFloat ( );
            switch ( choice ) {
              case 1 : {
                  indexKWhRouted = valueFloat;
                  break;
                }
              case 2 : {
                  indexKWhExported = valueFloat;
                  break;
                }
              case 3 : {
                  indexKWhImported = valueFloat;
                  break;
                }
              case 4 : {
                  noInterrupts ( );
                  indexImpulsion = long ( valueFloat );
                  interrupts ( );
                  break;
                }
            }
            indexWrite ( );
            Serial.println ( F("  >>>>  Index modifié ! <<<<") );
          }
          break;
        }
/*      case 81: {
          clearScreen ( );
          Serial.println ( F("  >>>>  Dump configuration <<<<") );
          eeConfigDump ( );
          Serial.println ( );
          break;
        }
*/
      case 82: {
          clearScreen ( );
          Serial.println ( F("  >>>>  Effacement de l'EEPROM <<<<") );
          for ( int i = 0 ; i < int ( EEPROM.length ( ) ) ; i++ ) {
            if ( ( i % 50 ) == 0) Serial.print ( F(".") );
            EEPROM.write ( i, 0 );
          }
          Serial.println ( F("  EEPROM effacé !") );
          Serial.println ( F("  !! Veuillez sauvegarder ou mettre à 0 les index !") );
          Serial.println ( F("  !! Sauvegardez la configuration pour la conserver !") );
          Serial.println ( F("  Sinon restauration de la configuration par défaut au prochain démarrage.") );
          break;
        }
      case 99: {
          if ( coldStart == 0 ) {  // On ne redémarre pas si on est encore dans le SETUP
            // ou en phase de démarrage
            indexWrite ( );
            delay ( 500 );
            stopPVR ( );
            Serial.println ( F("\n  Redémarrage en cours...") );
            delay ( 500 );
            startPVR ( );
          }
          clearSerialInputCache ( );
          return;
          break;
        }
      default: {
          Serial.println ( F("\n  >>>> Choix incorrect <<<<") );
          break;
        }
    }
    pressToContinue ( );
  }
}


//////////////////////////////////////////////////////////////////////////////////////
// fatalError                                                                       //
// Fonction de gestion d'erreur majeure                                             //
//////////////////////////////////////////////////////////////////////////////////////

void fatalError ( void ) {

  stopPVR ( );
  // Le système est mis en sécurité
  indexWrite ( );
  // Sauvegarde des index par sécurité

#if defined (OLED_128X64)
  oled.clear ( );
  oled.set2X ( );
  oled.println ( F("***********") );
  oled.println ( F("*  MAJOR  *") );
  oled.println ( F("* FAILURE *") );
  oled.println ( F("***********") );
#endif


#if defined (PV_STATS) || defined (PV_MOD_CONFIG)
if ( STF_TRACEUR == 0 ) {  //STF 23.04.2020
  clearScreen ( );
  Serial.print ( F("\n\n***** !!  A T T E N T I O N  !! *****\n\n\
Une erreur majeure s'est produite.\n\
") );
  Serial.print ( F("Bits de statut : ") );
  Serial.println ( stats_error_status, BIN );
  Serial.println ( );
  Serial.print ( F("Les causes possibles sont :\n\
- Tension secteur perturbée\n\
- Défaillance du circuit de lecture de la tension\n\
") );
  Serial.print ( F("- Défaillance du système\n\n\
") );
  Serial.println ( F("Le système a été mis en sécurité,") );
  Serial.println ( F("et tentera de redémarrer dans une minute.") );
  Serial.println ( F("Redémarrage immédiat --> Entrée\n") );
} //STF 23.04.2020
#endif

  clearSerialInputCache ( );

  for ( int k = 0; k <= 300; k++ ) {
    digitalWrite ( pulseTriacPin, OFF ); //arrêt du SSR par sécurité
    digitalWrite ( ledPinStatus,  OFF );
    digitalWrite ( ledPinRouting, ON );
    delay ( 100 );
    digitalWrite ( ledPinStatus,  ON );
    digitalWrite ( ledPinRouting, OFF );
    delay ( 100 );
    if ( Serial.available ( ) )
      break;
  };

#if defined (PV_STATS) || defined (PV_MOD_CONFIG)
if ( STF_TRACEUR == 0 ) {  //STF 23.04.2020
  Serial.println ( F("Redémarrage...\n") );
} //STF 23.04.2020
#endif

  clearSerialInputCache ( );
  startPVR ( );
}


//////////////////////////////////////////////////////////////////////////////////////
// eeConfigRead                                                                     //
// Fonction de lecture de la configuration EEPROM                                   //
//////////////////////////////////////////////////////////////////////////////////////

bool eeConfigRead ( void ) {

  dataEeprom pvrConfig;

  EEPROM.get ( PVR_EEPROM_START, pvrConfig );

  if ( pvrConfig.magic != DATAEEPROM_MAGIC ) return false;
  else {
    noInterrupts ( );
    PHASE_CALIB   = pvrConfig.phase_calib;
    P_OFFSET      = pvrConfig.p_offset;
    P_MARGIN      = pvrConfig.p_margin;
    GAIN_P        = pvrConfig.gain_p;
    GAIN_I        = pvrConfig.gain_i;
    E_RESERVE     = pvrConfig.e_reserve;
    interrupts ( );
    V_CALIB       = pvrConfig.v_calib;
    P_CALIB       = pvrConfig.p_calib;
    P_RESISTANCE  = pvrConfig.p_resistance;
    P_DIV2_ACTIVE = pvrConfig.p_div2_active;
    P_DIV2_IDLE   = pvrConfig.p_div2_idle;
    T_DIV2_ON     = pvrConfig.t_div2_on;
    T_DIV2_OFF    = pvrConfig.t_div2_off;
    T_DIV2_TC     = pvrConfig.t_div2_tc;
    
    // >>> STF 23.04.2020 - Ajout des infos supplémentaires dans l'eeprom
    STF_TRACEUR   = pvrConfig.stf_traceur;            // 0 : affichage standard jetblack , 1 : Series de données pour affichage sur traceur arduino
    // <<< STF 23.04.2020

    return true;
  }
}


//////////////////////////////////////////////////////////////////////////////////////
// eeConfigWrite                                                                    //
// Fonction d'écriture de la configuration EEPROM                                   //
//////////////////////////////////////////////////////////////////////////////////////

void eeConfigWrite ( void ) {

  dataEeprom pvrConfig;

  pvrConfig.magic           = DATAEEPROM_MAGIC;
  pvrConfig.struct_version  = DATAEEPROM_VERSION;
  pvrConfig.v_calib         = V_CALIB;
  pvrConfig.p_calib         = P_CALIB;
  pvrConfig.phase_calib     = PHASE_CALIB;
  pvrConfig.p_offset        = P_OFFSET;
  pvrConfig.p_resistance    = P_RESISTANCE;
  pvrConfig.p_margin        = P_MARGIN;
  pvrConfig.gain_p          = GAIN_P;
  pvrConfig.gain_i          = GAIN_I;
  pvrConfig.e_reserve       = E_RESERVE;
  pvrConfig.p_div2_active   = P_DIV2_ACTIVE;
  pvrConfig.p_div2_idle     = P_DIV2_IDLE;
  pvrConfig.t_div2_on       = T_DIV2_ON;
  pvrConfig.t_div2_off      = T_DIV2_OFF;
  pvrConfig.t_div2_tc       = T_DIV2_TC;

  // >>> STF 23.04.2020 - Ajout des infos supplémentaires dans l'eeprom
  pvrConfig.stf_traceur     = STF_TRACEUR;            // 0 : affichage standard jetblack , 1 : Series de données pour affichage sur traceur arduino
  // <<< STF 23.04.2020

  EEPROM.put ( PVR_EEPROM_START, pvrConfig );
}


//////////////////////////////////////////////////////////////////////////////////////
// eeConfigDump                                                                     //
// Fonction de dump de la configuration EEPROM                                      //
//////////////////////////////////////////////////////////////////////////////////////
/*
void eeConfigDump ( void ) {
  byte pvrConfigDump [PVR_EEPROM_SIZE];
  EEPROM.get ( PVR_EEPROM_START, pvrConfigDump );
  for ( int i = 0; i < PVR_EEPROM_SIZE; i++ ) Serial.print ( char ( pvrConfigDump[i] ) );
  Serial.println ( );
}
*/

//////////////////////////////////////////////////////////////////////////////////////
// indexRead                                                                        //
// Fonction de lecture des index en EEPROM                                          //
//////////////////////////////////////////////////////////////////////////////////////

void indexRead ( void ) {

  long indexImpulsionTemp = 0;

  EEPROM.get ( PVR_EEPROM_INDEX_ADR, indexKWhRouted );
  EEPROM.get ( ( PVR_EEPROM_INDEX_ADR + 4 ), indexKWhExported );
  EEPROM.get ( ( PVR_EEPROM_INDEX_ADR + 8 ), indexKWhImported );
  EEPROM.get ( ( PVR_EEPROM_INDEX_ADR + 12 ), indexImpulsionTemp );
  noInterrupts ( );
  indexImpulsion = indexImpulsionTemp;
  interrupts ( );
}


//////////////////////////////////////////////////////////////////////////////////////
// indexWrite                                                                       //
// Fonction d'écriture des index en EEPROM                                          //
//////////////////////////////////////////////////////////////////////////////////////

void indexWrite ( void ) {

  long indexImpulsionTemp = 0;

  noInterrupts ( );
  indexImpulsionTemp = indexImpulsion;
  interrupts ( );
  EEPROM.put ( PVR_EEPROM_INDEX_ADR, indexKWhRouted );
  EEPROM.put ( ( PVR_EEPROM_INDEX_ADR + 4 ), indexKWhExported );
  EEPROM.put ( ( PVR_EEPROM_INDEX_ADR + 8 ), indexKWhImported );
  EEPROM.put ( ( PVR_EEPROM_INDEX_ADR + 12 ), indexImpulsionTemp );
  delay (10);
}


//////////////////////////////////////////////////////////////////////////////////////
// upTime                                                                           //
// Fonction de lecture de mise à jour des données de l'horloge interne              //
//////////////////////////////////////////////////////////////////////////////////////

void upTime ( void ) {

  unsigned long stats_PVRClock;

  noInterrupts ( );
  stats_PVRClock = PVRClock;
  interrupts ( );
  stats_PVRClock /= NB_CYCLES;
  secondsOnline = ( stats_PVRClock         ) % 60;
  minutesOnline = ( stats_PVRClock / 60    ) % 60;
  hoursOnline   = ( stats_PVRClock / 3600  ) % 24;
  daysOnline    = ( stats_PVRClock / 86400 );
  // daysOnline est limité à 994 jours modulo 256, puis repassera à 0
}


//////////////////////////////////////////////////////////////////////////////////////
// PVRScheduler                                                                     //
// Fonction Scheduler                                                               //
//////////////////////////////////////////////////////////////////////////////////////

void PVRScheduler ( void ) {

  //*** Toutes les heures                                               ***
  if ( ( minutesOnline == 0 ) && ( secondsOnline == 0 ) ) {
    // Enregistrement des index en mémoire EEPROM
    indexWrite ( );
  }

  // *** Envoi des donnéees statistiques vers MYSENSORS si activé       ***
  // *** Période d'envoi définie par MYSENSORS_TRANSMIT_PERIOD          ***
  // *** Envoi à la 3ème seconde de l'intervalle de temps               ***
#if ( ( defined (MYSENSORS_COM) ) && ( defined (MYSENSORS_TRANSMIT_PERIOD) ) )
  if ( MYSENSORS_TRANSMIT_PERIOD > 0 ) {
    if ( ( secondsOnline % MYSENSORS_TRANSMIT_PERIOD ) == 3 ) mySensorsTransmit ( );
  }
#endif

  // *** Affichage des donnéees statistiques sur écran oled si activé   ***
  // *** Période d'envoi définie par OLED_128X64_REFRESH_PERIOD         ***
  // *** Envoi page 0 à la 2ème seconde de l'intervalle de temps        ***
  // *** et page 1 à la 5ème seconde                                    ***
#if ( ( defined (OLED_128X64) ) && ( defined (OLED_128X64_REFRESH_PERIOD) ) )
  if ( OLED_128X64_REFRESH_PERIOD > 0 ) {
    if ( ( secondsOnline % OLED_128X64_REFRESH_PERIOD ) == 2 ) oLedPrint ( 0 );
    if ( ( secondsOnline % OLED_128X64_REFRESH_PERIOD ) == 5 ) oLedPrint ( 1 );
  }
#endif
}

//////////////////////////////////////////////////////////////////////////////////////
// PVRLed                                                                           //
// Fonction gestion allumage des leds de signalisation                              //
//////////////////////////////////////////////////////////////////////////////////////

void PVRLed ( void ) {

  byte routingByte = stats_error_status & B00001011;
  byte errorByte = stats_error_status & B11110000;

  if ( routingByte == 0 ) {         // pas de routage
    digitalWrite ( ledPinRouting, OFF );      // led éteinte
  }
  else if ( routingByte == 1 ) {    // routage en régulation
    digitalWrite ( ledPinRouting, ON  );      // allumage fixe
  }
  else {                            // autre cas : routage à 100 % voire exportation
    digitalWrite ( ledPinRouting, ( ( ledBlink & B00001000 ) == 0 ) ? 0 : 1 ); // T = 320 ms
  }

  if ( errorByte == 0 ) {          // pas d'ereur
    digitalWrite ( ledPinStatus, ( ( ledBlink & B01000000 ) == 0 ) ? 0 : 1 ); // T = 2560 ms
  }
  else if ( errorByte < 64 ) {     // anomalie sur les signaux analogiques ou le taux d'acquisition
    digitalWrite ( ledPinStatus, ( ( ledBlink & B00001000 ) == 0 ) ? 0 : 1 ); // T = 320 ms
  }
  else {                           // autre cas : anomalie furtive voire grave de détection du passage à 0
    digitalWrite ( ledPinStatus, ON  );       // allumage fixe
  }
}


//////////////////////////////////////////////////////////////////////////////////////
// clearSerialInputCache                                                            //
// Fonction de vidage du buffer de réception de la liaison série                    //
//////////////////////////////////////////////////////////////////////////////////////

void clearSerialInputCache ( void ) {

  while ( Serial.available ( ) > 0 ) Serial.read ( );
}


//////////////////////////////////////////////////////////////////////////////////////
// printTab                                                                         //
// Fonction Afficher tabulation                                                     //
//////////////////////////////////////////////////////////////////////////////////////

void printTab ( void ) {

  Serial.print ( F("\t") );
}


//////////////////////////////////////////////////////////////////////////////////////
// clearScreen                                                                      //
// Fonction Effacer terminal                                                        //
// Ne fonctionne qu'avec un vrai terminal, pas avec la console de l'IDE Arduino     //
//////////////////////////////////////////////////////////////////////////////////////

void clearScreen ( void ) {

  Serial.write ( 27 );       // ESC
  Serial.print ( "[2J" );    // clear screen
  Serial.write ( 27 );       // ESC
  Serial.print ( "[H" );     // cursor to home
}


//////////////////////////////////////////////////////////////////////////////////////
// pressToContinue                                                                  //
// Fonction "Appui sur entrée pour continuer"                                       //
//////////////////////////////////////////////////////////////////////////////////////

void pressToContinue ( void ) {

  unsigned long refTime = millis ( );

  clearSerialInputCache ( );
  Serial.println ( F("\nAppuyez sur entrée pour continuer") );
  while ( ( Serial.available ( ) == 0 ) && ( ( millis ( ) - refTime ) < SERIALTIMEOUT ) );
  clearSerialInputCache ( );
}


//////////////////////////////////////////////////////////////////////////////////////
// analogReadReference                                                              //
// Fonction de lecture de la référence interne de tension                           //
// Adapté de : https://www.carnetdumaker.net/snippets/77/                           //
//////////////////////////////////////////////////////////////////////////////////////

unsigned int analogReadReference ( void ) {

  ADCSRA &= B00000000;
  ADCSRA |= B10000000;
  ADCSRA |= B00000111;
  /* Elimine toutes charges résiduelles */
  ADMUX = 0x4F;
  delayMicroseconds ( 500 );
  /* Sélectionne la référence interne à 1.1 volts comme point de mesure, avec comme limite haute VCC */
  ADMUX = 0x4E;
  delayMicroseconds ( 1500 );

  /* Lance une conversion analogique -> numérique */
  ADCSRA |= ( 1 << ADSC );
  /* Attend la fin de la conversion */
  while ( ( ADCSRA & ( 1 << ADSC ) ) );
  /* Récupère le résultat de la conversion */
  return ADCL | ( ADCH << 8 );

  /* Note : Idéalement VCC = 5 volts = 1023 'bits' en conversion ADC
    1 'bit' vaut VCC / 1023
    Référence interne à 1.1 volts = ( 1023 * 1.1 ) / VCC = 225 'bits'
    En mesurant la référence à 1.1 volts, on peut déduire la tension d'alimentation réelle du microcontrôleur
    vccSupply = ( 1023 * VCC_1BIT )
    avec   VCC_1BIT = ( 1.1 / analogReadReference ( ) )
  */
}

//////////////////////////////////////////////////////////////////////////////////////
// optionPrint                                                                      //
// Affichage des options de compilation activées                                    //
//////////////////////////////////////////////////////////////////////////////////////

void optionPrint ( void ) {

  // Affichage des options STATS
#if defined (PV_STATS)
  Serial.print ( F("Affichage des données statistiques ") );
  Serial.println ( F("par liaison série\t:\tactivé") );
#endif

  // Affichage des options CONFIG
#if defined (PV_MOD_CONFIG)
  Serial.print ( F("Modification de la configuration ") );
  Serial.println ( F("par liaison série\t:\tactivé") );
#endif

  // Affichage de l'option de communication disponible

#if defined (OLED_128X64)
  Serial.print ( F("Option OLED_128X64 installée :\n") );
  Serial.print ( F("\tAdresse de l'écran : 0x") );
  Serial.println ( I2C_ADDRESS, HEX );
#endif

#if defined (MYSENSORS_COM)
  Serial.print ( F("Option MYSENSORS installée :\n") );
  Serial.print ( F("\tID du noeud de capteur : ") );
  Serial.println ( MY_NODE_ID );
#endif

#if defined (ETHERNET_28J60)
  Serial.print ( F("Option ETHERNET installée :\n") );
  Serial.print ( F("\tAdresse MAC\t:\t") );
  Serial.print ( ethMac [0], HEX );
  Serial.print ( F(":") );
  Serial.print ( ethMac [1], HEX );
  Serial.print ( F(":") );
  Serial.print ( ethMac [2], HEX );
  Serial.print ( F(":") );
  Serial.print ( ethMac [3], HEX );
  Serial.print ( F(":") );
  Serial.print ( ethMac [4], HEX );
  Serial.print ( F(":") );
  Serial.println ( ethMac [5], HEX );
  Serial.print ( F("\tAdresse IP\t:\t") );
  Serial.print ( ethIp [0] );
  Serial.print ( F(".") );
  Serial.print ( ethIp [1] );
  Serial.print ( F(".") );
  Serial.print ( ethIp [2] );
  Serial.print ( F(".") );
  Serial.println ( ethIp [3] );
  Serial.print ( F("\tPort IP\t\t:\t") );
  Serial.println ( ethPort );
#endif

  Serial.println ( );
}


//////////////////////////////////////////////////////////////////////////////////////
// versionPrint                                                                     //
// Affichage de la version                                                          //
//////////////////////////////////////////////////////////////////////////////////////

void versionPrint ( void ) {

  Serial.print ( F("\n***** EcoPV version ") );
  Serial.print ( F(VERSION) );
  Serial.print ( F(" *****\n") );
  Serial.print ( F("EcoPV - Copyright (C) 2019 - Bernard Legrand and Mickaël Lefebvre\n\n") );
  /*Serial.print ( F("This program is free software: you can redistribute it and/or modify\n") );
  Serial.print ( F("it under the terms of the GNU Lesser General Public License as published\n") );
  Serial.print ( F("by the Free Software Foundation, either version 2.1 of the License, or\n") );
  Serial.print ( F("(at your option) any later version.\n\n") );
  */
}


//////////////////////////////////////////////////////////////////////////////////////
// mySensorsTransmit                                                                //
// Fonction de transmission MYSENSORS                                               //
//////////////////////////////////////////////////////////////////////////////////////

#if defined (MYSENSORS_COM)

void mySensorsTransmit ( void ) {

  send ( msg_vrms.set   ( Vrms, 1 ) );
  send ( msg_irms.set   ( Irms, 2 ) );
  send ( msg_pwr.set    ( Pact, 0 ) );
  send ( msg_pva.set    ( Papp, 0 ) );
  send ( msg_prt.set    ( Prouted, 0 ) );
  send ( msg_pimp.set   ( ( ( Pact >= 0 ) ? Pact : 0 ), 0 ) );
  send ( msg_pexp.set   ( ( ( Pact <= 0 ) ? -Pact : 0 ), 0 ) );
  send ( msg_cosphi.set ( cos_phi, 2 ) );
  send ( msg_error.set  ( stats_error_status ) );
  send ( msg_kwh.set    ( long ( indexKWhRouted ) ) );
}

#endif

//////////////////////////////////////////////////////////////////////////////////////
// oLedPrint                                                                        //
// Fonction d'affichage écran oled                                                  //
//////////////////////////////////////////////////////////////////////////////////////

#if defined (OLED_128X64)

void oLedPrint ( int page ) {

  oled.clear ( );
  oled.set2X ( );

  switch ( page ) {

    case 0 : {
        oled.println ( F(" Running") );
        if ( Pact < 0 )
          oled.print ( F("Expt ") );
        else
          oled.print ( F("Impt ") );
        if ( abs ( Pact ) < 10 ) oled.print ( F("   ") );
        else if ( abs ( Pact ) < 100 ) oled.print ( F("  ") );
        else if ( abs ( Pact ) < 1000 ) oled.print ( F(" ") );
        oled.print ( abs ( Pact ), 0 );
        oled.println ( F("W") );
        oled.print ( F("Rout ") );
        if ( Prouted < 10 ) oled.print ( F("   ") );
        else if ( Prouted < 100 ) oled.print ( F("  ") );
        else if ( Prouted < 1000 ) oled.print ( F(" ") );
        oled.print ( Prouted, 0 );
        oled.println ( F("W") );
        oled.print ( F("Relay ") );
        if ( digitalRead ( relayPin ) == ON ) oled.println ( F("  On") );
        else oled.println ( F(" Off") );
        break;
      }

    case 1 : {
        if ( stats_error_status > 15 ) oled.println ( F("! Check !") );
        else oled.println ( F("  Normal") );
        oled.print ( F(" ") );
        oled.print ( Vrms, 0 );
        oled.println ( F(" Volts") );
        if ( Irms < 10 ) oled.print ( F(" ") );
        oled.print ( Irms, 1 );
        oled.println ( F(" Amps") );
        oled.print ( F(" ") );
        oled.print ( abs ( cos_phi ), 1 );
        oled.println ( F(" Cosfi") );
        break;
      }
  }
}

#endif

//////////////////////////////////////////////////////////////////////////////////////
// ethernetProcess                                                                  //
// Fonction de gestion ETHERNET                                                     //
//////////////////////////////////////////////////////////////////////////////////////

#if defined (ETHERNET_28J60)

bool ethernetProcess ( void ) {

  char *ethParam;
  char buffer [16];
  bool returnFlag = false;

  ethParam = ethernet.serviceRequest ( );
  // Note : si aucune trame ethernet n'est disponible, alors ethParam est un pointeur de valeur 0
  // qui ne sera pas a priori l'adresse de la chaine de caractères de la requête si elle est valide
  // cf. codage de la méthode dans la librairie ETHER_28J60

  // Note : les fonctions de la bibliothèque ne permettent que d'écrire des nombres entiers (int)
  // De ce fait, les données numériques sont tronquées en entier.

  if ( ( unsigned int ) ethParam != 0  )
  {
    int ethParamLen = strlen ( ethParam );
    int ethParamNum;
    strcpy ( buffer, "{\"value\":\"" );
    ethernet.print ( buffer );

    if ( ( ethParamLen == 5 ) && ( strncmp ( "Get", ethParam, 3 ) == 0) ) {
      // Cas normal de demande de données : GetXX
      ethParamNum = atoi ( ( char* ) &ethParam [3] );
      switch ( ethParamNum ) {
        case 0: {
            strcpy ( buffer, "error param" );
            ethernet.print ( buffer );
            break;
          }
        case 1: {
            ethernet.print ( (int) Vrms );
            break;
          }
        case 2: {
            ethernet.print ( (int) Irms );
            break;
          }
        case 3: {
            ethernet.print ( (int) Pact );
            break;
          }
        case 4: {
            ethernet.print ( (int) Papp );
            break;
          }
        case 5: {
            ethernet.print ( (int) Prouted );
            break;
          }
        case 6: {
            ethernet.print ( (int) ( ( Pact >= 0 ) ? Pact : 0 ) );
            break;
          }
        case 7: {
            ethernet.print ( (int) ( ( Pact <= 0 ) ? -Pact : 0 ) );
            break;
          }
        case 8: {
            ethernet.print ( (int) ( 1000 * cos_phi ) );
            break;
          }
        case 9: {
            ethernet.print ( (int) indexKWhRouted );
            break;
          }
        case 10: {
            ethernet.print ( (int) indexKWhImported );
            break;
          }
        case 11: {
            ethernet.print ( (int) indexKWhExported );
            break;
          }
        case 12: {
            long indexImpulsionTemp = 0;
            noInterrupts ( );
            indexImpulsionTemp = indexImpulsion;
            interrupts ( );
            ltoa ( indexImpulsionTemp, buffer, 10 );
            ethernet.print ( buffer );
            break;
          }
        case 20: {
            ethernet.print ( (int) stats_error_status );
            break;
          }
        case 21: {
            ethernet.print ( (int) daysOnline );
            strcpy ( buffer, ":" );
            ethernet.print ( buffer );
            ethernet.print ( (int) hoursOnline );
            ethernet.print ( buffer );
            ethernet.print ( (int) minutesOnline );
            ethernet.print ( buffer );
            ethernet.print ( (int) secondsOnline );
            break;
          }
        case 90: {
            indexKWhRouted   = 0;
            indexKWhExported = 0;
            indexKWhImported = 0;
            noInterrupts ( );
            indexImpulsion = 0;
            interrupts ( );
            indexWrite ( );
            strcpy ( buffer, "ok" );
            ethernet.print ( buffer );
            break;
          }
        case 91: {
            indexWrite ( );
            strcpy ( buffer, "ok" );
            ethernet.print ( buffer );
            break;
          }
        case 92: {
            indexWrite ( );
            strcpy ( buffer, "ok" );
            ethernet.print ( buffer );
            returnFlag = true;
            delay ( 500 );
            stopPVR ( );
            delay ( 500 );
            startPVR ( );
            break;
          }
        case 93: {
            for ( int i = 0 ; i < int ( EEPROM.length ( ) ) ; i++ ) {
              EEPROM.write ( i, 0 );
            }
            strcpy ( buffer, "ok" );
            ethernet.print ( buffer );
            break;
          }
        case 94: {
            eeConfigWrite ( );
            strcpy ( buffer, "ok" );
            ethernet.print ( buffer );
            break;
          }
        case 99: {
            strcpy ( buffer, VERSION );
            ethernet.print ( buffer );
            break;
          }
        default: {
            strcpy ( buffer, "error param" );
            ethernet.print ( buffer );
            break;
          }
      }
    }

    else if ( ( ethParamLen == 5 ) && ( strncmp ( "Par", ethParam, 3 ) == 0) ) {
      // Cas normal de lecture de la configuration : ParXX
      ethParamNum = atoi ( ( char* ) &ethParam [3] );
      switch ( ethParamNum ) {
        case 0: {
            strcpy ( buffer, "error param" );
            ethernet.print ( buffer );
            break;
          }
        case 1: {
            ltoa ( (long) ( V_CALIB * 1000000 ), buffer, 10 );
            ethernet.print ( buffer );
            break;
          }
        case 2: {
            ltoa ( (long) ( P_CALIB * 1000000 ), buffer, 10 );
            ethernet.print ( buffer );
            break;
          }
        case 3: {
            ethernet.print ( (int) PHASE_CALIB );
            break;
          }
        case 4: {
            ethernet.print ( (int) P_OFFSET );
            break;
          }
        case 5: {
            ethernet.print ( (int) P_RESISTANCE );
            break;
          }
        case 6: {
            ethernet.print ( (int) P_MARGIN );
            break;
          }
        case 7: {
            ethernet.print ( (int) GAIN_P );
            break;
          }
        case 8: {
            ethernet.print ( (int) GAIN_I );
            break;
          }
        case 9: {
            ethernet.print ( (int) E_RESERVE );
            break;
          }
        case 10: {
            ethernet.print ( (int) P_DIV2_ACTIVE );
            break;
          }
        case 11: {
            ethernet.print ( (int) P_DIV2_IDLE );
            break;
          }
        case 12: {
            ethernet.print ( (int) T_DIV2_ON );
            break;
          }
        case 13: {
            ethernet.print ( (int) T_DIV2_OFF );
            break;
          }
        case 14: {
            ethernet.print ( (int) T_DIV2_TC );
            break;
          }
        default: {
            strcpy ( buffer, "error param" );
            ethernet.print ( buffer );
            break;
          }
      }
    }

    else if ( ( ethParamLen >= 7 ) && ( strncmp ( "Set", ethParam, 3 ) == 0 ) && ( strncmp ( "=", ( ethParam + 5 ) , 1 ) == 0 ) ) {
      // Cas normal de changement de la configuration : SetXX=

      strncpy ( buffer, ethParam, 5 );
      ethParamNum = atoi ( ( char* ) &buffer [3] );
      if ( ( ethParamNum > 0) && ( ethParamNum <= NB_PARAM ) ) {
        int index = ethParamNum - 1;
        byte dataType = pvrParamConfig [index].dataType;
        int minValue = pvrParamConfig [index].minValue;
        int maxValue = pvrParamConfig [index].maxValue;
        float valueFloat;
        int valueInt;
        if ( dataType == 1 ) {
          valueFloat = atof ( ( char* ) &ethParam [6] );
          valueFloat = constrain ( valueFloat, float ( minValue ), float ( maxValue ) );
        }
        else {
          valueInt = atoi ( ( char* ) &ethParam [6] );
          valueInt = constrain ( valueInt, minValue, maxValue );
        }

        switch ( dataType ) {
          case 0: {
              int *tmp_int = (int *) pvrParamConfig [index].adr;
              noInterrupts ( );
              *tmp_int = valueInt;
              interrupts ( );
              break;
            }
          case 1: {
              float *tmp_float = (float *) pvrParamConfig [index].adr;
              noInterrupts ( );
              *tmp_float = float ( valueFloat );
              interrupts ( );
              break;
            }
          case 4: {
              byte *tmp_byte = (byte *) pvrParamConfig [index].adr;
              noInterrupts ( );
              *tmp_byte = byte ( valueInt );
              interrupts ( );
              break;
            }
        }
        strcpy ( buffer, "ok" );
        ethernet.print ( buffer );      
      }
      else {
        strcpy ( buffer, "error param" );
        ethernet.print ( buffer );              
      }
    }

    else if ( ethParamLen == 0 ) {        // Requête vide
      strcpy ( buffer, "empty param" );
      ethernet.print ( buffer );
    }
    else {
      strcpy ( buffer, "error param" );
      ethernet.print ( buffer );          // Erreur de requête
    }
    strcpy ( buffer, "\"}\r\n" );
    ethernet.print ( buffer );
    ethernet.respond ( );
  }
  return returnFlag;
}

#endif

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.