Affichage "externe" dans une librairie

Bonjour

Je dispose d'un capteur dont tout le travail se fait dans la librairie (ici)
A plusieurs reprises, lorsqu'on lance le programme, des affichages sont envoyés sur le moniteur série.
En ce qui me concerne, j'aimerai pouvoir faire afficher sur un écran lcd : j'ai essayé de prendre la même procédure que si on l'utilisait dans le corps du programme mais cela ne fonctionne pas.

Est-ce possible et si oui comment puis-je procéder ?
Merci

Pour développer, vous avez toujours le moniteur série.
Pour rajouter le lcd, je le fais en 3 étapes
a) je fais tourner un exemple de la librairie (écrire "bonjour") après avoir vérifié plutôt 2 fois qu'une mon câblage.
b) je garde les affichages série qui font moins de 16-20 caractères -risque de débordement de buffers- , et je les doublonne si nécessaire par un affichage sur lcd. (le "Serial.println " se traduit par "aller à la gauche de la ligne)
c) je regarde les affichages sur lcd qui génèrent un clignotement ou qui sont trrop rapides (un homme ne peut pas les lire confortablement) et je simplifie encore.
PS: -post scriptum, sans contre façon- :
Si vous donniez un bout de code qui ne marche pas, ça serait plus facile de chercher plus loin que des généralités.

Bonjour

Mon problème n'est pas d'utiliser le lcd, ça j'y arrive dans un programme "normal".
Dans mon cas, c'est dans la blibliothèque (fichier DFRobot_PH.cpp) que l'affichage est programmé. Par exemple,

/*
 * file DFRobot_PH.cpp * @ https://github.com/DFRobot/DFRobot_PH
 *
 * Arduino library for Gravity: Analog pH Sensor / Meter Kit V2, SKU: SEN0161-V2
 *
 * Copyright   [DFRobot](http://www.dfrobot.com), 2018
 * Copyright   GNU Lesser General Public License
 *
 * version  V1.0
 * date  2018-04
 */


#if ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif

#include "DFRobot_PH.h"
#include <EEPROM.h>


#define EEPROM_write(address, p) {int i = 0; byte *pp = (byte*)&(p);for(; i < sizeof(p); i++) EEPROM.write(address+i, pp[i]);}
#define EEPROM_read(address, p)  {int i = 0; byte *pp = (byte*)&(p);for(; i < sizeof(p); i++) pp[i]=EEPROM.read(address+i);}

#define PHVALUEADDR 0x00    //the start address of the pH calibration parameters stored in the EEPROM


DFRobot_PH::DFRobot_PH()
{
    this->_temperature    = 25.0;
    this->_phValue        = 7.0;
    this->_acidVoltage    = 2032.44;    //buffer solution 4.0 at 25C
    this->_neutralVoltage = 1500.0;     //buffer solution 7.0 at 25C
    this->_voltage        = 1500.0;
}

DFRobot_PH::~DFRobot_PH()
{

}

void DFRobot_PH::begin()
{
    EEPROM_read(PHVALUEADDR, this->_neutralVoltage);  //load the neutral (pH = 7.0)voltage of the pH board from the EEPROM
    //Serial.print("_neutralVoltage:");
    //Serial.println(this->_neutralVoltage);
    if(EEPROM.read(PHVALUEADDR)==0xFF && EEPROM.read(PHVALUEADDR+1)==0xFF && EEPROM.read(PHVALUEADDR+2)==0xFF && EEPROM.read(PHVALUEADDR+3)==0xFF){
        this->_neutralVoltage = 1500.0;  // new EEPROM, write typical voltage
        EEPROM_write(PHVALUEADDR, this->_neutralVoltage);
    }
    EEPROM_read(PHVALUEADDR+4, this->_acidVoltage);//load the acid (pH = 4.0) voltage of the pH board from the EEPROM
    //Serial.print("_acidVoltage:");
    //Serial.println(this->_acidVoltage);
    if(EEPROM.read(PHVALUEADDR+4)==0xFF && EEPROM.read(PHVALUEADDR+5)==0xFF && EEPROM.read(PHVALUEADDR+6)==0xFF && EEPROM.read(PHVALUEADDR+7)==0xFF){
        this->_acidVoltage = 2032.44;  // new EEPROM, write typical voltage
        EEPROM_write(PHVALUEADDR+4, this->_acidVoltage);
    }
	Serial.println("Appuyer ETALON  ");
	Serial.println("pour etalonnage ");
	Serial.println("ou attendre 10 s");
	Serial.println("pour mesurer pH ");
	delay(10);
}

