Envoi fichier texte: nécessité ou non d'une carte SD?

bonjour tout le monde,

je souhaite transmettre (depuis un client ftp/nanoesp32) un petit fichier texte comprenant la date et une variable numérique:
est-il possible de l'envoyer directement, ou bien faut-il créer le fichier, l'enregistrer sur une carte mémoire externe, puis l'envoyer?
Cordialement
b-g-jp

Bonjour

la carte SD n’est pas indispensable, le fichier peut être enregistré dans la mémoire Flash en utilisant l’un des systèmes de fichiers disponibles pour ESP32 : Spiffs ou LIttleFS, des exemples sont disponibles ainsi que des tutoriels

merci, avec ces indications je vais pouvoir avancer dans le bon sens

quelle bibliothèque FTP utilisez vous ? généralement on peut balancer directement un buffer mémoire, donc pas besoin de fichier du tout.

par exemple (avec une API plus ou moins proche de celle de votre bibliothèque)

  char buffer[128];
  snprintf(buffer, sizeof(buffer),
           "%04d-%02d-%02d %02d:%02d:%02d | Tension=%.2f V\n",
           annee, mois, jour, heure, minute, seconde, tension);

  ftp.OpenConnection(ftpServer, ftpUser, ftpPass, 21);
  ftp.AppendFile("/data/mesures.txt");
  ftp.WriteData((uint8_t*)buffer, strlen(buffer));
  ftp.CloseFile();
  ftp.CloseConnection();

ça rajouterait à la fin du fichier /data/mesures.txt la ligne fabriquée avec le buffer (date, heure, mesure de la tension)


PS: ne postez pas les questions techniques dans la catégorie "Le bar". J'ai déplacé votre post pour cette fois. Merci

je n'ai pas encore choisi de client ftp, domaine que je n'ai pas encore exploré, mais qui m'est nécessaire pour l'évolution d'une réalisation opérationnelle (envoi de valeurs d'un compteur par mail) vers un stockage des valeurs sur un serveur ftp externe
Je vais étudier votre suggestion

PS: merci d'avoir déplacé mon post, créé un peu trop vite dans celui des trois sous-forums le moins inadapté...

je suis bien sûr preneur de tout conseil concernant le client ftp, qui peut être basique

Je n’ai pas de reco particulière, je n’ai pas cette carte mais prenez une simple pour ESP32 ça devrait le faire

ok, je vais tester et rendrai compte
Belle soirée

j'ai essayé d'adapter les suggestions de J-M-L avec les bibliothèques utilisées dans mon projet:
Arduino, WiFi, WiFiClientSecure, SPIFFS, FTPClient et time, mais sans beaucoup de réussite: j'arrive bien à créer un fichier sur un serveur ftp avec un nom fixe, mais n'arrive pas à ajouter au nom la date, ni à écrire dans ce fichier la valeur du compteur - de coupures d'une barrière IR- que ce soit avec buffer, ou avec String (counter), qui produisent chaque fois des erreurs de compilation...
Bref, je patine... en rond, et suis preneur de toute piste

