ESP32-C3 : css sur SPIFFS

Bonjour,

J'ai besoin d'aide pour reussir à utiliser un ".css" que j'ai placer en "SPIFFS".
Pour l'instant uniquement le css, car le HTML de la page évolue.
Sans prise en charge de l'upload SPIFFS par l'IDE 2.x, je dois passer par un programme spécifique pour gerer mes fichiers en SPIFFS (d'ou le choix de laisser le HTML dans mon code pour le moment).
J'utilise GitHub - roberttidey/BaseSupport: Common set up for Arduino applications (OTA + interface web d'upload en SPIFFS).

Le .css est semble-t-il à la racine (j'ai essayer de le mettre dans "/data/" mais le programme n'a pas l'air de le prendre en charge le répertoire que j'indique (en tout cas il m'affiche toujours le CSS comme étant à la racine).

En ligne 93, j'ai cette référence, qui ne fonctionne actuellement pas (j'ai essayé "./bulma.css" et "./data/bulma.css" également) :

    <link rel="stylesheet" href="bulma.css">

J'imagine que le serveur web n'expose pas les fichiers SPIFFS, mais je ne sais pas comment m'y prendre pour que ce soit le cas

Mon code complet

#include "SPIFFS.h"

#define p1PinPhoto A1
#define p2PinPhoto A2
#define pinBat A0
#define tonePin RX



void Debug();
float f_vBat() ;


int p1NbLap = 0;
int p2NbLap = 0;
int p1RAZ = 0;
int p2RAZ = 0;


int toneDuree = 125;
int toneDureeRecord = 350;
int p1Tone = 1100;
int p2Tone = 1100;


int p1PhotoRes = 0;
int p2PhotoRes = 0;

int p1PhotoSeuil = 4990;
int p2PhotoSeuil = 4990;


long p1TempsDepart = 0;  //Debut de la prise du temps
long p2TempsDepart = 0;
long p1TempsMeilleur = 0;
long p2TempsMeilleur = 0;
long tempsActuel = 0;  // stock millis() le temps du traitement

int tempsMiniTour = 3500;  // en ms, minimum pour comptabilisation temps



char forceWifi[32];
float etatBat = 0;
float vBat = 0;

/////////////////////////
#include <math.h>

bool boolDebug = false;


// Reglages Carte //
const int analogBit = 12;                             // Précision Analogique bit
const int maxAnalog = int(pow(2, float(analogBit)));  // Analog max en decimal




///////////////////////////////////////////////////
//Wifi + OTA
#include <WiFi.h>
#include <ESPmDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>

const char* ssid = "...";
const char* password = "...";






////////////////////////////////////////////////////
//Serveur Web
//#include <WebServer.h>
#include "ESPAsyncWebServer.h"
//WebServer server(80);
AsyncWebServer server(80);
const char* hostName = "XIAO-ESP32-C3";
const char* http_username = "";//Identification sur la page web
const char* http_password = "";


const char pageHtml[] PROGMEM = R"rawliteral(
<html><head>
    <title>Track Race Stat</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="refresh" content="8" >
    <link rel="stylesheet" href="bulma.css">
  </head><body>
  <div class="container is-max-desktop">
    </br>
    <p> <i class="fas fa-flag-checkered"></i>  <h1  class="title is-1">  Race Track Stats  </h1>  <i class="fas fa-flag-checkered"></i>   
    </br></br>

    <form action="/" method="get" name="Valeur">
      <table class="table" class="rel top30"><thead><tr><center><th colspan="3"> - Stats - </th></center></tr></thead>
          <tbody>
               <tr>
                <td></td><td><H2>Piste 1</H2></td><td><H2>Piste </H2></td>
               </tr><tr>
                  <td>
                       Nombre de tours : 
                  </td><td align="center">
                       %p1NbLap%
                  </td><td align="center">
                       %p2NbLap%
                  </td>
               </tr><tr>
                  <td>
                        Meilleurs temps :  
                  </td><td align="right">
                      %p1TempsMeilleur%
                  </td><td align="right">
                      %p2TempsMeilleur%
                  </td>
                </tr> <tr></tr> <tr>
                  <td></td>
                  <td align="center">
                    <button class="delete is-large" name="p1RAZ" value="%p1RAZ%"></button>
                  </td>
                  <td align="center">
                    <button class="delete is-light is-large" name="p2RAZ" value="%p2RAZ%"></button>
                  </td>
               </tr>
          </tbody>
      </table></form>
      </br></br></br>
      <form action="/" method="get" name="Valeur"><table class="table"><thead><tr><center><th colspan="3"> - Configuration - </th></center></tr></thead>
        <tbody>
          <tr>
          <td></td><td><H2>Piste 1</H2></td><td><H2>Piste </H2></td>
          </tr><tr>
            <td>
                  Valeurs detection  :  
            </td><td>
                %p1PhotoRes%
            </td><td>
                %p2PhotoRes%
            </td>
          </tr><tr>
              <td>
                    Seuil Actuel :
                    </br>
                    Seuil Cible  :  
              </td><td>
                  %p1PhotoSeuil%
                  </br>
                  <input type="text" size="3" name="p1PhotoSeuil" value="%p1PhotoSeuil%">
              </td><td>
                  %p1PhotoSeuil%
                  </br>
                  <input type="text" size="3" name="p2PhotoSeuil" value="%p2PhotoSeuil%">
              </td><td  COLSPAN="2">
                 <input type="submit" value="Modifier" class="button is-info is-rounded"></input">
              </td>
          </tr><tr>
              <td>Puissance wifi : %forceWifi%</td>
          </tr><tr>
              <td>Bat : </td>
              <td><progress class="progress is-link" value="%etatBat%" max="0.9">%vBat%</progress></td>
          </tr>
        </tbody>
    </table>
    </div>   
    </form>
  </body>
</html>)rawliteral";