J'aimerai que les dernières lignes, au lieu d'être visualisées dans le moniteur série, le soient sur l'écran LCD.

Déjà, sauf si vous avez un afficheur à 4 lignes, c'est impossible (chacune des lignes a moins de 16 caractères: ça tient en largeur). essayez d'être plus concis ("appui ETALON ou attente 10s" ? tient sur 2 lignes).

Ensuite, votre delay(10) est très bien si les lignes défilent... Je doute qu'un oeil réagisse en 1/100 seconde: si vous affichez quelque chose immédiatement après ce message, il sera perdu)

J'utilise un afficheur 4x20 (celui-ci en l'occurrence) : c'est pour cette raison que j'ai mis en forme de cette manière.

Le delay de 10 était pour un test.

Pour un test, remplacer le delay(10) par while(1) qui vous laissera le temps de voir venir et de contempler...
Ensuite, je suis allé voir comment positionner le curseur dans https://www.arduino.cc/en/Tutorial/LiquidCrystalSetCursor
Ce qui me donne le code :

lcd.clr();
lcd.setCursor(0, 0); // colonne 0, ligne ..0
lcd.print("Appuyer ETALON  ");
lcd.setCursor(0, 1); // colonne 0, ligne 1 
	lcd.println("pour etalonnage ");
lcd.setCursor(0, 2); // colonne 0, ligne 2 
	lcd.println("ou attendre 10 s");

lcd.setCursor(0, 3); // colonne 0, ligne 1 
	lcd.println("pour mesurer pH ");
while(1==1);

Cela me donne une erreur lors de la compilation "error: 'lcd' was not declared in this scope" car je n'ai pas déclaré lcd sauf que je ne sais pas comment déclarer cette variable dans ma librairie
j'ai essayé de rajouter les lignes dans le fichier DFRobot_PH.cpp

#include<LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 20, 4);

pour créer l'objet lcd mais j'ai la même erreur

Verifiez que les 8 lignes que je vous ai passées fonctionnent dans un test autonome, sans autre librairie que Lcd_I2C

Mettez (pour essai) vos deux lignes avant l'inclusion de DFRobot_PH.h, dans votre programme (un *.ino, pas un *.cpp ) : je sais que ce n'est pas beau, mais c'est le plus rapide.

... ou mettez les 8 lignes que je vosu ai passées dans le setup() (ne complexifie pas une librairie prééxistante; si vous passez à des afficheurs graphiques, ce sera plus simple)

J'avais bien réussi au préalable à faire fonctionner l'écran lcd.
Je vais tester la dernière méthode

Bonjour:
la première méthode a peu de chances de marcher: vous avez raison de tester la seconde .
Tous les affichages étant en dehors de votre librairie, et les modes d'affichage sont très variés (radio, ouaibe, liason serie, lcd, TFT): si on met des affichages dans une librairie, sa complexité ne peut que croître dramatiquement au cours du temps.
A noter que votre menu de démarrage peut être mis sur une feuille de papier, évitant du logiciel, et peut être plus agréable à lire qu'un lcd.

Bonjour

En passant dans le setup, j'arrive bien à écrire les premières lignes : il est vrai qu'elles ne sont pas intimement liées à la librairie, elles ne sont que dans la partie d'initialisation
Par contre, dans la librairie, il y a du texte qui s'affiche (et que j'aimerais pouvoir faire apparaître sur mon écran lcd) que je ne peux pas écrire dans le programme car le texte dépend d'une commande dans la librairie.
Je copie la partie correspondante

byte DFRobot_PH::cmdParse(const char* cmd)
{
    byte modeIndex = 0;
    if(strstr(cmd, "ENTERPH")      != NULL){
        modeIndex = 1;
    }else if(strstr(cmd, "EXITPH") != NULL){
        modeIndex = 3;
    }else if(strstr(cmd, "CALPH")  != NULL){
        modeIndex = 2;
    }
    return modeIndex;
}

byte DFRobot_PH::cmdParse()
{
    byte modeIndex = 0;
    if(strstr(this->_cmdReceivedBuffer, "ENTERPH")      != NULL){
        modeIndex = 1;
    }else if(strstr(this->_cmdReceivedBuffer, "EXITPH") != NULL){
        modeIndex = 3;
    }else if(strstr(this->_cmdReceivedBuffer, "CALPH")  != NULL){
        modeIndex = 2;
    }
    return modeIndex;
}

