[Résolu] Problème de variables de classe

Bonjour à tous, depuis toujours passionné par la programmation (Basic, Pascal puis Python) et l'électronique (Plutôt de la logique), je découvre avec un immense plaisir le monde d'Arduino et j'ai déjà bien entamé quelques projets. Mais je me rends bien compte qu'il faut que je passe par les classes et les bibliothèques (plutôt bien assimilé en Python, mais ça n'a pas grand chose à voir, n'est-ce-pas ?).

Le projet actuel est un télérupteur-minuterie pour gérer 6 lampes extérieures indépendamment. Chaque lampe dans la classe Lamp gère sa minuterie, ses petits clignotements et ainsi de suite.

Seulement, il me faut une variable de classe, commune à toutes les lampes, que l'utilisateur puisse modifier (duree_minuterie) et que je puisse inscrire au besoin dans l'EEPROM.

class Lamp
{
   private:
    byte pin_lamp;
    //unsigned long date_to_stop;
    static int duree_minuterie;       // Durée de la minuterie en secondes : 3 minutes (180 s)
    
  public:
    Lamp(byte pin);
    static void set_duree_minuterie(int duree);
    static int get_duree_minuterie();
};

Lamp::Lamp(byte pin)
{
  pin_lamp = pin;
}

void Lamp::set_duree_minuterie(int duree)
{
  duree_minuterie = duree;
}

int Lamp::get_duree_minuterie()
{
  return duree_minuterie;
}

Ca compile sans problème, mais quand je fais un appel à set_duree_minuterie dans le corps du programme, il me renvoie : référence indéfinie vers « Lamp::duree_minuterie »

Lamp lampe1(5);
lampe1.set_duree_minuterie(321);

Le mot-clé STATIC n'est pas clair, j'ai lu que c'était pour une variable locale mais qui resterait en mémoire même entre deux appels d'une fonction. Après j'ai crû comprendre que c'était utilisé pour les variables de Classe.

Vous savez comment ça marche, vous ?!

D'autre part, je voudrais bien pouvoir initialiser une seule fois cette variable avant le constructeur de la première lampe, et je ne comprends pas bien ce que je dois utiliser pour ça. Un constructeur ? Un begin ?