String processor(const String& var) {
  if (var == "p1NbLap") {
    return String(p1NbLap);
  }
  if (var == "p2NbLap") {
    return String(p2NbLap);
  }
  if (var == "p1PhotoRes") {
    return String(p1PhotoRes);
  }
  if (var == "p2PhotoRes") {
    return String(p2PhotoRes);
  }
  if (var == "p1RAZ") {
    return String(p1RAZ);
  }
  if (var == "p2RAZ") {
    return String(p2RAZ);
  }
  if (var == "p1PhotoSeuil") {
    return String(p1PhotoSeuil);
  }
  if (var == "p2PhotoSeuil") {
    return String(p2PhotoSeuil);
  }
  if (var == "p1TempsMeilleur") {
    return String((float)p1TempsMeilleur / 1000, 3);
  }
  if (var == "p2TempsMeilleur") {
    return String((float)p2TempsMeilleur / 1000, 3);
  }
  if (var == "forceWifi") {
    return String(forceWifi);
  }
  if (var == "etatBat") {
    etatBat = f_vBat() - 3.3 ; // Pourcentage batterie  
    return String(etatBat);
  }
  if (var == "vBat") {
    return String(f_vBat());
  }

  return String();
}




///////////////////////////////////////////////
//////////////////////////////////////////////
////////////////////////////////////////////


void setup() {

  pinMode(p1PinPhoto, INPUT);
  pinMode(p2PinPhoto, INPUT);
  pinMode(pinBat, INPUT);


  /// Initialisation de la sortie serie
  Serial.begin(115200);


  ////// Spiffs (fichier locaux)
  if(!SPIFFS.begin(true)){
   Serial.println("An Error has occurred while mounting SPIFFS");
   return;
 }

  ///////////// WIFI
  WiFi.begin(ssid, password);
  while (WiFi.waitForConnectResult() != WL_CONNECTED) {
    delay(5000);
    ESP.restart();
  }

  

  ///////////// OTA
  ArduinoOTA.setHostname("XIAO-ESP32-C3");
  ArduinoOTA.setPassword("...");

  ArduinoOTA
    .onStart([]() {
      String type;
      if (ArduinoOTA.getCommand() == U_FLASH)
        type = 'sketch';
      else  // U_SPIFFS
        type = 'filesystem';

      // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
      Serial.println('Start updating ' + type);
    })
    .onEnd([]() {
      Serial.println('\nEnd');

      delay(3000);
    })
    .onProgress([](unsigned int progress, unsigned int total) {
      Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
    })
    .onError([](ota_error_t error) {
      Serial.printf("Error[%u]: ", error);
      if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
      else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
      else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
      else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
      else if (error == OTA_END_ERROR) Serial.println("End Failed");
    });
  ArduinoOTA.begin();


  ///////////////////Serveur Web
  server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) {
    if (!request->authenticate(http_username, http_password))
      return request->requestAuthentication();
    int paramsNr = request->params();
    Serial.println(paramsNr);

    //// recuperation des actions utilisateur
    for (int i = 0; i < paramsNr; i++) {
      AsyncWebParameter* p = request->getParam(i);
      if (p->name() == "p1RAZ") {
        if (p->value().toInt() == 1) {
          p1RAZ = 0;
          p1NbLap = 0;
          p1TempsMeilleur = 0;
          p1TempsDepart = 0;
        }
      }
      if (p->name() == "p2RAZ") {
        if (p->value().toInt() == 1) {
          p2RAZ = 0;
          p2NbLap = 0;
          p2TempsMeilleur = 0;
          p2TempsDepart = 0;
          request->redirect("/");
        }
      }
      if (p->name() == "p1PhotoSeuil") {
        p1PhotoSeuil = p->value().toInt();
      }
      if (p->name() == "p2PhotoSeuil") {
        p2PhotoSeuil = p->value().toInt();
      }
    }
    request->send_P(200, "text/html", pageHtml, processor);
  });

  server.begin();

  tone(tonePin, p2Tone, 750);

}




