ESP8266 webserver

Hallo.
Ich suche ein Beispiel, bei dem ein Webserver läuft und eine stylsheet datei oder ein Bild
via SPIFFS verwendet wird.
Je kleiner/simpler desto besser.

Ich selbst bekomme es nur bis zum Webserver mit selbstgeschriebener HTML-Seite und gespeicherter Stylesheet datei hin, jedoch wird diese vom Internetexplorer(bei mir der Firefox) nicht gefunden.

Ich wäre sehr dankbar, falls mir jemand weiterhelfen kann, da ich sehr verzweifle.

Viele Grüße

daOEKL:
... jedoch wird diese vom Internetexplorer(bei mir der Firefox) nicht gefunden.

Zunächst meinst Du „Browser“, nicht „Internetexplorer“. Der IE war/ist eines der vielen minderwertigen Produkte aus Redmond.

daOEKL:
Ich wäre sehr dankbar, falls mir jemand weiterhelfen kann, da ich sehr verzweifle.

Dass einen die Computerei schier wahnsinnig machen kann, ist schon wahr. Aber verzweifeln würde ich deshalb nie.

Zeig doch mal den HTML-Code, in dem Du das Stylesheet verwenden möchtest.

Als Workaround könnte helfen, dass man Stile auch innerhalb einer HTML-Datei (in den Kopfdaten) statt in einer externen CSS-Datei definieren kann. Siehe hier.

Gruß

Gregor

Hallo Gregor, danke für deine Antwort.
Es geht mir gerade darum, das SPIFFS zu nutzen, den Workaround hatte ich auch schon im Hinterkopf.

Du hast völlig recht, es war schon spät. Ich meinte “Browser” und ja, ich stimme deinem Urteil bezüglich Internetexplorer zu :smiley:

Hier mal mein HTML-Code und als zweites mein-Arduino Code.
Ich denke das Problem liegt an dem SPIFFS.

HTML (nochmal neu und absolut minimalistisch gehalten):

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
         <head>
                 <title>Steuerung</title>
                 <link rel="stylesheet" type="text/css" href="/MeinStyle.css" />
         </head>
         <body>
         <div id="mainframe">

                         <div id="tastenAuswahl">
                              <button type="button" class="button">1x kl</button><button type="button" class="button">2x kl</button>

                              <button type="button" class="button">1x mi</button><button type="button" class="button">2x mi</button>

                              <button type="button" class="button">1x gr</button><button type="button" class="button">2x gr</button>

                         </div>




         </div>
         </body>
</html>

Und der Arduino_Code, “zusammenkopiert/abgeguckt” aus verschiedenen Beispielen:

/*
  ESP8266 mDNS responder sample

  This is an example of an HTTP server that is accessible
  via http://esp8266.local URL thanks to mDNS responder.

  Instructions:
  - Update WiFi SSID and password as necessary.
  - Flash the sketch to the ESP8266 board
  - Install host software:
    - For Linux, install Avahi (http://avahi.org/).
    - For Windows, install Bonjour (http://www.apple.com/support/bonjour/).
    - For Mac OSX and iOS support is built in through Bonjour already.
  - Point your browser to http://esp8266.local, you should see a response.

 */


#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiClient.h>
#include "yeah.h"
#include "FS.h"


const char* ssid = "MeineSsid";
const char* password = "geändert";

// TCP server at port 80 will respond to HTTP requests
WiFiServer server(80);

void setup(void)
{  
  Serial.begin(115200);
  SPIFFS.begin();
  pinMode(0,OUTPUT);
  pinMode(2,INPUT);
   
  // Connect to WiFi network
  WiFi.begin(ssid, password);
  Serial.println("");  
  
  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  // Set up mDNS responder:
  // - first argument is the domain name, in this example
  //   the fully-qualified domain name is "esp8266.local"
  // - second argument is the IP address to advertise
  //   we send our IP address on the WiFi network
  if (!MDNS.begin("esp8266")) {
    Serial.println("Error setting up MDNS responder!");
    while(1) { 
      delay(1000);
    }
  }
  Serial.println("mDNS responder started");
  
  // Start TCP (HTTP) server
  server.begin();
  Serial.println("TCP server started");
  
  // Add service to MDNS-SD
  MDNS.addService("http", "tcp", 80);
}