void DFRobot_PH::phCalibration(byte mode)
{
    char *receivedBufferPtr;
    static boolean phCalibrationFinish  = 0;
    static boolean enterCalibrationFlag = 0;
    switch(mode){
        case 0:
        if(enterCalibrationFlag){
            Serial.println(F(">>>Command Error<<<"));
        }
        break;

        case 1:
        enterCalibrationFlag = 1;
        phCalibrationFinish  = 0;
        Serial.println();
        Serial.println(F(">>>Mode étalonnage du pHmètre<<<"));
        Serial.println(F(">>>Mettre la sonde de pH dans la solution tampon 4.0 ou 7.0<<<"));
	Serial.println(F(">>>Attendre quelques mesures de pH"));
	Serial.println(F(">>>Puis saisir CALPH<<<"));
	Serial.println(F(">>>pour annuler saisir EXITPH<<<"));
        Serial.println();
        break;

        case 2:
        if(enterCalibrationFlag){
            if((this->_voltage>1322)&&(this->_voltage<1678)){        // buffer solution:7.0{
                Serial.println();
                Serial.print(F(">>>Solution tampon : 7.0"));
                this->_neutralVoltage =  this->_voltage;
                Serial.println(F(",Taper EXITPH pour sauvegarder et quitter<<<"));
                Serial.println();
                phCalibrationFinish = 1;
            }else if((this->_voltage>1854)&&(this->_voltage<2210)){  //buffer solution:4.0
                Serial.println();
                Serial.print(F(">>>Solution tampon : 4.0"));
                this->_acidVoltage =  this->_voltage;
                Serial.println(F(",Taper EXITPH pour sauvegarder et quitter<<<<<<")); 
                Serial.println();
                phCalibrationFinish = 1;
            }else{
                Serial.println();
                Serial.print(F(">>>Changer de solution tampon<<<"));
                Serial.println();                                    // not buffer solution or faulty operation
                phCalibrationFinish = 0;
            }
        }
        break;

        case 3:
        if(enterCalibrationFlag){
            Serial.println();
            if(phCalibrationFinish){
                if((this->_voltage>1322)&&(this->_voltage<1678)){
                    EEPROM_write(PHVALUEADDR, this->_neutralVoltage);
                }else if((this->_voltage>1854)&&(this->_voltage<2210)){
                    EEPROM_write(PHVALUEADDR+4, this->_acidVoltage);
                }
                Serial.print(F(">>>Etalonnage réussi"));
            }else{
                Serial.print(F(">>>Echec de l'étalonnage Failed"));
            }
            Serial.println(F(",Exit PH Calibration Mode<<<"));
            Serial.println();
            phCalibrationFinish  = 0;
            enterCalibrationFlag = 0;
        }
        break;
    }

Bien sûr, je corrigerai plus tard les lignes pour qu'elles rentrent en taille sur l'écran

Vous pourriez modifier la librairie pour rendre dispo les messages dans un « callback » vers votre programme

J-M-L:
Vous pourriez modifier la librairie pour rendre dispo les messages dans un « callback » vers votre programme

Comment on ajoute un "callback" vers le programme ?

Une fonction C (ou Fortran, depuis les années 70...) a la possibilité d'avoir en argument une autre (ou plusieurs autres) fonction(s) C. C'est assez tordu, mais, lors de l'invocation de la calibration, si vous avez fabriqué une liste de fonctions qui gèrent le dialogue suivant votre configuration (avez vous une liaison serie? un LCD? un lcd sur I2C) , ce peut être une solution brillante. Cela implique de modifier l'appel de (au moins) phCalibration ... et son contenu... (la dernière fois que j'ai joué avec des callback fonctions en c++ , c'était il y a un an et je ne me souviens pas comment faire.....)

dbrion06:
Uavez vous une liaison serie? un LCD? un lcd sur I2C)

Mon écran LCD est branché en I2C

nlb:
Comment on ajoute un "callback" vers le programme ?

voilà un petit exemple tout simpliste:

class MaClasse
{
  public:
    void (*callBack)(const char*) = NULL; // variable de type pointeur sur fonction

    void definirCallback(void (*pointeurFonction)(const char*))
    {
      callBack = pointeurFonction;
    }