le programme en cours de test:
(merci d'avance pour toute idée)

#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <SPIFFS.h>
#include <FTPClient.h>
#include <time.h>

#define WIFI_SSID "xxxxxxxxxx"
#define WIFI_PASSWORD "xxxxxxxxxx"

#define FTP_SERVER_ADDRESS    "xxxxxxxxxx"
#define FTP_SERVER_PORT       21
#define FTP_USER_NAME         "xxxxxxxxxx"
#define FTP_USER_PASSWORD     "xxxxxxxxxx"

WiFiClientSecure ssl_client;

const int buttonPin = 2; // sortie du relais IR (état haut = faisceau coupé) 
const int resetPin = 3;
int counter = 0;
int buttonState = 0;      // état du relais IR (Haut ou Bas)
int lastButtonState = 0;  // dernier état du relais IR

boolean compteurAun = false;

const char* timezone = "CET-1CEST,M3.5.0,M10.5.0/3";

const int task1Hour = 20;
const int task1Minute = 0;
const int task2Hour = 20;
const int task2Minute = 30;
int lastRunDayTask1 = -1;
int lastRunDayTask2 = -1;

unsigned long lastNTPUpdate = 0;
const unsigned long ntpSyncInterval = 60 * 60 * 1000;

void syncTime() {
  Serial.print("synchro horloge NTP");
  configTime(0, 0, "pool.ntp.org", "time.nist.gov");
  time_t now = time(nullptr);
  while (now < 24 * 3600) {
    delay(100);
    now = time(nullptr);
  }
  Serial.println(" ok");
  setenv("TZ", timezone, 1);
  tzset();
  lastNTPUpdate = millis();
}

void setup() {
Serial.begin(115200);
Serial.println("\n");

WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
Serial.print("Connecting to Wi-Fi");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(300);
}
Serial.println();
Serial.println("connecté IP: " + WiFi.localIP().toString());

WiFiClient tcp_client,passive_tcp_client;
FTPClient ftp_client(&tcp_client,&passive_tcp_client);
if (ftp_client.open(FTP_SERVER_ADDRESS,FTP_SERVER_PORT,FTP_USER_NAME,FTP_USER_PASSWORD))Serial.println("ok");
else Serial.println("ko");

Serial.print("création fichier: ");
char text_to_write[]="test\n";
if (ftp_client.write_file("test.txt",(uint8_t*) text_to_write,strlen(text_to_write)+1)) Serial.println("ok");
else Serial.println("ko");

delay (2000);

Serial.print("Append to file: ");
char text_to_append[]= "texte detest\n";
if (ftp_client.append_file("test.txt",(uint8_t*) text_to_append,strlen(text_to_append)+1)) Serial.println("ok");
else Serial.println("ko");

ssl_client.setInsecure();
syncTime();
pinMode(buttonPin, INPUT);
pinMode(resetPin, INPUT);
bool buttonPressed = false;  // flag la coupure IR
}

void loop() {
  int buttonState = digitalRead(buttonPin);
  if (buttonState == HIGH) {
    counter++;
    Serial.println(counter);
    delay(2000);
  }

 lastButtonState = buttonState;
 
    time_t now = time(nullptr);
  struct tm timeinfo;
  localtime_r(&now, &timeinfo);
 
  if (millis() - lastNTPUpdate > ntpSyncInterval) syncTime();

  delay(1000);
}

avec ce code le "texte de test" créé dans le fichier est en caractères chinois... :thinking:

si vous faites +1 vous prenez le caractère nul de fin de chaîne. Vous ne voulez pas l'envoyer, vous voulez juste envoyer votre texte avec le \n qui va faire le passage à la ligne - sinon votre fichier destination aura plein de caractères nuls disséminés à chaque fin de ligne


avec ça dans le setup, vous ne touchez pas la variable globale

ça sera viré par le compilateur.


idem votre client FTP

est local à au setup...

il y a du ménage à faire... (je suis toujours sur mon tel donc je ne peux pas tester pour le moment)

merci pour ces remarques, bien prises en compte

bonsoir,
je souhaite créer un fichier texte quotidien sur un serveur ftp distant, dont le nom serait la date du jour, et le contenu la valeur numérique d'un compteur.
Je ne trouve pas comment utiliser la date comme nom du fichier...
merci d'avance si vous pouvez me donner une piste

#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <SPIFFS.h>
#include <FTPClient.h>
#include <time.h>

#define WIFI_SSID "xxxxxxxxxx"
#define WIFI_PASSWORD "xxxxxxxxxx"

#define FTP_SERVER_ADDRESS    "xxxxxxxxxx"
#define FTP_SERVER_PORT       21
#define FTP_USER_NAME         "xxxxxxxxxx"
#define FTP_USER_PASSWORD     "xxxxxxxxxx"

WiFiClientSecure ssl_client;

const int buttonPin = 2; // sortie du relais IR (état haut = faisceau coupé) 
const int resetPin = 3;
int counter = 0;
int buttonState = 0;      // état du relais IR (Haut ou Bas)
int lastButtonState = 0;  // dernier état du relais IR