Pffouuu, je ne sais même pas quel langage je dois prendre comme référence, le C, le C++ (pour les classes, c'est ça ?)

Merci de m'avoir lu, et de m'apporter quelques éclaircissements si possible ..
Antoine

Bonjour,

Si tu déclare une variable de classe static, ça veut dire qu'elle est commune à toutes les instances de la classe. Elle doit don être définie à l'extérieur de la classe.
Ajouteint Lamp::duree_minuterie;      // Durée de la minuterie en secondes : 3 minutes (180 s)après ta définition de class.

J'ajoute que ta variable n'a aucune raison d'être static puisque je suppose que chaque lamp va avoir sa propre minuterie.

Ok, merci beaucoup, ça fonctionne. Ma variable a bien besoin d'être statique car c'est la duree par défaut que vont utiliser toutes les lampes.

D'autre part, où puis-je initialiser cette classe (cette variable en particulier) autre part que dans le constructeur d'une instance ? Je suppose qu'il peut y avoir un constructeur de classe ..

Dernière chose pour me rendre plus autonome, est-ce que je peux me fier au code C++ pour l'Arduino ou tout n'est pas compatible ? Il n'y a pas tant de documentation que ça pour la progra pure sur l'Arduino, enfin si, en anglais ..

Merci encore, j'espère pouvoir participer bientôt ..!

Bonjour,

Ma variable a bien besoin d'être statique car c'est la duree par défaut que vont utiliser toutes les lampes.

Il y a un petite erreur dans la conception à mon sens :
Chaque lampe doit avoir sa durée propre.
Ce qui est commun à toutes les lampes, c'est la durée par défaut. Mais cette valeur n'existe que tant que l'on a pas assigné de valeur particulière à une instance.

Dès lors, le mieux est de définir la variable comme étant non statique, c'est-à-dire qu'il existe une valeur pour chaque instance.
Et dans le constructeur, tu peux assigner une valeur par défaut à cette variable.

Ainsi, chaque instance aura sa valeur de minuterie, mais à la création, toutes les valeurs seront identiques.

Coyotte

Oui, c'est bien ça, il y a une valeur par défaut pour toutes les lampes, et chaque lampe ou groupe de lampe la récupère au départ d'une minuterie pour calculer la date où s'arrêter (je compare millis() et cette date). Je trouverais ça plus élégant que cette variable soit commune à toutes, de manière statique, d'autant plus qu'il y en a une ou deux autres à mettre en place. Après, je peux bidouiller en mettant une variable pour chaque lampe avec la même valeur que les autres, mais ça me faiche, programmatiquement parlant !

Est-ce que je peux initialiser une variable static avant la première instanciation ?

Re,

Il y a un truc que je ne comprends pas bien : La durée de la minuterie est la même pour toutes les lampes ?
Dans ce cas, ton approche est bonne.

Coyotte

Oui si c'est une variable statique tu peux l'initialiser comme les variables qui n'appartiennent pas à une class

int Lamp::duree_minuterie=180;       // Durée de la minuterie en secondes : 3 minutes (180 s)

Merci beaucoup, Kamille et Coyotte, j'ai pu tester le local et le global, est-ce que chaque instance utilise bien les mêmes fonctions (je veux dire sans prendre plus de mémoire) que les autres ?

Et surtout, savez-vous comment je peux initialiser cette classe (donc ces variables globales) AVANT la première instanciation (ou pendant, mais ça ne devra se faire qu'une fois) ? En fait, au démarrage de l'Arduino, je veux lire quelques variables dans l'EEPROM et les injecter dans cette Classe, au départ, donc, vous suivez ? Il s'agira de la durée de la minuterie reglée par l'utilisateur final, et quelques autres réglages.

Merci à tous, bonne soirée

Pardon, Kamill !

Oui, chaque instance utilise les mêmes fonctions donc ne prend pas de mémoire programme supplémentaire. Ce qui prend de la mémoire (RAM) supplémentaire ce sont les variables non statiques dans la classe car elles sont dupliquées à chaque instance.

Il n'est pas possible l'initialiser les variables dynamiquement avant la première instanciation (à moins de faire une initialisation externe avant une instanciation dynamique).

Pour faire ce que tu veux faire, il faut le faire dans le constructeur de la première instance.
Pour cela une technique est d'avoir une variable statique flagInit qui est à faux au départ. Dans le constructeur tu testes ce flag s'il est à faux tu fais les initialisation et tu le mets à vrai, s'il est à vrai c'est que les initialisations ont déjà été faites.

atoide:
Pardon, Kamill !

Pourquoi?

Parce que j'avais écorché ton nom, simplement !
Bonne soirée, je vais essayer d'avancer sur ces questions.

Ah oui, question non répondue : En quel langage puis-je avoir confiance pour programmer sur l'Arduino, histoire de pouvoir élargir ma gamme de recherche ?
Le C est tout bon ? Le C++ pas tout ?

On utilise MAIN sur l'Arduino ?

Bon allez, merci encore, à bientôt

C'est les deux du C et du C++.

En fait le compilo s'occupe de tout remettre correctement dans la fonction main().

Setup() et loop() sont 2 fonctions void, la setup() est appelée une fois puis la loop() est placé dans une boucle while(1).

Ah oui d'accord, merci .. C'est chaud de gérer ce langage amoindri ... Est-ce qu'il y a une documentation un peu complète quelque part ? Ou un livre qui aille un peu loin ?

Là je mate la complète d'OpenClassroom sur le C++, mais plein de choses de marchent pas, au moins ça donne du vocabulaire !

Bonne soirée

Bonjour, j’ai enfin réussi, grâce à votre aide, à me dépatouiller pour ces histoires de variables de classe. Voici un petit résumé codé, c’est peut-être tout bête, avec encore plein de débilités (n’hésitez pas à me les faire remarquer …), mais chu vachement content ! Après, y’aura plus qu’à mettre ça dans une bibli…

Merci encore

#define VERSION "Essai d'utilisation de variables de Classe - STATIC - atoide 03-17"

// Le code est destiné à un multirupteur-minuterie, avec différentes zones
// comprenant poussoirs individuels et lampes individuelles.
// Un appui bref sur un poussoir met en marche ou éteint la minuterie dans sa zone.
// Un appui long parcourt différentes configurations lumineuses après chaque seconde.

// Il est prévu de pouvoir changer la durée de la minuterie par l'utilisateur, durée qui sera ensuite mémorisée
// dans l'EEPROM de l'Arduino, et devra etre récupérée en cas de reset pour etre transmise en variable de classe
// avant l'instanciation des lampes.

class Lamp
{
   private:
    byte pin_lamp;
    static int duree_minuterie;     // Durée de la minuterie pour toutes les lampes
    static byte lamp_count;         // Compteur pour le nombre de lampes instanciées
    
  public:
    Lamp();                                      // Constructeur
    void init(byte pin);                         // Initialisation de la lampe
    byte get_pin();
    static void set_duree_minuterie(int duree);  // Fonction statique lui permettant d'etre appelée hors instance
    static byte get_count();                     //     ''      ''      ''      ''
    int get_duree_minuterie();
};

int  Lamp::duree_minuterie;        // Les variables statiques doivent etre
byte Lamp::lamp_count=0;           // re-déclarées en dehors de la Classe (Declaré n'est surement pas le bon terme)

Lamp::Lamp()
{
  lamp_count++;                    // Le compteur est incrémenté à chaque nouvel objet
}                                  // Surement faudrait-il le décrémenter dans un destructeur ..!

void Lamp::init(byte pin)
{
  pin_lamp = pin;
}

byte Lamp::get_pin()
{
  return pin_lamp;
}

int Lamp::get_duree_minuterie()
{
  return duree_minuterie;
}

void Lamp::set_duree_minuterie(int duree)
{
  duree_minuterie = duree;
}

byte Lamp::get_count()
{
  return lamp_count;
}

void setup()                                      // ********* Exemple concret ! **********
{
  
  Lamp::set_duree_minuterie(180);                 // Initialisation d'une variable de classe à l'aide d'une fonction statique 
                                                  // avant de créer le moindre objet
  Serial.begin(9600);
  Serial.print("Nombre de lampes ");
  Serial.println(Lamp::get_count());              // Fonction statique
  
  Lamp ampoule;                                   // Création d'un 1er objet 
  ampoule.init(2);                                // Initialisation, --> Pin 2
  Serial.print("Pin No ");
  Serial.println(ampoule.get_pin());
  Serial.print("Nombre de lampes ");
  Serial.println(ampoule.get_count());            // Equivalent à Lamp::get_count()
  Serial.print("Duree minuterie : ");
  Serial.println(ampoule.get_duree_minuterie());
  
  Lamp lampe[6];                                  // Puis création d'un tableau de 6 lampes
  Serial.print("Nombre de lampes ");
  Serial.println(lampe[0].get_count());           // Les lampes du tableau ne sont pas encore initialisées mais on accède déjà
                                                  // au compteur statique, également équivalent à Lamp::get_count()
  for (int n=0;n<6;n++)
  {
    lampe[n].init(n+3);                           // Init de chaque lampe, la 1ère commence au Pin D3
    Serial.print("Pin No ");
    Serial.println(lampe[n].get_pin());
  }
}

void loop()
{
}

Le problème que je vois dans ton code c'est que tes variables Lamp sont définies comme variables automatiques dans le setup(), donc tu ne pourras pas y accéder en dehors du setup (en fait elles n'existeront même plus).

Bonsoir, merci Kamill de m'avoir fait remarqué cette grossière erreur, pas remarqué vu que j'avais rien écrit dans loop(). J'ai eu peur de me retrouver devant un serpent qui se mord la queue.
Heureusement j'ai pu instancier les lampes avant le setup(), puis dans le setup() initialiser la variable de Classe et enfin les lampes .
Tout ce beau monde est maintenant accessible depuis loop();
Mais quand même, en Python il me semble qu'il n'y avait strictement rien de global, j'instanciais un objet puis je baladais sa référence de fonction en fonction. C'est comme ça qu'il faudrait faire, là ? Avec des pointeurs ? C'est gênant de ne pouvoir initialiser une Classe avant le setup() et de ne pas pouvoir instancier dedans sous peine de ne pas pouvoir accèder à ses objets.

Je me mets doucement au C++ par la force des choses, mais ça paraît tout rigide après le Python !

Tu peux initialiser les variables statiques après l'instanciation de la class si elles ne sont pas utilisées dans le constructeur.

Oui, c'est ce que j'ai fait, du coup j'ajoute un init(... ...) pour compléter le constructeur si besoin de variable statique. Dommage de ne pouvoir le faire au moment de l'instanciation !