void loop(void)
{
  // Check if a client has connected
  WiFiClient client = server.available();
  if (!client) {
    return;
  }
  Serial.println("");
  Serial.println("New client");

  // Wait for data from client to become available
  while(client.connected() && !client.available()){
    delay(1);
  }
  
  // Read the first line of HTTP request
  String req = client.readStringUntil('\r');
  
  // First line of HTTP request looks like "GET /path HTTP/1.1"
  // Retrieve the "/path" part by finding the spaces
  int addr_start = req.indexOf(' ');
  int addr_end = req.indexOf(' ', addr_start + 1);
  if (addr_start == -1 || addr_end == -1) {
    Serial.print("Invalid request: ");
    Serial.println(req);
    return;
  }
  req = req.substring(addr_start + 1, addr_end);
  Serial.print("Request: ");
  Serial.println(req);
  client.flush();
  
  String s;
  if (req == "/")
  {
    IPAddress ip = WiFi.localIP();
    s = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n";
    s += file1;
    s += "</html>\r\n\r\n";
    Serial.println("Sending 200");
  }
 
    else if (req =="AN")
  {
    digitalWrite(2,HIGH);
  }

      else if (req =="AUS")
  {
    digitalWrite(2,LOW);
  }
  
  else
  {
    s = "HTTP/1.1 404 Not Found\r\n\r\n";
    Serial.println("Sending 404");
  }
  client.print(s);
  
  Serial.println("Done with client");
}

und hier die eingebundene “yeah.h” datei.

#ifndef header_h
#define header_h

String file1=
"!DOCTYPE HTML PUBLIC -W3CDTD HTML 4.01 TransitionalEN httpwww.w3.orgTRhtml4loose.dtd\r\n"
"html\r\n"
"         head\r\n"
"                 titleSteuerungtitle\r\n"
"                 link rel=stylesheet type=textcss href=MyStyle.css \r\n"
"         head\r\n"
"         body\r\n"
"         div id=mainframe\r\n"
"\r\n"
"                         div id=tastenAuswahl\r\n"
"                              button type=button class=button1x klbuttonbutton type=button class=button2x klbuttonbr\r\n"
"                              button type=button class=button1x mibuttonbutton type=button class=button2x mibuttonbr\r\n"
"                              button type=button class=button1x grbuttonbutton type=button class=button2x grbuttonbr\r\n"
"                         div\r\n"
"\r\n"
"\r\n"
"\r\n"
"\r\n"
"         div\r\n"
"         body\r\n"
"html\r\n";

#endif

Der Webserver läuft und zeigt auch die Buttons an.
Das Stylesheet ist bereits mittels Tool auf dem ESP8266-01.
Das Stylesheet wird nicht gefunden und demnach auch nicht benutzt.

Lokal funktioniert alles.

Gibt es hier ein Beispiel wie man erfolgreich ein Bild oder eben ein Stylsheet oder andere Dateien via SPIFFS “bereitstellen” kann ?
Das wäre großartig.
Bin für jede Mühe und jede Antwort dankbar!

Gibt es hier ein Beispiel wie man erfolgreich ein Bild oder eben ein Stylsheet oder andere Dateien via SPIFFS "bereitstellen" kann ?

Das muss auch ohne Beispiel gehen....

Für das lesen der Datei gibt es Beispiele.
Und wie man einen HTTP Response Header zusammen baut, ist auch keine Geheimnis.
Und wie man Daten zum Client schickt... naja.. das findet sich auch in vielen Beispielen.

So ganz verstehe ich dein Problem nicht.

Danke fürs Antworten !