boolean compteurAun = false;

const char* timezone = "CET-1CEST,M3.5.0,M10.5.0/3";

const int task1Hour = 2;
const int task1Minute = 28;
int lastRunDayTask1 = -1;

unsigned long lastNTPUpdate = 0;
const unsigned long ntpSyncInterval = 60 * 60 * 1000;

struct tm timeinfo;

void syncTime() {
Serial.print("synchro horloge NTP");
configTime(0, 0, "pool.ntp.org", "time.nist.gov");
time_t now = time(nullptr);
while (now < 24 * 3600) {
delay(100);
now = time(nullptr);
}
Serial.println(" ok");
setenv("TZ", timezone, 1);
tzset();
lastNTPUpdate = millis();
}

void dailyTask1() {
// Serial.println(counter);
WiFiClient tcp_client,passive_tcp_client;
FTPClient ftp_client(&tcp_client,&passive_tcp_client);
if (ftp_client.open(FTP_SERVER_ADDRESS,FTP_SERVER_PORT,FTP_USER_NAME,FTP_USER_PASSWORD))Serial.println("ok");
else Serial.println("ko");

// création fichier
char timeDate[12];
strftime(timeDate,12, "%F", &timeinfo);
Serial.println(timeDate);
char text_to_write[]="provisoire"; // timeDate ?
if (ftp_client.write_file("test.txt",(uint8_t*) text_to_write,strlen(text_to_write))) Serial.println("création fichier ok");
else Serial.println("ko");

// envoi valeur compteur dans fichier
char text_to_append[]= "texte de test\n";
if (ftp_client.append_file("test.txt",(uint8_t*) text_to_append,strlen(text_to_append))) Serial.println("envoi valeur ok");
else Serial.println("ko");

}

void setup() {
Serial.begin(115200);
Serial.println("\n");

WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
Serial.print("Connecting to Wi-Fi");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(300);
}
Serial.println();
Serial.println("connecté IP: " + WiFi.localIP().toString());

ssl_client.setInsecure();
syncTime();
pinMode(buttonPin, INPUT);
pinMode(resetPin, INPUT);

}

void loop() {
int buttonState = digitalRead(buttonPin);
if (buttonState == HIGH) {
counter++;
Serial.println(counter);
delay(2000);
}

lastButtonState = buttonState;
 
time_t now = time(nullptr);
struct tm timeinfo;
localtime_r(&now, &timeinfo);
 
if (timeinfo.tm_hour == task1Hour && timeinfo.tm_min == task1Minute && lastRunDayTask1 != timeinfo.tm_mday) {
dailyTask1();
lastRunDayTask1 = timeinfo.tm_mday;
}

if (millis() - lastNTPUpdate > ntpSyncInterval) syncTime();

delay(1000);
}

bool buttonPressed = false;  // flag la coupure IR

je vais essayer de retrouver un code d'exemple qui gère le WiFi, NTP et l'écriture d'un log journalier à périodique.

ça vous donnera un exemple

bonjour,
je souhaite envoyer par ftp un fichier texte sous la forme "essai_DD-MM-YY.txt"
(l'envoi d'un fichier texte avec un nom fixe se fait sans problème).
Je récupère la date du jour au bon format, mais n'arrive pas à l'insérer dans le nom du fichier depuis le buffer:
Extrait du code (infructueux) :

char timeDate[12];
strftime(timeDate,12, "%F", &timeinfo);
Serial.println(timeDate);
char buffer[128];
int cx;
cx = snprintf(buffer, 128,"%F\n");
char text_to_write[40]= "cx"; // timeDate ?
if (ftp_client.write_file) etc"

merci d'avance pour toute aide

Ne créez pas de nouveau sujet quand c’est la continuité de l’autre .

Je vous donne un exemple dans l’après midi - depuis le téléphone c’est un peu long d’extraire mon code et en faire un exemple

j'avais créé le sujet pour mettre en avant le point de blocage rencontré
Bien compris, cependant

Voici un petit code d'exemple à tester qui inclut un bibliothèque minimaliste FTP.

