Gui Menu et SSD1306Ascii

bonsoir
les bases : Arduino uno - Afficheur OLED SH1106 un 128x64 - 4 bouton poussoirs

donc un Menu léger, en mémoire et d'utilisation.
après test d'autres possibilité ( u8glib, Backend., .....)

suis tomber sur un blog en polonais Menu na wyświetlaczu 16×2 – Starter Kit
(avec translate le Polonais n'a plus de secret pour moi :slight_smile: )

bien expliquer, bon commentaire, et pas de Lib. exotique a rajouter (enfin)

j'ai tester, c impeccable, facile a modifier et surtout léger

Le croquis utilise 10306 octets (31%)
Les variables globales utilisent 628 octets (30%)

j'ai francisé le croquis pour certaine variable, idem pour les commentaire.

le croquis

/*
 *        Menu sur afficheur OLED pour Arduino uno
 *  
* Le croquis utilise dans l'exemple 
* 8692 octets (26%) de l'espace de stockage 
* et
* 644 octets (31%) pour les variables globales 
*        
* sur une base LCD de Kamil : https://starter-kit.nettigo.pl/2017/04/menu-wyswietlaczu-16x2/
* modif pour OLED avec 
* SSD1306Ascii de greiman   : https://github.com/greiman/SSD1306Ascii/
* et
* Gui Menu et SSD1306Ascii : https://forum.arduino.cc/index.php?topic=715415.0
*   
*   4 Boutons Poussoir:  
*   pour naviguer dans le menu, modifier les valeurs et confirmer.
*   Droite et Gauche  -  Retour et OK
* 
*   afficheur OLED 1,3'' I2C TF051 / SH1106
* 
*  
*     Modification :
*  x : desactiver ligne pour prise en compte de default_value
*  x : ajout oled.set2X pour certaine valeur
*  - : passage en auto-scroll des menus ?? 
*  - :   
 * - : 
 *  
 *      ------ Fonctionnelle 24-nov-2020 par JeeLet  -------
 */

  #include <Wire.h>
  #include "SSD1306Ascii.h"
  #include "SSD1306AsciiAvrI2c.h"


  #define I2C_ADDRESS 0x3C
  #define RST_PIN -1             /*Define proper RST_PIN if required*/

  SSD1306AsciiAvrI2c oled;

//les constantes
  #define BTN_BACK  7   /* RETOUR   ^     */
  #define BTN_NEXT  6   /* DROITE  -->    */
  #define BTN_PREV  5   /* GAUCHE  <--    */   
  #define BTN_OK    4   /* OK       *     */
  
  #define LED       8  //pour test pompe

//---------structure et type d'énumération-------------

  typedef struct                  /*déclaration de STRUCT_MENUPOS*/
    {
    String label;
    int minVal;
    int maxVal;
    int currentVal;
    void (*handler)(); //gestionnaire 
    }   STRUCT_MENUPOS;

  typedef enum                      /*déclaration de ENUM_BUTTON*/
    {
    BACK, NEXT, PREV, OK, NONE
    }   ENUM_BUTTON;

//----- structure et variables du tableau menu-------

 STRUCT_MENUPOS menu[5];     /*taille du tableau (menu)*/
 /* Nbr ligne de menu en rapport avec   STRUCT_MENUPOS menu[4];*/

  int currentMenuPos = 0;       /*indique Position actuelle du menu*/
  int menuSize;                 /*taille du menu calculée  en fonction de la déclaration*/               
  bool isInLowerLevel = false;  /*indique si nous sommes menu principal ou sous-menu*/ 
  int tempVal;                  /*valeur temporaire pour currentVal de structure STRUCT_MENUPOS*/  
  
  
 void setup() {    //-------------------------------SETUP-------------------
  
  Serial.begin(115200);
  Wire.begin();
  Wire.setClock(400000L);

  #if RST_PIN >= 0
    oled.begin(&SH1106_128x64, I2C_ADDRESS, RST_PIN);
  #else // RST_PIN >= 0
    oled.begin(&SH1106_128x64, I2C_ADDRESS);
  #endif // RST_PIN >= 0

  oled.setFont(System5x7);
  oled.clear();

  pinMode(BTN_NEXT, INPUT_PULLUP);
  pinMode(BTN_PREV, INPUT_PULLUP);
  pinMode(BTN_BACK, INPUT_PULLUP);
  pinMode(BTN_OK, INPUT_PULLUP);

/*"Menu name", range_start, range_end, default_value, NULL*/
  menu[0] = {"   POMPES 1", 1, 3, 2, actionPump1};       /*définission des éléments du menu*/
  menu[1] = {"Nombres", 10, 1000, 15, NULL};             /*avec les numéros d'index...*/
  menu[2] = {"Sous-Menus", 0, 4, 4, SousMenu};           /*.. le libellé, les valeurs*/
  menu[3] = {"Menus", 0, 0, 0, NULL};                    /*..et apl fonction void format Chaînes??? */
  menu[4] = {"SerialPort", 0, 0, 0, actionserialport};
 
 /*voir STRUCT_MENUPOS pour Info sur valeurs: "menu.maxVal = range.end" ....*/

  menuSize = sizeof(menu)/sizeof(STRUCT_MENUPOS);

               pinMode(LED, OUTPUT);  // simulation pompe
  }

 void loop()    //------------------------------LOOP------------------------
  {   
  drawMenu();
  }

  ENUM_BUTTON getButton()               /*Enum des boutons Poussoir*/
  {
  if(!digitalRead(BTN_BACK)) return BACK;   /* RETOUR   ^    0 */
  if(!digitalRead(BTN_NEXT)) return NEXT;   /* DROITE  -->   1 */
  if(!digitalRead(BTN_PREV)) return PREV;   /* GAUCHE  <--   2 */ 
  if(!digitalRead(BTN_OK))   return OK;     /* OK       *    3 */
  return NONE;
  }


 void drawMenu()                                  /*affiche du Menu*/
 {          
  static unsigned long lastRead = 0;
  static ENUM_BUTTON lastPressedButton = OK;      /*actualise oled lorsque action sur un bouton*/
  static unsigned int isPressedSince = 0;
  int autoSwitchTime = 500;

 //----- fonctionnement des boutons---------------//

  ENUM_BUTTON pressedButton = getButton();              /*vérifie l'etat du bouton*/
    if(pressedButton == NONE && lastRead != 0)
      {
      isPressedSince = 0;
      return;
      }
  if(pressedButton != lastPressedButton)
      {
      isPressedSince = 0;
      }
                                                        /*tempo appui bouton*/ 
  if(isPressedSince > 3) autoSwitchTime = 70;   
    if(lastRead != 0 && millis() - lastRead < autoSwitchTime && pressedButton == lastPressedButton) return;
    isPressedSince++;
    lastRead = millis();
    lastPressedButton = pressedButton;
  
  switch(pressedButton) 
    {
    case NEXT: bpNEXT(); break;
    case PREV: bpPREV(); break;
    case BACK: bpBACK(); break;
    case OK:   bpOK(); break;
    }

 //-----affichage des données à l'écran---------------//
 
  oled.home();                               /*curseur sur position initiale (0,0)*/
  oled.clear();
  
  if(isInLowerLevel)                              /*Si Niveau inférieur*/
    {  
    oled.set1X();
    oled.print(menu[currentMenuPos].label);       /*affiche l'en-tete en rapport*/
    oled.setCursor(0, 2);                         /*position de la selection du menu*/
    oled.print(F("> ")); //inutil??
      if(menu[currentMenuPos].handler != NULL)
      {
      (*(menu[currentMenuPos].handler))();
      }
      else 
      {
      oled.print(tempVal);
      }
    }  
  else
    {
    oled.set1X();
    oled.print(F("Menu principal"));              /*en-tête du menu principal*/
    oled.setCursor(0, 2);                         /*position ligne du menu*/
    oled.print(F("> "));
    oled.print(menu[currentMenuPos].label);
    }
  
 }  //--- fin drawmenu----

//-----------------------appel appropriée en fonction des boutons-------------------------------

 void bpNEXT()          /*BP NEXT --> droite */
 {                                        
  if(isInLowerLevel)                  /*Si Niveau inférieur*/
    {          
    tempVal++;                        /*augmente la valeur*/
    if(tempVal > menu[currentMenuPos].maxVal) tempVal = menu[currentMenuPos].maxVal;
    }                     /*vérifi  dépassé la plage*/
  else
    {
    currentMenuPos = (currentMenuPos + 1) % menuSize;
    /*incrémente de +1 la Position actuelle du menu, suivant taille du menu*/
    }
 }

 void bpPREV()         /*BP PRECEDENT  <-- gauche*/
 {                                    
  if(isInLowerLevel)
    {
    tempVal--;
    if(tempVal < menu[currentMenuPos].minVal) tempVal = menu[currentMenuPos].minVal;
    }
    else 
    {
    currentMenuPos--;
    if(currentMenuPos < 0) currentMenuPos = menuSize - 1;
    }
 }  

 void bpBACK()           /*BP BACK  retour d'un niveau*/ 
 {                                                
  if(isInLowerLevel)
    {
    isInLowerLevel = false;
    }
 }

 void bpOK()            /*BP OK  exécute ou changement de niveau*/ 
 {                                   
  if(menu[currentMenuPos].handler != NULL && menu[currentMenuPos].maxVal <= menu[currentMenuPos].minVal) 
    {
    (*(menu[currentMenuPos].handler))();
    return;
    }
  if(isInLowerLevel)
    {
        //  menu[currentMenuPos].currentVal = tempVal;  
        // desactiver pour prise en compte de default_value du menu
    isInLowerLevel = false;
    }
  else
    {
    tempVal = menu[currentMenuPos].currentVal;
    isInLowerLevel = true;
    }
 }

//-------------------Fonctions de gestion de l'utilisateur---------------------------------

 void actionPump1 ()
 {
  oled.print("   Cmd pompe 1" );    
  oled.setCursor(22, 4);

  switch (tempVal)
    {
    case 1 :  oled.set2X();oled.print("  ON");break;
    case 2 :  oled.set1X();oled.print("on <--  --> off");break;
    case 3 :  oled.set2X();oled.print("  OFF");break;
    }
    
  if(tempVal == 1 ) { digitalWrite(LED, HIGH);}
  if(tempVal == 3)  {digitalWrite(LED, LOW);}
 }

 void SousMenu() 
 {
  //String dictonary[] = {"inscription 1", "inscription 2", "inscription 3"};
  //oled.print(dictonary[tempVal]);

  switch (tempVal)
    {
    case 0 : oled.print("niv1");break;
    case 1 : oled.print("niv2");break;
    case 2 : oled.print("niv3");break;
    case 3 : oled.print("niv4");break;
    case 4 : oled.print("niv5");break;
    }
 }

 void actionserialport()
 {
 Serial.println("Actions déclenchées: port série");  /*sortie sur terminal*/
 }

 void Element()
 {
 oled.print(tempVal / 10.0);
 }

//----------------------------- Fin du Pgm------------------------------------

Bonjour
une version très alléger,
une commande de pompe (led en simulation)
et essai d’un sous-sous-Menu

/*
 *  Menu Gui pour Arduino uno
 * 
 * info: https://skyduino.wordpress.com/2014/07/06/arduino-lcd-faire-un-menu-sous-forme-de-liste/
 *      : https://www.ihm3d.fr/httpihm3d-frprogrammer-un-menu-avec-un-ecran-lcd.html
 *      
 * sur une base LCD : https://starter-kit.nettigo.pl/2017/04/menu-wyswietlaczu-16x2/
 * modif pour OLED et SSD1306Ascii de https://github.com/greiman/SSD1306Ascii/
 * 
 * 
 * 
 *  4 Boutons Poussoir:  
 *  pour naviguer dans le menu et modifier puis confirmer les options
 *  Droite et Gauche (inc
 *  Retour ou OK
 * 
 *  afficheur OLED 1,3'' I2C TF051 / SH1106
 * 
 *  le 24-nov-2020 par JeeLet 
 *  
 *  Démo ultra simple  - 2niveau de menu
 */

  #include <Wire.h>
  #include "SSD1306Ascii.h"
  #include "SSD1306AsciiAvrI2c.h"

  #define I2C_ADDRESS 0x3C
  #define RST_PIN -1    /*Define proper RST_PIN if required*/
  
  SSD1306AsciiAvrI2c oled;

//les constantes
  #define BTN_BACK  7   /* RETOUR   ^     */
  #define BTN_NEXT  6   /* DROITE  -->    */
  #define BTN_PREV  5   /* GAUCHE  <--    */   
  #define BTN_OK    4   /* OK       *     */
  #define LED       8
  
//---------structure et type d'énumération-------------

  typedef struct /*déclaration de STRUCT_MENUPOS*/
    {
    String label;
    int minVal;
    int maxVal;
    int currentVal;
    void (*handler)(); //gestionnaire 
    }   STRUCT_MENUPOS;

  typedef enum    /*déclaration de ENUM_BUTTON*/
    {
    BACK, NEXT, PREV, OK, RC,NONE
    }   ENUM_BUTTON;

//----- structure et variables du tableau menu-------

 STRUCT_MENUPOS menu[4];     /*taille du tableau (menu)*/

  int currentMenuPos = 0;  /* indique Position actuelle du menu*/
  int menuSize;           /*taille du menu calculée  en fonction de la déclaration*/               
  bool isInLowerLevel = false;    /*indique si nous sommes menu principal ou sous-menu*/ 
  int tempVal;    /*valeur temporaire pour currentVal de la structure STRUCT_MENUPOS*/  
  

void setup() {    //-------------------------------SETUP-------------------
  
  Serial.begin(115200);
  Wire.begin();
  Wire.setClock(400000L);

  #if RST_PIN >= 0
    oled.begin(&SH1106_128x64, I2C_ADDRESS, RST_PIN);
  #else // RST_PIN >= 0
    oled.begin(&SH1106_128x64, I2C_ADDRESS);
  #endif // RST_PIN >= 0

  oled.setFont(System5x7);
  oled.clear();

  pinMode(BTN_NEXT, INPUT_PULLUP);
  pinMode(BTN_PREV, INPUT_PULLUP);
  pinMode(BTN_BACK, INPUT_PULLUP);
  pinMode(BTN_OK, INPUT_PULLUP);

/*"Menu name", range_start, range_end, default_value, NULL*/
  menu[0] = {"POMPES", 1, 3, 2, actionPump};                  /*définission des éléments du menu*/
  menu[1] = {"Nombres", 10, 1000, 15, NULL};                  /*avec les numéros d'index...*/
  menu[2] = {"Sous-Menus", 0, 3, 0, formatSousMenu};          /*.. le libellé, les valeurs*/
  menu[3] = {"Menus", 0, 0, 0, NULL}; 

/*Info sur valeurs: "menu.maxVal = range.end" ....*/

  menuSize = sizeof(menu)/sizeof(STRUCT_MENUPOS);

   pinMode(LED, OUTPUT);  //pompe
  }

void loop()    //------------------------------LOOP------------------------
  {   
  drawMenu();
  }


  ENUM_BUTTON getButton()       /*Enum des boutons Poussoir*/
  {
  if(!digitalRead(BTN_BACK)) return BACK;   /* RETOUR   ^    0 */
  if(!digitalRead(BTN_NEXT)) return NEXT;   /* DROITE  -->   1 */
  if(!digitalRead(BTN_PREV)) return PREV;   /* GAUCHE  <--   2 */ 
  if(!digitalRead(BTN_OK))   return OK;     /* OK       *    3 */
  return NONE;
  }

void drawMenu()            /*affiche du Menu*/
  {          
  static unsigned long lastRead = 0;
  static ENUM_BUTTON lastPressedButton = OK;  /*actualise lorsque action sur un bouton*/
  static unsigned int isPressedSince = 0;
  int autoSwitchTime = 500;

 //----- fonctionnement des boutons---------------//
   ENUM_BUTTON pressedButton = getButton();    /* vérifie l'etat du bouton*/
  if(pressedButton == NONE && lastRead != 0) {
    isPressedSince = 0;
    return;
  }
  if(pressedButton != lastPressedButton) {
    isPressedSince = 0;
  }

/*-----tempo appui bouton*/ 
  if(isPressedSince > 3) autoSwitchTime = 70;
  if(lastRead != 0 && millis() - lastRead < autoSwitchTime && pressedButton == lastPressedButton) return;
  isPressedSince++;
  lastRead = millis();
  lastPressedButton = pressedButton;

//Serial.print("test pressedButton : ");  
//Serial.println(pressedButton); //test,pour control de la valeur
  
  switch(pressedButton) {
    case NEXT: bpNEXT(); break;
    case PREV: bpPREV(); break;
    case BACK: bpBACK(); break;
    case OK:   bpOK(); break;
  }

 //-----affichage des données à l'écran---------------//

  oled.home();         /*curseur sur la position initiale (0,0)*/
  oled.clear();
  
  if(isInLowerLevel)  {   /*Si Niveau inférieur*/
    oled.print(menu[currentMenuPos].label);     /*affiche l'en-tete en rapport*/
    oled.setCursor(0, 2);   //position ligne de la selection du menu
    oled.print(F("> "));

    if(menu[currentMenuPos].handler != NULL) {
      (*(menu[currentMenuPos].handler))();
      }
    else  {
    oled.print(tempVal);
    }
 }
    else {
    oled.print(F("Menu principal"));      /*en-tête du menu principal*/
    oled.setCursor(0, 2);              //position ligne du du menu
    oled.print(F("> "));
    oled.print(menu[currentMenuPos].label);
    }
            //  Serial.println(tempVal); //test,pour control de la valeur
  }

//-----------------------appel appropriée en fonction des boutons-------------------------------

void bpNEXT()  {                                         /*BP NEXT --> droite */
  if(isInLowerLevel)    {          /*Si Niveau inférieur*/
    tempVal++;                  /*augmente la valeur et vérifi  dépassé la plage*/
    if(tempVal > menu[currentMenuPos].maxVal) tempVal = menu[currentMenuPos].maxVal;
    }
    else {
    currentMenuPos = (currentMenuPos + 1) % menuSize;
    //incrémente de +1 la Position actuelle du menu, suivant taille du menu
    }
  }


void bpPREV()   {                                    /*BP PRECEDENT  <-- gauche*/
  if(isInLowerLevel)
    {
    tempVal--;
    if(tempVal < menu[currentMenuPos].minVal) tempVal = menu[currentMenuPos].minVal;
    }
    else {
    currentMenuPos--;
    if(currentMenuPos < 0) currentMenuPos = menuSize - 1;
    }
  }


void bpBACK() {                                     /*BP BACK  retour d'un niveau*/              
  if(isInLowerLevel) {
    isInLowerLevel = false;
    }
 }


void bpOK() {                                   /*BP OK  exécute ou changement de niveau*/ 
  
  if(menu[currentMenuPos].handler != NULL && menu[currentMenuPos].maxVal <= menu[currentMenuPos].minVal) 
    {
    (*(menu[currentMenuPos].handler))();
    return;
    }
   if(isInLowerLevel) {
    menu[currentMenuPos].currentVal = tempVal;
    isInLowerLevel = false;
    }
    else  {
    tempVal = menu[currentMenuPos].currentVal;
    isInLowerLevel = true;
    }
 }

//-------------------Fonctions de gestion de l'utilisateur---------------------------------

 void actionPump () 
   {
    /*
    String dictonary[] = {"marche pompe", "arret pompe"};
    oled.print(dictonary[tempVal]);
    if(tempVal == LOW){ digitalWrite(LED, HIGH);}
    if(tempVal == HIGH){digitalWrite(LED, LOW);}
    */

    oled.print(" Cmd pompe" );    
    oled.setCursor(0, 3);
    
     switch (tempVal)
    {
    case 1 : oled.print("ON");break;
    case 2 : oled.print("off<-- -->on");break;
    case 3 : oled.print("OFF");break;
    }
    
    if(tempVal == 1 ){ digitalWrite(LED, HIGH);}
    if(tempVal == 3){digitalWrite(LED, LOW);}
  } 

 void formatSousMenu() {
  //String dictonary[] = {"inscription 1", "inscription 2", "inscription 3"};
  //oled.print(dictonary[tempVal]);

 switch (tempVal)
  {
    case 0 : oled.print("niv1");break;
    case 1 : oled.print("niv2");break;
    case 2 : oled.print("niv3");break;
  }
}
//----------------------------- Fin du Pgm------------------------------------

Bonsoir
La suite de mon histoire sur la recherche de gestion d'un menu avec un Arduino Uno.
Je bute toujours sur l'envoi de commande On/Off de manière "sécurisé"

avec le croquis dans l'état, c'est très bien pour visualisé des valeurs,
mais pour la commande d'équipement cella n'est pas possible.

…enfin c bon, la semaine sur la gestion d’un On Off :slight_smile:

donc lorsque appuie sur OK je selectionne la Pompe1
j’arrive bien dans la partie commande,
et la avec les touches <— ou —> je peux mettre à On ou Off la pompe
dans “Void bpOK” j’ai désactivé la ligne

void bpOK() {                                   /*BP OK  exécute ou changement de niveau*/ 
    ......  
   if(isInLowerLevel) {
  //  menu[currentMenuPos].currentVal = tempVal;  
  // desactiver pour prise en compte de default_value du menu

le default_value n’est plus écrasé

/“Menu name”, range_start, range_end, default_value, NULL/
menu[0] = {" POMPES 1", 1, 3, 2, actionPump1};

------------------ (après appui bpOK)
| POMPES 1
| > Cmd pompe 1

on ← -->off

j’arrive bien sur la partie “neutre”

aprés je peux faire mon choix
------------------ (après appui bp <—)
| POMPES 1
| > Cmd pompe 1

ON

ou Off avec —>

Ouff

des infos pour les intéressés

//----------------------------- Fin du Pgm------------------------------------
/*
* table des appelle/action void:
 *  Void            : bpSuivant   bpPrecedent   bpRetour    bpOK
 *  pressedButton   :     1           2             .         .
 *  currentMenuPos  :   0.1.2       0.1.2           x         x
 *  isInLowerLevel  :     .           .             0        0.1
 *                          x:suivant dernnier appui
*/
//---------------- INFO sur https://starter-kit.nettigo.pl/2017/04/menu-wyswietlaczu-16x2/ ------------------
/* 
*  Dans notre code, nous déclarons également une structure et un type d'énumération:
*
*   typedef  struct  { 
*     String  label ; 
*     int  minVal ; 
*     int  maxVal ; 
*     int  currentVal ; 
*     void  ( * gestionnaire ) ( ) ; 
*     }  STRUCT_MENUPOS ;
*
*   Chaque élément STRUCT_MENUPOS représentera une seule option du menu. 
*   Le dernier champ peut être déroutant ici, 
*   c'est-à-dire un pointeur vers une fonction qui n'accepte ou ne renvoie aucun paramètre. 
*   Cela nous servira de deux manières. 
*   Si minVal est supérieur ou égal à maxVal (c'est-à-dire que la situation semble absurde), 
*   cette fonction sera l'action que nous voulons effectuer (sans passer au deuxième niveau de menu). 
*   La deuxième façon d'utiliser cette fonction est de remplacer le comportement par défaut du programme 
*   (c'est-à-dire d'afficher la valeur currentVal directement à l'utilisateur 
*   - nous pourrons y insérer nos propres chaînes). 
*   La troisième option consiste à y entrer NULL 
*   - alors le programme se comportera par défaut, 
*   c'est-à-dire afficher currentVal sans aucun formatage.
*  
*  --------------------------------------------------------------
*  
 * bouton OK: 
 * Voyons ce que cache la fonction qui prend en charge le bouton OK. 
 * Si le champ du gestionnaire n'est pas NULL et que maxVal est inférieur ou égal à minVal, 
 * nous exécutons la fonction définie comme gestionnaire et quittons handleOk (). 
 * Sinon, si nous sommes à un niveau inférieur, nous écrivons tempVal dans le champ currentVal 
 * et passons un niveau plus haut. 
 * Si nous sommes dans le menu principal, nous faisons exactement le contraire: 
 * nous sauvegardons currentVal dans tempVal et entrons dans le niveau inférieur du menu
 * 
 * ----------------------------------------
 * 
 * Si vous souhaitez exécuter une action ON OFF pour une pompes
 *  paramétrez l'élément de la manière suivante: 
 * {“ON. POMPES ”, 0, 0, 0, actionPumpPumpSarry}; 
 * puis écrivez la fonction 
 * void actionZalaczeniePompy () {...} 
 * dans laquelle vous exécuterez les étapes nécessaires pour allumer la pompe :) 
 * En cas d'HYSTERESIS, vous pouvez lire la valeur de consigne via le menu [x] .currentVal
 *
 * ------------------------------------------------
 * 
 *  affichage dynamique pour les températures (ou heure)
 *  (de base drawMenu est rafraichi que sur une action d'un bouton)
 *  
 *  s'assurer que la fonction drawMenu et appelle toutes les xsecondes 
 *  lorsque vous vous trouvez dans un menu qui affiche la température
 * 
 *  Dans la boucle principale du programme, vous pouvez vous donner une condition (instruction if) 
 *  que si vous êtes actuellement dans le menu qui affiche la seconde, 
 *  laissez-le appeler drawMenu () toutes les secondes. 
 *  Cela peut être réalisé en mémorisant l'heure du dernier appel dans une variable globale, 
 *  par exemple unsigned long lastLcdRefreshTime, à laquelle la valeur renvoyée par millis () 
 *  sera attribuée à chaque fois que vous appelez drawMenu (). 
 *  Ensuite, vous écrivez la condition if (millis () - lastLcdRefreshTime> = 1000) {drawMenu (); }
 * 
 * ---------------------------------------------------
 * 
 *  question: affichage de l'heure/date, et seulement aprés le Menu si action sur un bouton. 
 *  Réponse: Dans la fonction loop (), appelez drawMenu () uniquement lorsque vous souhaitez dessiner un menu, 
 *  rien de plus simple: D
 *  
 *  ----------------------------------------------
 *        mis en veille de l'OLED
 *     // oled.ssd1306WriteCmd(SSD1306_DISPLAYON); //activer ou désactiver l'affichage    
 *   // oled.ssd1306WriteCmd(SSD1306_DISPLAYOFF);
 *   
 *  -----------------------------------------
  */

A+

Bonsoir

info trouver sur le Forum Polonais:

(de base drawMenu est rafraichi que sur une action d'un bouton)
 *  
 *  s'assurer que la fonction drawMenu et appelle toutes les xsecondes 
 *  lorsque vous vous trouvez dans un menu qui affiche la température
 * 
 *  Dans la boucle principale du programme, vous pouvez vous donner une condition (instruction if) 
 *  que si vous êtes actuellement dans le menu qui affiche la seconde, 
 *  laissez-le appeler drawMenu () toutes les secondes. 
 *  Cela peut être réalisé en mémorisant l'heure du dernier appel dans une variable globale, 
 *  par exemple unsigned long lastLcdRefreshTime, à laquelle la valeur renvoyée par millis () 
 *  sera attribuée à chaque fois que vous appelez drawMenu (). 
 *  Ensuite, vous écrivez la condition
 
 	 if (millis() - lastLcdRefreshTime >= 1000) {drawMenu (); }

autre info : https://pastebin.com/DYLvQbfW&usg=ALkJrhihsDNIR637gpctv6pNxqZ4MwMubQ

 #define TEMP_READ_PERIOD 5000
 float currentTemperature ;
 
boucle void ( )  {
    readTemperature ( ) ;  // température de lecture "thread" toutes les 5 secondes
    drawMenu ( ) ;  // "thread" dessine le menu sans aucun délai
}
 
void drawMenu ( )  {
    // Affichage MENU 
}
 
void readTemperature ( )  {
    static  unsigned  long lastRead = millis ( ) ;
    if ( lastRead + TEMP_READ_PERIOD > = millis ( ) )  {
        // ICI VOUS POUVEZ LIRE LA TEMPÉRATURE À LA TEMPÉRATURE VARIABLE
        lastRead = millis ( ) ;
    }
}

zut

… suite et FIN :slight_smile:

Bonsoir

une version final du Menu sur afficheur OLED

vidéo pour visu 2020-12-13-193702.webm - Casimages.com
(bon pas toptop, mais regardable)

j’ai bien mon retour d’état des commandes (le 0 ou 1 entre <—x---->)
… des commandes On-Off avec prise en compte de la touche OK pour confirmation.
et valeurs dynamique (mesures) rafraîchi sans scintillement.

A+

/*
*        Menu sur afficheur OLED pour Arduino uno
*  
* Le croquis utilise dans l'exemple 
*  11846 octets (36%) de l'espace de stockage 
*   et
*  740 octets (36%) pour les variables globales 
*        
* sur une base LCD de Kamil : https://starter-kit.nettigo.pl/2017/04/menu-wyswietlaczu-16x2/
* modif pour OLED avec 
* SSD1306Ascii de greiman   : https://github.com/greiman/SSD1306Ascii/
* et
* DHT22 - Lib dhtnew.h de Rob Tillaart
* 
* autres info sur Gui Menu et SSD1306Ascii : https://forum.arduino.cc/index.php?topic=715415.0
* 
*   4 Boutons Poussoir:  
*   pour naviguer dans le menu, modifier les valeurs et confirmer.
*   Droite et Gauche  -  Retour et OK
* 
*   afficheur OLED 1,3'' I2C TF051 / SH1106
* 
*  
*   Menu avec valeurs actualisée (sonde/mesure) 
*   et 
*   fonction "confirmer" une demande On/Off ou valeurs (avec touche OK) 
*  
*                 autres Info en fin de croquis   
*  
*/
        //-----------4.dec.2020---------------- fonctionnnelle -----------------//


  #include <Wire.h>
  #include "SSD1306Ascii.h"
  #include "SSD1306AsciiAvrI2c.h"
  #include <dhtnew.h>                     /*capteur DHT22*/

//les constantes du menu
  #define BTN_BACK  7   /* RETOUR   ^     */
  #define BTN_NEXT  6   /* DROITE  -->    */
  #define BTN_PREV  5   /* GAUCHE  <--    */   
  #define BTN_OK    4   /* OK       *     */
  
// ...autres
  #define I2C_ADDRESS 0x3C
  #define RST_PIN -1             /*Define proper RST_PIN if required*/

  SSD1306AsciiAvrI2c oled;  
  
  #define LED   8             /*test On Off (simul pompe)*/
  DHTNEW capt1 (9);

//---------structure et type d'énumération-------------

  typedef struct   {               /*déclaration de STRUCT_MENUPOS*/
    String label;
    int minVal;
    int maxVal;
    int currentVal;
    void (*handler)();            /*gestion des fonction void*/ 
    }   STRUCT_MENUPOS;

  typedef enum                      /*déclaration de ENUM_BUTTON*/
    {
    BACK, NEXT, PREV, OK, NONE
    }   ENUM_BUTTON;

//----- structure et variables du tableau menu-------

 STRUCT_MENUPOS menu[3];     /*taille du tableau , Nbr ligne en rapport*/
 
  int currentMenuPos = 0;       /*indique Position actuelle du menu*/
  int menuSize;                 /*taille du menu calculée  en fonction de la déclaration*/               
  bool isInLowerLevel = false;  /*indique si nous sommes menu principal ou sous-menu*/ 
  int tempVal;                  /*valeur temporaire pour currentVal de structure STRUCT_MENUPOS*/  

//------------ DHT22 - Refresh et stateOK---------

  float currentTemperatur ;   //DHT22
  float currentHumiditi ;
  
  unsigned long previousMillis=0 ;
  unsigned long interval = 3000;
  bool dynRefresh = false;            /*rafraîchissement des valeurs*/

  int stateOK;                        /*action OK surTC*/
  
//-------------------------------SETUP-------------------//  
 void setup() {    
  
  Serial.begin(115200);
  Wire.begin();
  Wire.setClock(400000L);

  #if RST_PIN >= 0
    oled.begin(&SH1106_128x64, I2C_ADDRESS, RST_PIN);
  #else // RST_PIN >= 0
    oled.begin(&SH1106_128x64, I2C_ADDRESS);
  #endif // RST_PIN >= 0

  oled.setFont(System5x7);
  oled.clear();

  pinMode(BTN_NEXT, INPUT_PULLUP);
  pinMode(BTN_PREV, INPUT_PULLUP);
  pinMode(BTN_BACK, INPUT_PULLUP);
  pinMode(BTN_OK, INPUT_PULLUP);

/*définission des éléments du menu avec les numéros d'index,le libellé, les valeurs et apl fonction void*/
//-----------"....................."------pour largeur OLED------------
  menu[0] = {"Selec. Menu <-->", 0, 1, 1,infomenu};     
  menu[1] = {"       POMPES        ", 1, 2, 2, actionPump};     
  menu[2] = {"       DHT 22        ", 0, 100, 12, readDHT};    

  menuSize = sizeof(menu)/sizeof(STRUCT_MENUPOS);          
 
/*"Menu name", range_start, range_end, default_value, NULL*/
/*voir STRUCT_MENUPOS pour Info sur valeurs: "label", min-Val, maxVal, currentval, NUL ou apl Fonction Void*/

  pinMode(LED, OUTPUT);  // simulation pompe
               
  }

//------------------------------LOOP------------------------//
 void loop() {   
    
  drawMenu();       /*---- apl Affichage du menu ----*/
  
  if( millis() - previousMillis >= interval)  { //---- rafraîchissement des valeurs dynamique ----
      previousMillis = millis(); 
      Serial.println(dynRefresh); 
    //  Serial.println(tempVal);
      dynRefresh = true;
      }

  if (millis() - capt1.lastRead() > 2000) {    //----capteur local DHT22-----
      capt1.read();
      currentTemperatur = capt1.temperature ;
      currentHumiditi   = capt1.humidity;
      }
    
  }  // ------------fin Loop-----------------------------//

  ENUM_BUTTON getButton()  {             /*Enum des boutons Poussoir*/
    if(!digitalRead(BTN_BACK)) return BACK;   /* RETOUR   ^    0 */
    if(!digitalRead(BTN_NEXT)) return NEXT;   /* DROITE  -->   1 */
    if(!digitalRead(BTN_PREV)) return PREV;   /* GAUCHE  <--   2 */ 
    if(!digitalRead(BTN_OK))   return OK;     /* OK       *    3 */
    return NONE;
    }

 void drawMenu() {                       //-----affichage du Menu ---- 
  
  /*----partie de code qui actualise le menu à chaque action sur un bouton -----------*/
 
  static unsigned long oldRead = 0;            /*derniere lecture*/
  static ENUM_BUTTON lastPressedButton = OK;    /*actualise lorsque action sur un bouton*/
  static unsigned int isPressedSince = 0;       /*etat dernnier action sur BP*/
  int autoSwitchTime = 500;                     /*incremental action*/

 //----- fonctionnement des boutons---------------//

   ENUM_BUTTON pressedButton = getButton();    /* obtenir l'etat des bouton*/

   if(pressedButton == OK)
   {
    stateOK = 1;
   }

 
  if(pressedButton == NONE && dynRefresh == false )    
      {
      isPressedSince = 0;  /*etat dernnier action sur BP*/
      return;
      }

    if(pressedButton != lastPressedButton)
      {
      isPressedSince = 0;
      }
                                                   
//----- fonction répet bp ---------------//
      
 if(isPressedSince > 3) autoSwitchTime = 70;   
    if(oldRead != 0 && millis() - oldRead < autoSwitchTime && pressedButton == lastPressedButton) return;
    isPressedSince++;
    oldRead = millis();
    lastPressedButton = pressedButton;
  
  switch(pressedButton)     //---- apl fonction void des BP  -------//
    {
    case NEXT: bpNEXT(); break;
    case PREV: bpPREV(); break;
    case BACK: bpBACK(); break;
    case OK:   bpOK();   break;
    }

//-----affichage des données à l'écran---------------//
 
  oled.home();         /*curseur sur la position initiale (0,0)*/
  
  if( pressedButton == OK  ||  pressedButton == BACK ) {
    oled.clear();
    }
 
  if(isInLowerLevel)     {         /*verifi si 2éme Niveau du menu (niveau nférieur)*/
  
    oled.set1X();
    oled.print(menu[currentMenuPos].label);     /*affiche l'en-tete en rapport*/
    oled.setCursor(0, 3);   //position ligne de la selection du menu
   
/*
 Si tel est le cas, nous affichons dans l'en-tête l'info-bulle de l'option dans laquelle nous nous trouvons,
 nous imprimons l'invite et s'il n'y a pas de fonction qui gérera l'affichage des options disponibles,
 nous imprimons une variable temporaire à l'écran. 
 Sinon, nous appelons la fonction handler,qui devrait s'occuper d'afficher la valeur à sa manière.
*/

//si le "handler" de la gestion des fonction void du menu et "vide"
      if(menu[currentMenuPos].handler != NULL)
        {
        (*(menu[currentMenuPos].handler))();
        }
      else
        {
        oled.print(tempVal);
        }
    }
  else   /*----------------- Racine du menu ----------------*/  
    {
    oled.set1X();                               /*en-tête du menu principal*/
    sensorLocal();                              /*apl DHT22 local*/
    oled.setCursor(0, 2);                       /*position ligne du menu*/
    oled.print(menu[currentMenuPos].label);
    }
    
 }  //--------------- fin de drawMenu----------------------//

...

…la suite.

//--------------- fin de drawMenu----------------------//


//-----------------------appel appropriée en fonction des boutons-------------------------------//

void bpNEXT()  {                            /*BP NEXT --> droite */
     
    if(isInLowerLevel)
      {          /*Niveau dans le  menu, Si Niveau inférieur*/
      tempVal++;                  /*augmente la valeur et vérifi  dépassé la plage*/
      if(tempVal > menu[currentMenuPos].maxVal) tempVal = menu[currentMenuPos].maxVal;
      }
  else
      {
      currentMenuPos = (currentMenuPos + 1) % menuSize;
      //incrémente de +1 la Position actuelle du menu, suivant taille du menu
      }
   }

void bpPREV()  {                                    /*BP PRECEDENT  <-- gauche*/
     
    if(isInLowerLevel)          /*Niveau dans le  menu, Si Niveau inférieur*/
      {
      tempVal--;
    
      if(tempVal < menu[currentMenuPos].minVal) tempVal = menu[currentMenuPos].minVal;
      }
    else
      {
      currentMenuPos--;
      if(currentMenuPos < 0) currentMenuPos = menuSize - 1;
      }
  }

void bpBACK() {                                     /*BP BACK  retour d'un niveau*/              
    
    if(isInLowerLevel)
      {
      isInLowerLevel = false;
      }
  }

void bpOK() {                                        /*BP OK  exécute ou changement de niveau*/ 
                                    
    if(menu[currentMenuPos].handler != NULL && menu[currentMenuPos].maxVal <= menu[currentMenuPos].minVal) 
      {
      (*(menu[currentMenuPos].handler))();
      return;
      }
    if(isInLowerLevel)                          /*Niveau du menu*/
      {                           
      menu[currentMenuPos].currentVal = tempVal;  
 // a desactiver pour prise en compte de default_value du menu
     //  isInLowerLevel = false; //desactiver pour action OK sur TC
            stateOK = 3;  //prise en compte valeur ou action On/Off
      }
    else
      {
      tempVal = menu[currentMenuPos].currentVal;
      isInLowerLevel = true;
      }
  }

//-------------------Fonctions Void des menu utilisateur---------------------------------//
   
  void infomenu()
  {
  oled.println(" utiliser <-- ou -->");
  oled.println("pour selection valeur");
  oled.println("  OK pour activer");
  oled.println("   ^ retour Menu ");
  }

void readDHT()           //test pour multi  température
  {
  oled.print("chambre ");
  oled.print(currentTemperatur, 1);              
  oled.print("C ");
  oled.print(currentHumiditi, 1);
  oled.println("%");
      oled.print("sallon  ");
      oled.print(currentTemperatur, 1);              
      oled.print("C ");
      oled.print(currentHumiditi, 1);
      oled.println("%");
  }

void actionPump() {     /*simulation pompe*/
  
  oled.print(" On <---  ");   
  oled.print (digitalRead(LED));    /*retour (virtuel) d'état de la commande*/
  oled.println("  ---> Off");
  //oled.setCursor(0, 4);
  oled.println("  ");
  
  switch (tempVal)
    {
    case 1 : oled.set2X();oled.print("    ON ");break;
    case 2 : oled.set2X();oled.print("    OFF");break;
    }

    if(tempVal == 1 && stateOK == 3 ){ digitalWrite(LED, HIGH);}
    if(tempVal == 2 && stateOK == 3){digitalWrite(LED, LOW);}
  }

void sensorLocal()   {         /*capteur DHT22 local*/
    
    oled.print("DHT ");
    oled.print(currentTemperatur, 1);              
    oled.print("'C  ");
    oled.print(currentHumiditi, 1);
    oled.println(" %");
    }
 
//----------------------------- Fin du Pgm------------------------------------//

des modifs ?? des amélioration possible ?
Merci