Meine Denkweise ist folgende:
Ich bastel mir eine Website und lade diese auf den Arduino bzw via ArduinoIDE auf den ESP.
Meine Website hat einmal die *.htm(l) datei und die *.css Datei.
Rufe ich die Datei lokal mit einem Browser auf, findet diese automatisch die Stylesheet datei, welche sich im selben Verzeichnis befindet.

Nun habe ich das ganze auf den ESP geladen. Die HTML-Seite habe ich im Quellcode eingebunden, so dass diese mit kompiliert wird, die Stylesheet Datei auf den ESP geladen.

Wider meiner Erwartung findet der Browser diese css Datei nun aber nicht. (lokal klappt es ja)
Und hier harpert es bei mir, denn ich verstehe nicht ganz, woran das liegt.
Der Serielle Monitor spuckt mir aus, dass die css datei angefragt wird, mittels get request aber diese dann nicht gefunden wird.

Muss ich anders verlinken ?
Muss ich die Datei erst über SPI aus dem FFS system holen ?

Eine Textdatei im SPIFFS öffnen, verändern und schließen klappt.

Ich verstehe hier etwas noch nicht richtig und hatte gehofft, dass mich jemand in die richtige Richtung bringen kann.

Muss ich die Datei erst über SPI aus dem FFS system holen ?

Ja Sicherlich!

Der Browser sendet einen Request.
Und du als Programmierer muss dafür Sorge tragen, dass der Request beantwortet wird.

Ich verstehe hier etwas noch nicht richtig und hatte gehofft, dass mich jemand in die richtige Richtung bringen kann.

Und wir/ich müssen erstmal die Frage verstehen ... :o

Nach aktuellem Stand wäre die gezielte Frage folgende:

Wie stelle ich eine Stylesheet Datei über SPI(-FFS) zur Verfügung, damit der Internetbrowser diese findet ?
Wenn html- und Stylesheetdatei auf dem Desktop liegen, findet mein Firefox ihn automatisch.

Ich hatte es mal mit

File styleSheet= SPIFFS.open(/MyStyle.css, "r");

probiert, dies funktionierte aber leider nicht.

Entschuldigung wenn ich etwas unpräzise gefragt habe,
ich hoffe ich habe damit niemandens Motivation mir zu helfen gegen 0 geschmälert.

damit der Internetbrowser diese findet ?

Falsche Denke!
Nicht er muss sie finden, sondern DU musst sie senden!

Open ist doch schon mal ein wichtiger Schritt.
Leider sagst du nicht, was nicht klappt....

Das Grundsätzliche Vorgehen:

  1. Request annehmen
  2. Request analysieren (z.B. querystring auswerten)
  3. HTTP Header senden
  4. Datei lesen und senden (zu Not Häppchen weise)

wenn du beim request auf \ abfragst, schickst du die HTML Seite, wenn du auf \MyStyle.css abfragst, öffnest du die Datei im Spiffs und gibst die aus.

Leute, wie toll und hilfreich (soein) dieses Forum doch ist.
Die letzten Antworten haben mich weiter gebracht als die letzten drei Tage in denen ich Suchmaschinen angeschmissen habe.

Was mich jetzt interessieren würde ist, wieso ich (im Sinne von coden) nichts tun muss, wenn ich die html datei sowie die stylesheet Datei beide lokal beispielsweise auf dem Desktop liegen habe.
Weil dies hier eben nicht auf einem Server läuft ?
Hat es mit Sicherheitsbeschränkungen zu tun ?

Ich bastel nochmal einen Versuch zusammen und poste den Code und den “Fehler” hier.

Weil dies hier eben nicht auf einem Server läuft ?

Kann man so sehen...

Der Dreh und Angelpunkt ist das HTTP !
Da muss alles drüber abgehandelt werden.

Bitte lesen und auswendig lernen:
HTTP Spezifikation
:o :o :o :o :o

daOEKL:
...
Ich suche ein Beispiel, bei dem ein Webserver läuft und eine stylsheet datei oder ein Bild
via SPIFFS verwendet wird.
...