///////////////////////////////////////////////
//////////////////////////////////////////////
////////////////////////////////////////////

void loop() {
  // Mode Debug
  if (boolDebug) {
    Debug();
  }

  //server.handleClient();
  ArduinoOTA.handle();

  String(WiFi.RSSI()).toCharArray(forceWifi, 32);

  p1PhotoRes = analogRead(p1PinPhoto);

  if (p1PhotoRes < p1PhotoSeuil) {
    tempsActuel = millis();
    if ((p1TempsDepart + tempsMiniTour) < tempsActuel) {
      p1NbLap = p1NbLap + 1;
      tone(tonePin, p1Tone, toneDuree);
      if (((tempsActuel - p1TempsDepart) < p1TempsMeilleur) || (p1TempsMeilleur == 0)) {
        p1TempsMeilleur = tempsActuel - p1TempsDepart;
        tone(tonePin, p1Tone, toneDureeRecord);
      }
    }
    p1TempsDepart = tempsActuel;
  }


  p2PhotoRes = analogRead(p2PinPhoto);

  if (p2PhotoRes < p2PhotoSeuil) {
    tempsActuel = millis();
    if ((p2TempsDepart + tempsMiniTour) < tempsActuel) {
      p2NbLap = p2NbLap + 1;
      tone(tonePin, p2Tone, toneDuree);
      if (((tempsActuel - p2TempsDepart) < p2TempsMeilleur) || (p2TempsMeilleur == 0)) {
        p2TempsMeilleur = tempsActuel - p2TempsDepart;
        tone(tonePin, p2Tone, toneDureeRecord);
      }
    }
    p2TempsDepart = tempsActuel;
  }

  Serial.println(analogRead(p1PinPhoto));
}

float f_vBat() 
{
  int Vbatt = 0;
  for(int i = 0; i < 16; i++) {
    Vbatt = Vbatt + analogReadMilliVolts(pinBat); // ADC with correction   
  }
  float Vbattf = 2 * Vbatt / 16 / 1000.0 ;     // attenuation ratio 1/2, mV --> V

  return Vbattf ;
}

void Debug() {
  Serial.println("Force Signal : " + String(WiFi.RSSI()));
  Serial.println(WiFi.localIP());
}

Merci pour votre aide !

Salut,

Il faut que tu indique explicitement à ton serveur web de servir les fichiers. Pour cela, dans le setup() il faut ajouter des commandes du type :

  server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(LittleFS, "/style.css", "text/css");
  });

La doc qui m'a beaucoup servi : ESP32 Web Server using SPIFFS (SPI Flash File System) | Random Nerd Tutorials et d'autres du même site.

Merci pour ton aide ! J'ai ajouté ça à mon code et tout fonctionne :

  server.on("/bulma.min.css", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send(SPIFFS, "/bulma.min.css", "text/css");
  });

Ce qui n'est pas du tout intuitif qi on a déjà manipulé des serveurs web plus classiques sur des plateformes plus « conventionnelles » c'est qu'il faut préciser le chemin d'accès pour tous les fichiers susceptibles d'être utilisés par le navigateur (CSS, JS, polices, etc. et avec le bon type MIME). Et en plus, si tu as des formulaires à traiter, il faut penser en permettre l’envoi en HTTP_POST si c'est le cas.

C'est vite lourd donc je relance juste le fil sur ce point : n'y aurait-il pas une fonction magique pour signifier au serveur de servir tout ce qu'il a à disposition sans trop discuter ?

Il me semble que la méthode serveStatic() fait ce que tu demandes.

merci je regarderai ça...

C'est bien, je vais passer du css "minimal" au css normal, qui si j'ai tout suivi s'appuie sur un ensemble de fichier (et non un fichier unique).
Tu as donc anticipé une question qui serait arrivée ce soir ou demain :pray:

Je creuserais aussi "serveStatic()"

Merci à vous deux

Tu peux aussi y mettre ton code HTML, ça allégera le code C++

Oui je le ferais quand j'arrêtais de bidouiller le HTML.
La je dois passer par un sketch spécifique pour avoir un gestionnaire SPIFFS sur interface web, pour faire les upload/download, puis remettre mon sketch principal.

Et pour une raison que j'ignore, je dois batailler comme c'est pas permis pour reussi un upload avec l'IDE au repassage sur mon code principal (j'ai des erreurs de communication que ce soit en OTA ou USB... Le serveur WEB est portant fonctionnel et aucun probleme quand il s'agit d'upload mon skect principal lorsque la carte tourne déjà avec, c'est uniquement passage sketch SPIFFS à SKTECH principal ).

C'est maintenant plutôt une norme sur les servers directement codé par programme, de définir les routes et d'avoir une action en fonction de chaque route.
Si on définit uniquement "/", normalement le reste de l'URI est récupérable par la variable "request" ?

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.