ça gère le temps par NTP, le maintient de la connexion au WiFi en cas de déconnexion et ça propose de faire un log journalier et un log cyclique (tous les delta T).

Dans le code vous aurez à remplir cette partie là

// Le WiFi
const char WIFI_NETWORK[] = "************";  // le SSID
const char WIFI_PWD[] = "*********";         // le mot de passe

// le FTP
const char ftpServer[] = "******";        // le serveur ftp comme ftp.cluster1.hosting.ovh.net
const uint16_t ftpPort = 21;              // le port standard FTP
const char ftpUser[] = "******";          // le login sur le serveur FTP
const char ftpPwd[] = "******";           // le mot de passe du serveur FTP
const char ftpDirectory[] = "/FTP_LOG";   // le chemin du répertoire depuis la racine du serveur FTP où sauver les logs

avec les infos de votre environnement

et pour voir comment construire un nom de fichier avec la date, regardez les fonctions ecrireLogJournalier() (le nom est sous la forme AAAAMMJJ.txt et la fonction ecrireLogCyclique() où là le nom est sous la forme AAAAMMJJ_HHMMSS.txt

ça devrait vous donner des idées.

Le code principal (le .ino)

/* 

╔═════════════════════════════════════════════════════════════════════════════════╗ 
║                                                                                 ║ 
║  code is placed under the MIT license                                           ║ 
║  Copyright (c) 2026 J-M-L                                                       ║ 
║  For the Arduino Forum : https://forum.arduino.cc/u/j-m-l                       ║ 
║                                                                                 ║ 
║  Permission is hereby granted, free of charge, to any person obtaining a copy   ║ 
║  of this software and associated documentation files (the "Software"), to deal  ║ 
║  in the Software without restriction, including without limitation the rights   ║ 
║  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell      ║ 
║  copies of the Software, and to permit persons to whom the Software is          ║ 
║  furnished to do so, subject to the following conditions:                       ║ 
║                                                                                 ║ 
║ The above copyright notice and this permission notice shall be included in      ║ 
║  all copies or substantial portions of the Software.                            ║ 
║                                                                                 ║ 
║  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR     ║ 
║  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,       ║ 
║  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE    ║ 
║  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER         ║ 
║  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,  ║ 
║  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN      ║ 
║  THE SOFTWARE.                                                                  ║ 
╚═════════════════════════════════════════════════════════════════════════════════╝ 

*/

#include <WiFi.h>
#include <WiFiClient.h>
#include <esp_sntp.h>
#include <time.h>
#include "ClientFTPMinimaliste.h"

// Le WiFi
const char WIFI_NETWORK[] = "************";  // le SSID
const char WIFI_PWD[] = "*********";         // le mot de passe

// le FTP
const char ftpServer[] = "******";        // le serveur ftp comme ftp.cluster1.hosting.ovh.net
const uint16_t ftpPort = 21;              // le port standard FTP
const char ftpUser[] = "******";          // le login sur le serveur FTP
const char ftpPwd[] = "******";           // le mot de passe du serveur FTP
const char ftpDirectory[] = "/FTP_LOG";   // le chemin du répertoire depuis la racine du serveur FTP où sauver les logs


// Log journalier
const bool journalisation = true;    // fait on un log par jour ? 
// Heure cible pour un log ici tous les jours à 12h30:00
const uint8_t heureCibleLog = 12;
const uint8_t minuteCibleLog = 30;
const uint8_t secondeCibleLog = 0;

// Log cyclique (écritures periodiques dans un log)
const bool cyclique = true;           // fait on un log cyclique ?
const uint32_t periodeLog = 15000ul;  // toutes les 15 secondes

WiFiClient tcpClient;
WiFiClient dataClient;
ClientFTPMinimaliste ftpClient(tcpClient, dataClient);


const char *ntpServer = "fr.pool.ntp.org";
bool wifiOK = false;
bool synchroNtpEffectuee = false;

time_t derniereSynchroNTP;

void ntpSyncCallback(struct timeval *tv) {
  derniereSynchroNTP = time(NULL);
  synchroNtpEffectuee = true;
  Serial.print("NTP synced at: ");
  struct tm *pTime = localtime(&derniereSynchroNTP);
  Serial.println(pTime, "%d/%m/%Y %H:%M:%S");
}

void WiFiStationConnection(WiFiEvent_t, WiFiEventInfo_t) {
  Serial.println("Wi-Fi connected");
}

void WiFiStationObtentionIP(WiFiEvent_t, WiFiEventInfo_t) {
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  wifiOK = true;
}

void WiFiStationDeconnection(WiFiEvent_t, WiFiEventInfo_t info) {
  wifiOK = false;
  Serial.print("Wi-Fi disconnected. Reason: ");
  Serial.println(info.wifi_sta_disconnected.reason);
  WiFi.begin(WIFI_NETWORK, WIFI_PWD);
}

bool afficherTemps(bool affichageForce = false) {
  static unsigned long dernierAffichage = 0;
  if (!synchroNtpEffectuee) return false;
  if (affichageForce || millis() - dernierAffichage >= 1000) {
    time_t now = time(NULL);
    struct tm *pTime = localtime(&now);
    Serial.println(pTime, "%d/%m/%Y %H:%M:%S");
    dernierAffichage = millis();
  }
  return true;
}

bool logJournalier() {
  if (!journalisation) return false;
  if (!wifiOK || !synchroNtpEffectuee) return false;

  time_t now = time(NULL);
  struct tm *pTime = localtime(&now);

  static bool dejaEcritAujourdHui = false;
  static int dernierJour = -1;

  // Réinitialisation si changement de jour
  if (pTime->tm_mday != dernierJour) {
    dejaEcritAujourdHui = false;
    dernierJour = pTime->tm_mday;
  }

  uint32_t secondesDepuisMinuit = pTime->tm_hour * 3600ul + pTime->tm_min * 60ul + pTime->tm_sec;
  uint32_t cibleSecondes = heureCibleLog * 3600ul + minuteCibleLog * 60ul + secondeCibleLog;

  if (secondesDepuisMinuit >= cibleSecondes && !dejaEcritAujourdHui) {
    dejaEcritAujourdHui = true;
    return true;
  }

  return false;
}

bool logRegulier() {
  if (!cyclique) return false;
  if (!wifiOK || !synchroNtpEffectuee) return false;
  static uint32_t derniereEcriture = -periodeLog;

  if (millis() - derniereEcriture >= periodeLog) {
    derniereEcriture = millis();
    return true;
  }
  return false;
}


void ecrireFichierFTP(const char *nomFichier, const char *texte, size_t taille) {
  if (!wifiOK || !synchroNtpEffectuee) {
    Serial.println("Erreur : WiFi ou NTP non disponible, log impossible");
    return;
  }

  if (!ftpClient.ouvrir(ftpServer, ftpPort, ftpUser, ftpPwd)) {
    Serial.printf("Erreur : impossible de se connecter au serveur FTP %s:%d\n", ftpServer, ftpPort);
    return;
  }

  Serial.printf("Changer de répertoire : ");
  if (!ftpClient.changerRepertoire(ftpDirectory)) {
    Serial.printf("Erreur : impossible de changer pour le répertoire %s\n", ftpDirectory);
    ftpClient.fermer();
    return;
  } else {
    Serial.println("OK");
  }

  Serial.printf("Écriture du fichier %s : ", nomFichier);
  if (!ftpClient.ecrireFichier(nomFichier, texte, taille)) {
    Serial.println("Erreur : écriture FTP échouée");
    ftpClient.fermer();
    return;
  } else {
    Serial.println("OK");
    Serial.printf("Contenu écrit : %s\n", texte);
  }

  ftpClient.fermer();
}

void ecrireLogJournalier() {
  char ftpFilename[] = "AAAAMMJJ.txt" ;  // juste pour réserver le bon nombre de caractères 
  char buffer[128];
  time_t now = time(NULL);
  struct tm *pTime = localtime(&now);

  snprintf(buffer, sizeof buffer, "Log journalier - Nous sommes le %02d/%02d/%04d et l'heure est %02d:%02d:%02d\r\n",
           pTime->tm_mday, pTime->tm_mon + 1, pTime->tm_year + 1900,
           pTime->tm_hour, pTime->tm_min, pTime->tm_sec);

  snprintf(ftpFilename, sizeof ftpFilename, "%04d%02d%02d.txt", pTime->tm_year + 1900, pTime->tm_mon + 1, pTime->tm_mday);

  ecrireFichierFTP(ftpFilename, buffer, strlen(buffer));
}

void ecrireLogCyclique() {
  char ftpFilename[] = "AAAAMMJJ_HHMMSS.txt"; // juste pour réserver le bon nombre de caractères 
  char buffer[128];
  time_t now = time(NULL);
  struct tm *pTime = localtime(&now);

  snprintf(buffer, sizeof buffer, "Log cyclique - Nous sommes le %02d/%02d/%04d et l'heure est %02d:%02d:%02d\r\n",
           pTime->tm_mday, pTime->tm_mon + 1, pTime->tm_year + 1900,
           pTime->tm_hour, pTime->tm_min, pTime->tm_sec);

  snprintf(ftpFilename, sizeof ftpFilename, "%04d%02d%02d_%02d%02d%02d.txt",
           pTime->tm_year + 1900, pTime->tm_mon + 1, pTime->tm_mday,
           pTime->tm_hour, pTime->tm_min, pTime->tm_sec);

  ecrireFichierFTP(ftpFilename, buffer, strlen(buffer));
}

void log() {
  if (logJournalier()) ecrireLogJournalier();  // si c'est le moment du log journalier, écrire le log journalier
  if (logRegulier()) ecrireLogCyclique();      // si c'est le moment du log cyclique, écrire le log cyclique
}


void setup() {
  Serial.begin(115200);
  Serial.println("Demo with Wi-Fi, NTP, and FTP");

  WiFi.onEvent(WiFiStationConnection, ARDUINO_EVENT_WIFI_STA_CONNECTED);
  WiFi.onEvent(WiFiStationObtentionIP, ARDUINO_EVENT_WIFI_STA_GOT_IP);
  WiFi.onEvent(WiFiStationDeconnection, ARDUINO_EVENT_WIFI_STA_DISCONNECTED);

  configTzTime("CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00", ntpServer);
  sntp_set_time_sync_notification_cb(ntpSyncCallback);

  WiFi.begin(WIFI_NETWORK, WIFI_PWD);
}

void loop() {
  afficherTemps(); // à commenter si vous ne voulez pas voir les secondes défiler
  log();
}

ensuite dans le même répertoire du sketch vous mettez ces deux fichiers

ClientFTPMinimaliste.h

/* 

╔═════════════════════════════════════════════════════════════════════════════════╗ 
║                                                                                 ║ 
║  code is placed under the MIT license                                           ║ 
║  Copyright (c) 2026 J-M-L                                                       ║ 
║  For the Arduino Forum : https://forum.arduino.cc/u/j-m-l                       ║ 
║                                                                                 ║ 
║  Permission is hereby granted, free of charge, to any person obtaining a copy   ║ 
║  of this software and associated documentation files (the "Software"), to deal  ║ 
║  in the Software without restriction, including without limitation the rights   ║ 
║  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell      ║ 
║  copies of the Software, and to permit persons to whom the Software is          ║ 
║  furnished to do so, subject to the following conditions:                       ║ 
║                                                                                 ║ 
║ The above copyright notice and this permission notice shall be included in      ║ 
║  all copies or substantial portions of the Software.                            ║ 
║                                                                                 ║ 
║  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR     ║ 
║  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,       ║ 
║  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE    ║ 
║  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER         ║ 
║  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,  ║ 
║  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN      ║ 
║  THE SOFTWARE.                                                                  ║ 
╚═════════════════════════════════════════════════════════════════════════════════╝ 

*/


#ifndef ClientFTPMinimaliste_H
#define ClientFTPMinimaliste_H

#define DEBUG_FTP 0    // mettre à 0 pour enlever les traces, 1 pour activer les traces

#if DEBUG_FTP
#define D_SerialBegin(...) Serial.begin(__VA_ARGS__)
#define D_print(...)       Serial.print(__VA_ARGS__)
#define D_write(...)       Serial.write(__VA_ARGS__)
#define D_println(...)     Serial.println(__VA_ARGS__)
#define D_printf(...)      Serial.printf(__VA_ARGS__)
#else
#define D_SerialBegin(...)
#define D_print(...)
#define D_write(...)
#define D_println(...)
#define D_printf(...)
#endif

#include <Client.h>
#include <Arduino.h>

#define DELAI_TIMEOUT_FTP 20000
#define TAILLE_BUFFER_FTP 128
#define TAILLE_BLOC_FTP 512

class ClientFTPMinimaliste {
private:
  Client& controle;
  Client& donnees;
  uint16_t code;
  char tamponReponse[TAILLE_BUFFER_FTP];
  uint8_t tamponEcriture[TAILLE_BLOC_FTP];

  uint16_t executerCommande(const char* commande, const char* parametre = "", char* reponse = nullptr);
  uint16_t lireReponse(char* reponse = nullptr);
  bool ouvrirModePassif();
  bool fermerModePassif();
  void envoyer(const uint8_t* tampon, size_t taille);

public:
  ClientFTPMinimaliste(Client& controle, Client& donnees);

  bool ouvrir(const char* serveur, const uint16_t port, const char* utilisateur, const char* motDePasse);
  void fermer();

  bool creerRepertoire(const char* repertoire);
  bool changerRepertoire(const char* repertoire);
  bool ecrireFichier(const char* nomFichier, const uint8_t* tampon, size_t taille);
  bool ecrireFichier(const char* nomFichier, const char* tampon, size_t taille);
  bool supprimerFichier(const char* nomFichier);
};

#endif

et l'implémentation associée

ClientFTPMinimaliste.cpp

/* 

╔═════════════════════════════════════════════════════════════════════════════════╗ 
║                                                                                 ║ 
║  code is placed under the MIT license                                           ║ 
║  Copyright (c) 2026 J-M-L                                                       ║ 
║  For the Arduino Forum : https://forum.arduino.cc/u/j-m-l                       ║ 
║                                                                                 ║ 
║  Permission is hereby granted, free of charge, to any person obtaining a copy   ║ 
║  of this software and associated documentation files (the "Software"), to deal  ║ 
║  in the Software without restriction, including without limitation the rights   ║ 
║  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell      ║ 
║  copies of the Software, and to permit persons to whom the Software is          ║ 
║  furnished to do so, subject to the following conditions:                       ║ 
║                                                                                 ║ 
║ The above copyright notice and this permission notice shall be included in      ║ 
║  all copies or substantial portions of the Software.                            ║ 
║                                                                                 ║ 
║  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR     ║ 
║  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,       ║ 
║  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE    ║ 
║  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER         ║ 
║  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,  ║ 
║  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN      ║ 
║  THE SOFTWARE.                                                                  ║ 
╚═════════════════════════════════════════════════════════════════════════════════╝ 

*/

#include "ClientFTPMinimaliste.h"
#include <Arduino.h>



ClientFTPMinimaliste::ClientFTPMinimaliste(Client& controle, Client& donnees)
  : controle(controle), donnees(donnees) {}

bool ClientFTPMinimaliste::ouvrir(const char* serveur, const uint16_t port, const char* utilisateur, const char* motDePasse) {
  if (!controle.connect(serveur, port)) return false;
  if (lireReponse() != 220) return false;
  if (executerCommande("USER ", utilisateur) != 331) return false;
  if (executerCommande("PASS ", motDePasse) != 230) return false;
  executerCommande("TYPE ", "I");  // mode binaire
  return true;
}

void ClientFTPMinimaliste::fermer() {
  executerCommande("QUIT");
  controle.stop();
}

bool ClientFTPMinimaliste::creerRepertoire(const char* repertoire) {
  return executerCommande("MKD ", repertoire) == 257;
}

bool ClientFTPMinimaliste::changerRepertoire(const char* repertoire) {
  return executerCommande("CWD ", repertoire) == 250;
}

bool ClientFTPMinimaliste::supprimerFichier(const char* nomFichier) {
  return executerCommande("DELE ", nomFichier) == 250;
}

bool ClientFTPMinimaliste::ecrireFichier(const char* nomFichier, const uint8_t* tampon, size_t taille) {
  if (!ouvrirModePassif()) return false;
  if (executerCommande("STOR ", nomFichier) == 150) envoyer(tampon, taille); 
  return fermerModePassif();
}

bool ClientFTPMinimaliste::ecrireFichier(const char* nomFichier, const char* tampon, size_t taille) {
  return ecrireFichier(nomFichier, reinterpret_cast<const uint8_t*>(tampon), taille);
}

uint16_t ClientFTPMinimaliste::executerCommande(const char* commande, const char* parametre, char* reponse) {
  controle.print(commande);
  controle.println(parametre);
  return lireReponse(reponse);
}

uint16_t ClientFTPMinimaliste::lireReponse(char* reponse) {
  uint32_t timeout = millis() + DELAI_TIMEOUT_FTP;
  uint8_t i = 0;

  while (!controle.available() && millis() < timeout) delay(5);
  while (controle.available()) {
    char c = controle.read();
    if (i < TAILLE_BUFFER_FTP - 1) {
      tamponReponse[i++] = c;
      tamponReponse[i] = 0;
    }
  }

  if (reponse) strcpy(reponse, &tamponReponse[4]);
  sscanf(tamponReponse, "%hu", &code);
  return code;
}

bool ClientFTPMinimaliste::ouvrirModePassif() {
  char reponse[128];
  uint16_t code = executerCommande("PASV", "", reponse);
  D_printf("Réponse PASV : %u %s\n", code, reponse);

  if (code != 227) {
    D_println("Erreur : serveur refuse PASV");
    return false;
  }

  strtok(reponse, "(,");
  uint8_t d[6];

  for (uint8_t i = 0; i < 6; i++) {
    char* t = strtok(nullptr, "(,");
    if (!t) {
      D_println("Erreur : impossible d'extraire l'adresse IP/port PASV");
      return false;
    }
    d[i] = atoi(t);
  }

  IPAddress ip(d[0], d[1], d[2], d[3]);
  uint16_t port = (d[4] << 8) | d[5];
  D_printf("Connexion données passive : %u.%u.%u.%u:%u\n", d[0], d[1], d[2], d[3], port);

  bool ok = donnees.connect(ip, port);
  D_printf("Connexion données : %s\n", ok ? "OK" : "Erreur");
  return ok;
}

bool ClientFTPMinimaliste::fermerModePassif() {
  donnees.stop();
  return lireReponse() == 226;
}

void ClientFTPMinimaliste::envoyer(const uint8_t* tampon, size_t taille) {
  size_t n = 0;

  for (size_t i = 0; i < taille; i++) {
    tamponEcriture[n++] = tampon[i];

    if (n == TAILLE_BLOC_FTP) {
      donnees.write(tamponEcriture, n);
      n = 0;
    }
  }
  if (n > 0) donnees.write(tamponEcriture, n);
}

vous avez dans le code principal la possibilité de régler l'heure d'écriture du log journalier et le temps entre 2 logs successifs (cyclique) avec chacun un bool qui dit si on le fait ou pas.

// Log journalier
const bool journalisation = true;    // fait on un log par jour ? 
// Heure cible pour un log ici tous les jours à 12h30:00
const uint8_t heureCibleLog = 12;
const uint8_t minuteCibleLog = 30;
const uint8_t secondeCibleLog = 0;

// Log cyclique (écritures periodiques dans un log)
const bool cyclique = true;           // fait on un log cyclique ?
const uint32_t periodeLog = 15000ul;  // toutes les 15 secondes

ça devrait fonctionner :crossed_fingers:

merci, j'ai de quoi étudier en effet!
belle soirée