Hier:
FSBrowser

Mit Cache control:
FSBrowser mit Cache control

Hallo,
es funktioniert inzwischen, nicht gut, aber es funktioniert.

Es dauert ca 30-60 Sekunden, bis die winzige stylesheet Datei beim Browser angekommen und in Verwendung ist.

Ich habe es so gelöst, dass bei entsprechendem get request, dieser erkannt wird und anschließend folgendes tut:

else if (req =="/MyStyle.css")  //falls Stylesheet verlangt wird, gib ihm Stylesheet
  {   
  styleSheet = SPIFFS.open("/MyStyle.css", "r");  //Stylesheet öffnen
  if(styleSheet){
    Serial.println("Stylesheet geöffnet");    //hat das öffnen geklappt?
    }
    while(styleSheet.available()){            //zeichen für zeichen lesen und an client senden:
    client.write(styleSheet.read());
    }
    styleSheet.close();
    Serial.println("Stylesheet übertragen");
  }

Wieso diese Variante so ewig dauert kann mir nicht zufällig jemand erklären ?

Wäre es nicht besser zu Streamen?

size_t sent = server.streamFile(file, contentType);

Wieviel kB hat das CSS?
Problematisch ist, dass der Browser aufgrund referenzierter Dateien mehere Verbindungen zum ESP aufbauen will. Mit mehreren Verbindungen kommt der ESP nur bedingt zurecht.

In dem von dir verlinkten Beispiel wird auch gestreamt.
Das gucke ich mir gerade an.

Ich würde natürlich gerne nachvollziehen, was da genau passiert und probiere gerade diese Funktion zu finden, bzw die Header Datei "ESP8266WebServer.h" zu finden um den Unterschid zu meiner Variante zu verstehen. (Ich vermute die Funktion StreamFile als Funktion der Klasse Server in jener Datei)

Das CSS hat gerade mal 1kB; genau genommen nur 824 Bytes.

Eigentlich ist es doch nur die CSS die referenziert wurde, oder ?

daOEKL:
Es dauert ca 30-60 Sekunden, bis die winzige stylesheet Datei beim Browser angekommen und in Verwendung ist.

    while(styleSheet.available()){            //[b]zeichen für zeichen[/b] lesen und an client senden:

client.write(styleSheet.read());
    }





Wieso diese Variante so ewig dauert kann mir nicht zufällig jemand erklären ?

Der macht für jedes Zeichen ein Paket? Warum machst du das nicht zeilenweise? oder kb-weise?

Oder eben doch streamen, wie es scheint, geht das ja. Das müsste das schnellste sein, wenn man an dem inhalt der datei nicht mehr fummeln muss.

Wie ElEspanol schon sagt, würde ich prüfen, ob client.write(styleSheet.read()); jedes Byte einzeln schickt oder die Daten intern puffert.
Die Daten solltest du möglichst nicht Byte für Byte lesen und verschicken sondern das in größeren Blöcken erledigen. Die einzelnen Funktionsaufrufe benötigen teilweise sehr lange bis sie abgearbeitet sind. Dabei macht es oft (fast) keinen zeitlichen Unterschied, ob pro Aufruf 1 oder x Bytes (z.B. 50) transportiert werden.

Bei 800 Byte Dateigröße wären das 800 Funktinsaufrufe vs. 800/50 = 16 Funktionsaufrufe mit jeweils entsprechendem zeitlichen Overhead.

Falls dein Router eine tcpdump-Funktion (die oft genutze Fritzbox hat das) hat, könntest du den Verkehr des ESP mitscheiden und dir anschauen ob da für jedes Byte ein einzelnes Paket über den Aether geht. Falls dem so ist, koenntest du entweder via Streaming oder händischem Puffern weiter kommen:

Am besten ist es, wenn du "große" Blöcke vom Interface liest und diese möglichst auch am Stück versendest:
in Pseudocode würde ich es so schreiben:

