Séparer un gros code en plusieurs fichiers

Salut les amis !

Pour mon projet actuel, un contrôleur de prises connectées Sonoff S26 via internet, j'ai décidé de faire les choses proprement pour une fois : séparer le fichier ino en plusieurs fichiers cpp avec les fonctions dedans, et ajouter un fichier .h avec les déclarations en extern des variables globales et les prototypes des fonctions de mes fichiers cpp.

Mal m'en a pris, car je galère dessus depuis hier, grave (comme dit ma fille)...

J'ai 2 problèmes principaux à la compilation mais pas mal de questions en plus :

1/ D'abord les prototypes ne font rien : le compilo (ou le linker) me dit qu'il ne trouve pas mes fonctions. Exemple :

C:\Users\irena\Documents\Arduino\Client_simple_final\Client_simple_final.ino: In function 'void loop()':

Client_simple_final:136: error: 'setTimesBE' was not declared in this scope

if (timeinfo.tm_hour + timeinfo.tm_min == 0) setTimesBE ();

Alors que j'ai bien dans le fichier .h :

void setTimesBE ();

Tous mes fichiers .cpp et le .ino débutent par le include de mon fichier .h

#include "client.h"

2/ Ensuite j'utilise une struct que j'ai définie dans le fichier .h et j'en déclare une à la ligne suivante :

typedef struct {
  byte device;
  byte days : 7;  // binary 0bSMTWTFS (Sunday, Monday, Tuesday ... Saturday) - use 7 bits
  byte hourBegin;  // 0 ... 23
  byte minBegin;   // 0 ... 59
  byte hourEnd;
  byte minEnd;
  byte randomMin;  // randomly add / decrease begin and end times of xx minutes
} TimeSlot ;
TimeSlot Slot[];

Puis je l'initie dans un fichier cpp (pas dans le ino, car c'est un paramètre que j'aimerais rendre facilement accessible à un utilisateur), mais le compilateur ne la connait pas :

C:\Users\irena\Documents\Arduino\Client_simple_final\Client_simple_final.ino: In function 'void verifyDevices(int)':

Client_simple_final:149: error: 'Slot' was not declared in this scope

byte device = Slot*.device;[/quote]Ce qui est bizarre c'est que maligne 149 n'est pas comme le dit le compilateurn c'est en vérité*
* *byte device = Slot[i].device;* *
Si vous pouvez m'aider à corriger ça, ce sera déjà un grand pas en avant pour moi.
A part ça, j'utilise des #define pour définir des constantes (tailles de tableaux par exemple ou numéro de pin). J'ai remarqué que si je les mets dans le .h elles ne sont pas connues du programme alors que si je les mets dans le ino ça marche. C'est normal ? Peut-on faire autrement ? Mon but est de simplifier le ino au maximum...
Pour moi, l'idéal (mais peut-on atteindre l'idéal dans notre monde ? C'est le sujet de philo du bac 2019, je relève les copies dans 4 heures :slight_smile: ) serait d'avoir un fichier spécifique (extension .h ou autre) dans lequel je mettrais tous les paramètres utilisateur : ssid, password, nombre de prises, nom des prises, définition du contenu de mon tableau de structure, etc. Est-ce possible ?
Merci de votre aide.

Bonjour lesept,

cela me ferai plaisir de t'aider. Fait peut-être un zip de ton projet et je le compile chez moi.

Et à ta question la réponse est oui ;-). C'est bien que tu passes à la compilation modulaire, c'est comme cela que le noyau Linux est compilé alors crois moi ce n'est pas quelques malheureux fichiers qui vont poser problèmes :wink:

Voici le zip... Merci ! :slight_smile:

Client_simple_final.zip (7.9 KB)

Salut @lesept, éventuellement jetez un oeil sur un petit exemple simple d'organisation de code que j'avais posté dans une discussion récente

ça devrait vous donner des idées sur la structuration.

il faut aussi bien comprendre la différence entre "déclaration" et "définition".

Déclarer une variable ou une fonction, c'est dire au compilateur "je vais utiliser ce truc, voilà son type ou la forme que ça prend. Tu ne le connais pas encore mais t'en fais pas fais moi confiance ça existe par ailleurs". Le compilateur alors vous fait confiance et remet à plus tard (au link) la mise ensemble des différents bouts. On fait cela en utilisant le mot clé extern pour dire que ce sera défini ailleurs

Définir une fonction ou une variable, c'est vraiment allouer la mémoire / le code qui qui est nécessaire à son usage. Bien sûr on ne peut avoir qu'une seule définition.

par exemple dans votre code, je vois dans client.hTimeSlot Slot[];On est bien d'accord que ça veut dire que vous demandez de définir la variable de type tableau Slot mais vous ne donnez pas la taille. et ensuite dans parameters.cpp vous mettez à nouveau une définition mais ce coup ci correcte.

TimeSlot Slot[] = {
  /* Salon, every days, from 7:10 to 8:00, no random */
  {Salon, 0b1111111, 7, 10, 8, 0, 0},
  /* Salon, saturday & sunday, from 21:45 to 23:15, no random */
  {Salon, 0b1000001, 21, 45, 23, 15, 0},
  /* Salon, saturday & sunday, from 23:45 to 00:15, no random */
  {Salon, 0b1000001, 23, 45, 0, 15, 0},
  /* Salon, week days, from 22:30 to 23:55, no random */
  {Salon, 0b0111110, 22, 30, 23, 55, 0},
  /* Salon, -MTW---, from 00:15 to 01:05, no random */
  {Salon, 0b0111000, 0, 15, 1, 5, 0},
  /*
    Cuisine */
  {Cuisine, 0b1111111, 6, 55, 7, 40, 0},
  {Cuisine, 0b1000001, 19, 55, 20, 15, 0},
  {Cuisine, 0b0111110, 19, 30, 21, 10, 0}
};