    void disCoucou()
    {
      Serial.println("Coucou depuis le code de l'instance");
      if (callBack != NULL) (*callBack)("Coucou"); // on envoie aussi le message ailleurs en dehors de la classe 
    }
};


MaClasse uneInstance;

void maFonctionAffichage(const char* message)
{
  Serial.print(message);
  Serial.println(" depuis le code du programme principal");
}

void setup()
{
  Serial.begin(115200);
  uneInstance.definirCallback(maFonctionAffichage); // on affecte le callback
  uneInstance.disCoucou(); // ici on appelle une fonction de la librairie qui va nous rappeler 
}

void loop() {}

la classe est supposée représenter votre libraire dont on crée une instance.

j'ai rajouté dans la classe une variable de type pointeur sur fonction qui prend une cString en paramètre et qui ne retourne rien

je déclare une fonction qui a la même signature dans mon code principal maFonctionAffichage()

dans le setup() j'affecte cette fonction en tant que call back à mon instance

et quand j'appelle une des méthodes de la "librairie" (classe) qui est bavarde (disCoucou()) cette méthode après avoir écrit sur la console déclenche le callback en passant le même texte.

maFonctionAffichage() est donc appelée et c'est là que vous pourriez afficher sur le LCD (ici je ne fais qu'imprimer le message en précisant que c'est depuis le programme principal).

je n'ai pas testé le code, mais si je ne me suis pas trompé vous devriez voir dans la console

[color=purple]Coucou depuis le code de l'instance
Coucou depuis le code du programme principal
[/color]

c'est clair ?

L'interet d'avoir une batterie de fonctions qui gerent divers afficheurs est de repondre à ces questions (la reponse est triviale aujourd'hui, mais peut être pas dans 6 mois).
Pour le moment, j'hesite entre réécrire une fonction de calibration (si le dialogue avec l'utilisateur est satisfaisant), adapté à I2CLCD ou faire appel à une ou plusieurs fonctions d'affichage (pour une évolution, c'est plus propre), recommandé par JML.

Edité: je vois que JML a donné un exemple, qui ne me choque pas du tout....
le point qui me gène est le fait que les structures de texte ne sont pas tout à fait les mêmes avec un LCD (texte court) ou un ecran PC sur liaison serie.

dbrion06:
le point qui me gène est le fait que les structures de texte ne sont pas tout à fait les mêmes avec un LCD (texte court) ou un ecran PC sur liaison serie.

Oui l'idée c'est que la librairie devrait simplement donner une information générique dans le callback, charge au programme principal de présenter cela correctement.

En pratique la librairie ne devrait pas envoyer de texte ce qui est un gâchis de ressource et pas adapté à la localisation dans une autre langue --> elle devrait retourner un N° d'erreur ou N° d'état (définis dans un enum) qui serait ensuite interprété dans l'application appelante.

les callback c'est pratique quand on veut notifier un code externe en cours d'execution d'une méthode du genre

int faireQuelqueChose(int x)
{
  (*callback_JevaisCommencerLeCalul)();
  var = 3*var + x;
  (*callback_JaiFiniLeCalul)();
  return var;
}

Une autre option serait d’avoir une variable d’instance qui pointe vers un objet afficheur qui « respecte » Print.h - un stream par exemple et donc envoyer tous les affichages vers cet objet qui pourrait être un fichier, un software ou hardware Serial ou un LCD par exemple ou un objet spécifique crée a façon.

Bien sûr charge à cet objet de gérer proprement son affichage

J'avoue ne pas tout comprendre : toute la programmation se fait dans le programme, pas dans la librairie ?

avec l'exemple donné plus haut, vous rajoutez la variable de type "pointeur sur fonction" et la méthode qui permet de l'initialiser dans la librairie.

puis dans les fonctions "bavardes" de la librairie, à coté des Serial.print("toto") vous rajoutez

      if (callBack != NULL) (*callBack)("toto"); // on envoie aussi le message ailleurs en dehors de la classe

de cette façon à chaque fois qu'un texte est imprimé par la librairie, il est aussi envoyé à votre programme que vous contrôlez

Sinon si vous voulez modifier la librairie pour qu'elle soit adaptée à vos besoins, elle a besoin d'un pointeur sur le LCD, qu'il faudra donc déclarer en variable d'instance et rajouter une méthode pour l'affecter puis ensuite vous modifiez tous les Serial.print pour mettre à la place des lcd->print (plus gestion du curseur)