char buffer[50];   // Zwischenspeicher
int gelesen;
do {
  // versuche 50 Bytes aus SPIFFS in den Buffer zu lesen
  gelesen = readFromSPIFFS(buffer, 50); 
  if (gelesen > 0) { // falls wir Daten gelesen haben => "gelesen" Byte senden
    wifiSend(buffer, gelesen);
  }
} while (gelesen == 50); 
// solange wir 50 Zeichen gelesen haben, können wir noch nicht am Dateiende sein 
// (ausser die Dateigröße ist ein Vielfaches von 50, das wird aber 
// durch den >0 Vergleich abgefangen) => nochmal machen.

Was du auch machen könntest: An allen kritischen Stellen die Performance messen und ein wenig Profiling betreiben:
Im Prinzip so:

// start und stop sind Hilfsvariablen
// deltaWifi entspricht der Zeit in Microsekunden, die für Wifi Operationen verwendet wurde
// deltaSPIFFS entspricht der Zeit in Microsekunden, die für SPIFFS Operationen verwendet wurde
// wifiCount ist die Anzahl der Wifi Funktionsaufrufe
// SPIFFSCount ist die Anzahl der SPIFFS Funktionsaufrufe
long start, stop, deltaWifi = 0, deltaSPIFFS=0;
int wifiCount = 0, SPIFFSCount = 0;
...
start = micros();
IrgendeineWifiOperation()
stop = micros();
wifiCount++;
deltaWifi += stop - start;
....
start = micros();
IrgendeineSPIFFSOperation()
stop = micros();
SPIFFSCount++;
deltaSD += stop - start;
....
// deltaWifi, wifiCount und deltaWifi/wifiCount ausgeben
// deltaSPIFFS, SPIFFSCount und deltaSPIFFS/SPIFFSCount ausgeben

So kannst du schnell rausfinden, wo dein Code die meiste Zeit "verbrät".

Gerade mal den FSBrowser getestet…
Blitzschnell liefert er Bilder aus.
150KB, Blitzschnell.

(OK, nicht das ganz große Blitzschnell, aber auch nicht das kleine Blitzschnell, sondern eher das mittlere Blitzschnell)

Und so eine kleine CSS, das zuckt nur…

Ja, der ist echt nicht schlecht.
Den habe ich auch noch um Cache control erweitert… siehe oben…

Nur der ESP hat noch ein Problem mit mehreren anfragen. Trotz Cache control hakt es bei mir am Rechner. Aufm Handy gehts recht problemlos.
analyse_0027.png

Beim kompletten Neuladen braucht er am Anfang immer viel Zeit.
analyse_0013.png

Hallo,
ich wollte mich nochmal zurück melden.
Ich habe leider nur “immer mal zwischendurch” Zeit zum Basteln/Coden.

Ich habe nun das “HelloWebserver” Beispiel ausgebaut und handle es so, dass die index.html Datei bei entsprechndem request übertragen wird. Selbiges für die css datei.

Beides wird mittels der server.streamFile() Funktion übertragen und ist blitz-schnell da.

Was genau die Funktion tut und weshalb es nun sehr flott funktionert ist für mich jedoch nicht klar

In der Headerdatei ESP8266WebServer.h habe ich folgende codeschnipsel gefunden:

template<typename T> size_t streamFile(T &file, const String& contentType){
  setContentLength(file.size());
  if (String(file.name()).endsWith(".gz") &&
      contentType != "application/x-gzip" &&
      contentType != "application/octet-stream"){
    sendHeader("Content-Encoding", "gzip");
  }
  send(200, contentType, "");
  return _currentClient.write(file, HTTP_DOWNLOAD_UNIT_SIZE);
}

in der letzten Zeile der Funktion findet man “return _curretClient.write(file, HTTP_DOWNLOAD_UNIT_SIZE)”

Kann mir jemand erklären wo und vorallem wie ich den Inhalt dieser Funktion finden kann,
um zu verstehen, was da genau abgeht?