Le compilo va en perdre son latin... Dans le client.h il faudrait avoir extern pour que ce ne soit qu'une déclaration, la définition (unique) se faisant dans parameters.cpp (ce qui n'est pas terrible en soi - autant le mettre en début de .ino)

Je t'en prie, et puis j'espère ne pas avoir vendu la peau de l'ours avant de l'avoir tué :wink:

Bon sinon je suis en train de virer le corps de toutes tes fonctions histoire de voir plus clair car il y a du code :wink:

Merci J-M-L, je me souvenais de cette conversation mais impossible de remettre la souris dessus...

lesept:
Merci J-M-L, je me souvenais de cette conversation mais impossible de remettre la souris dessus...

j'ai rajouté un peu d'explications ci dessus

Oui, merci
En parcourant ton post, j'ai remarqué que j'avais oublié le extern devant la déclaration de Slot : j'essaye donc avec

typedef struct {
  byte device;
  byte days : 7;  // binary 0bSMTWTFS (Sunday, Monday, Tuesday ... Saturday) - use 7 bits
  byte hourBegin;  // 0 ... 23
  byte minBegin;   // 0 ... 59
  byte hourEnd;
  byte minEnd;
  byte randomMin;  // randomly add / decrease begin and end times of xx minutes
} TimeSlot ;
extern TimeSlot Slot[];

mais j'ai toujours les mêmes erreurs de compilation...

Fidèle à ma devise, j'ai aussi essayé de mettre extern devant le typedef : pas mieux.

Enfin j'ai ajouté extern devant chaque prototype ... pareil. Je songe à me mettre au tricot...

Bonjour,

client_h semble défini, ce qui empêche d'inclure client.h

Au lieu d'utiliser un #define pour empêcher plusieurs inclusions tu peux utiliser

#pragma once

Je continu a décrire ce que je fais, la démarche peut-être critiquable, et surtout moins efficace que d'autres :wink:

Là je suis un peu embêter avec des points h externes que je ne possède pas, rien de grave, je vire :

#include <WebServer.h>, #include <WebServer.h>, ...

Entre temps, j'ai je pense compris ton découpage et ce qu'on appelle le graphe de dépendance entre tes fichiers (qui à besoin de qui). Ici client .h est central.

OK, j'avance aussi de mon côté.

@Kamill : merci, le pragma corrige les problèmes des prototypes.

J'ai déplacé la déclaration de l'écran dans le fichier ino avec la bibli u8g2lib.
Il me reste juste une erreur dans le setup :

Client_simple_final:63: error: invalid application of 'sizeof' to incomplete type 'TimeSlot []'

numberOfSlots = sizeof(Slot) / sizeof(TimeSlot);

J'ai déclaré ça dans le fichier .h :

typedef struct {
  byte device;
  byte days : 7;  // binary 0bSMTWTFS (Sunday, Monday, Tuesday ... Saturday) - use 7 bits
  byte hourBegin;  // 0 ... 23
  byte minBegin;   // 0 ... 59
  byte hourEnd;
  byte minEnd;
  byte randomMin;  // randomly add / decrease begin and end times of xx minutes
} TimeSlot ;
extern TimeSlot Slot[];

et j'ai bien l'include au début du ino :

#include "client.h"

@supercc : c'est vrai que je compile pour un ESP32, d'où ces bibliothèques exotiques...

Fidèle à ma devise, j'ai aussi essayé de mettre extern devant le typedef : pas mieux.

Ben non, votre type il faut le définir :slight_smile:

Je crois que tu as un petit problème avec :
enum state {OFF, ON, UNPLUG, TIMEOUT};
enum state deviceState[deviceNumber];

Le 2nd enum est de trop.

Ensuite j'arrive à un vrai problème de compilation modulaire.

./Client/client.h:31:1: error: 'state' does not name a type
state deviceState[deviceNumber];
^

client.h ne connaît pas l'enum state et c'est normal elle est définie dans le .ino

Pour passer ce cap je déplace ta definition de l'enum dans client.h. Le .ino la verra puisqu'il inclut le .h

@supercc : c'est vrai que je compile pour un ESP32, d'où ces bibliothèques exotiques...

Aucun soucis, se suis passé sur compilateur esp8266 pour ne pas avoir a virer le wifi mais l'écran je n'ai envie de télécharger les bibliothèques (et surtout a ce stade cela ne sert à rien). standards.

Pour sizeof, le compilateur doit connaitre la taille de TimeSlot[]

La solution est de mettre int numberOfSlots = sizeof(Slot) / sizeof(TimeSlot); dans Parameters.cpp et de déclarer numberOfSlots extern dans client.h

Idem :wink: : Un petit problème de compilation :

il faut donner la taille du tableau lors du :

TimeSlot Slot[];

Sans réfléchir j'ai mis 4 :wink: je cherche à ce que cela compile pas que cela marche :wink:

En recherchant client_h :

cores/esp32/Client.h:#ifndef client_h
cores/esp32/Client.h:#define client_h

Écris plutôt :

#ifndef _CLIENT_H_
#define _CLIENT_H_

Oui - ou trouver un nom plus spécifique pour le fichier et son define

Je poste mais c'est juste pour garder une trace je n'ai pas de question :wink:

En fait Slot est définit dans Parameters.cpp donc ma modif précédente n'est pas pertinente, j'ai mis au final dans client.h.

extern TimeSlot Slot[];

Pauvre le sept, on est tous sur ton dos :wink: