Forum: Mikrocontroller und Digitale Elektronik ESP8266, SD-Karte freien Speicher und Dateidatum


von Bernhard S. (b_spitzer)


Lesenswert?

Hallo zusammen,
für ein Datenlogger-Projekt mit dem ESP nutze ich die SD-Lib. Ich lege 
die LOG-Dateien in einem Unterverzeichnis automatisch an. Die nächste 
freie Nummer wird als neuer Dateiname genommen. Soweit erstmal alles ok.

Aber jetzt brauche ich die Anzeige des verfügbaren Speicherplatzes und 
ein korrektes Dateidatum für die Log-Dateien wäre auch nicht schlecht...

Nach dem ESP-Beispiel "SD-Card Info" bekomme ich erstmal die Gesamtgröße 
der SD-Karte ausgegeben. Meine einzige Idee wäre jetzt, alle Dateien auf 
der Karte zu durchsuchen und den genutzten Speicherpatz (aufgerundet auf 
den nächsten Sektor) zu summieren. Irgendwie recht umständlich... Hat da 
jemand eine andere Idee?

Zum Dateidatum: in der von der SD.h genutzen SDfat.h gibt es eine 
Definition für das Dateidatum und die Uhrzeit:
1
/** Default date for file timestamps is 1 Jan 2000 */
2
uint16_t const FAT_DEFAULT_DATE = ((2000 - 1980) << 9) | (1 << 5) | 1;
3
/** Default time for file timestamp is 1 am */
4
uint16_t const FAT_DEFAULT_TIME = (1 << 11);
Diese sind aber als const definiert (warum auch immer) und ich finde 
keine Funktion, um das Datum korrekt zu setzen. Datum und Uhrzeit ist 
per NTP bekannt.
Dateinamen mit JJMMDDHH.log scheiden aus, da mehrfach pro Stunde neue 
Logdateien angelegt werden können. Einzige Alternative wäre ein 
Dateiname aus Einerstelle des Jahres, Tag des Jahres (001-366), Stunde 
und Minute. Ist aber eher "gewöhnungsbedürftig"...

von Bauform B. (bauformb)


Lesenswert?

Auch wenn man per SD-Lib das Datum schreiben könnte, würde ich es lieber 
in den Dateinamen einbauen. Beim Kopieren geht ein Dateidatum schon mal 
verloren. FAT ist auch nicht eindeutig was die Sommerzeit angeht.

Wer NTP hat, hat auch die Unix-Zeit. Die funktioniert in 32 Bit 
mindestens bis 2037, als uint32_t auch bis 2106 mit 1 Sekunde Auflösung. 
32 Bit in Hex-Schreibweise sind zufällig 8 Zeichen. Wenn eine Minute 
Auflösung wirklich ausreicht, kann man auch durch 60 teilen und kommt 
auch Dezimal bis 2160 (99999999 Minuten ab 1970).

date -d@$((5ABEC128)) konvertiert Hex nach menschenlesbar.

von Michael U. (amiga)


Lesenswert?

Hallo,

Müßte bei SD

FSInfo fs_info;
SD.info(fs_info);

sein.
fs_info.usedBytes sollte dann den belegten Platz liefern.

Gruß aus Berlin
Michael

von Stefan F. (Gast)


Lesenswert?

Anstelle von NTP kann man Datum und Uhrzeit auch relativ einfach aus 
einem HTTP Header extrahieren:
1
stefan@stefanspc:~$ curl -v ptb.de
2
* Rebuilt URL to: ptb.de/
3
*   Trying 192.53.103.142...
4
* TCP_NODELAY set
5
* Connected to ptb.de (192.53.103.142) port 80 (#0)
6
> GET / HTTP/1.1
7
> Host: ptb.de
8
> User-Agent: curl/7.52.1
9
> Accept: */*
10
> 
11
< HTTP/1.1 302 Found
12
< Date: Sat, 31 Mar 2018 06:50:41 GMT
13
< Server: Apache
14
< Location: http://www.ptb.de/
15
< Content-Length: 202
16
< Content-Type: text/html; charset=iso-8859-1
17
< 
18
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
19
<html><head>
20
<title>302 Found</title>
21
</head><body>
22
...
23
</body></html>
24
* Curl_http_done: called premature == 0
25
* Connection #0 to host ptb.de left intact
26
stefan@stefanspc:~$

Du musst nach dem Verbindungsaufbau nur zwei Zeilen (+1 Leerzeile) 
senden:
1
GET / HTTP/1.1
2
Host: ptb.de

Und erhältst dann diese Antwort:
1
Date: Sat, 31 Mar 2018 06:50:41 GMT

Die Breite der einzelnen Felder ist immer gleich, deswegen kann man sich 
hier sehr einfach die Teile heraus schneiden, die man benötigt.

Ich habe hier bewusst den Webserver der ptb gewählt, weil dieses 
Institut die offizielle Uhrzeit für Deutschland ermittelt. Ich hoffe, 
dass deren Webserver halbwegs damit überein stimmen. Ein bisschen 
Verzögerung durch das Netz muss man bei dieser Methode natürlich 
tolerieren.

von Bernhard S. (b_spitzer)


Lesenswert?

Das Unix-Datum als Dateiname ist auch eine Idee. Ist zwar nicht sofort 
lesbar, aber die Sortierung auf der KArte passt. In der CSV-Datei steht 
eh der Startpunkt nochmal im Klartext.

@Michael: fsinfo ist wohl für das SPIFFS gedacht und findet sich in der 
FS.h. Wenn ich die zur SD.h dazubinde, dann streiten die sich über die 
Zuständigkeit für das file-Objekt :-(
Beim ESP32 oassen die Bibliotheken scheinbar besser zusammen. Im 
Beispiel hier 
https://github.com/espressif/arduino-esp32/blob/master/libraries/SD/examples/SD_Test/SD_Test.ino 
sind die beiden H-Dateien friedlich zusammen...

von Bernhard S. (b_spitzer)


Lesenswert?

@Stefan: die Abfrage bei der PTB ist auch ein interessanter Ansatz. Die 
Zeit ist um die halbe Ping-Zeit falsch, das kann man verschmerzen...

von Michael U. (amiga)


Lesenswert?

Hallo,

Bernhard S. schrieb:
> @Michael: fsinfo ist wohl für das SPIFFS gedacht und findet sich in der
> FS.h. Wenn ich die zur SD.h dazubinde, dann streiten die sich über die
> Zuständigkeit für das file-Objekt :-(

stimmt, hatte ich aus einem meiner ESP-Projekte entliehen ohne 
nachzudenken.
Die Streitereien der Module kenne ich auch zur genüge.

Bleibt wohl doch nur selber addieren...

Gruß aus Berlin
Michael

von Bernhard S. (b_spitzer)


Lesenswert?

Selber addieren geht erstmal. Leider hat die SD.h keine Funktionen zum 
auslesen der gesamten Sektoren einer Karte. Hier müsste wieder FS.h ran 
:-(

Aber erstmal das Posisitve: ich logge meine Messdaten als CSV-Dateien 
mit individuellem Dateinamen auf der SD-Karte im Verzeichnis log. Ein 
Logo wird von der SD-Karte gelesen und in den HTML-Seiten korrekt 
dargestellt. Eine Callback-Funktion liefert mir ein Dateilisting (und 
die Anzahl der belegten Sektoren). Der Datei-Download klappt jetzt mit 
einem kleinen Hack auch. Ich habe es nicht geschafft, einen Callback für 
log anzulegen. Stattdessen missbrauche ich den Callback für "File not 
found" und werte darin das Server-Argument aus. Wenn das jetzt log 
drinsteht, wird der Dateiname extrahiert und die Datei zum Client 
gestreamt. Klappt auch mit größeren Dateien ;-)

Meine Frage wäre jetzt nur, wie ich auf die gesamtgröße der SD-Karte 
komme, ohne die FS.h zu verwenden.

Meine Code-Teile:
1) Callbacks in setup()
1
// *******************************************************************
2
// set up WiFi callback-functions
3
// *******************************************************************
4
// called when the url is not defined here
5
server.onNotFound(handleNotFound);
6
7
server.on("/", HTTP_GET, handleRoot);
8
server.on("/index.htm", HTTP_GET, handleRoot);
9
server.on("/index.html", HTTP_GET, handleRoot);
10
server.on("/files", HTTP_GET, handleFileList);
11
server.on("/log", HTTP_GET, handleDEBUG);       // klappt leider nicht!
12
server.on("CSV", HTTP_GET, handleDEBUG);        // klappt leider nicht!
13
// Logo-Datei ausliefern
14
server.on("/logo.png",[]() { server.streamFile(logo, "image/png"); logo.seek(0);});
2) Callback-Funktion handleNotFound() mit Workaraound:
Der DEBUG-Teil liefert mir die übergebenen Argumente des HTTP-Aufrufs. 
Damit kann man unbekannte Aufrufe untersuchen und passende Callbacks 
erstellen. Im Else-Zweig wird dann die eigentliche 404-Fehlermeldung bei 
unbekannten URIs ausgegeben.
1
void handleNotFound() {
2
  // DEBUG output, check the response from the webpage
3
  Serial.print("URI: ");
4
  Serial.println(server.uri());
5
  Serial.print("Method: ");
6
  Serial.println((server.method() == HTTP_GET) ? "GET" : "POST");
7
  Serial.print("Arguments: ");
8
  Serial.println(server.args());
9
  // print current arguments, if there are any..
10
  for (uint8_t i = 0; i < server.args(); i++) {
11
    Serial.print(server.argName(i));
12
    Serial.print(": ");
13
    Serial.println(server.arg(i));
14
  }
15
  // END OF DEBUG
16
  if(server.uri().substring(1,5)=="log/") {
17
    // filename in 8.3-format with additional log/-string
18
    String filename = server.uri().substring(1,17);   // remove leading "/"
19
    File daten=SD.open(filename);
20
    server.streamFile(daten, "text/csv");
21
    daten.close();
22
    handleFileList();                // show file-listing again
23
  }
24
  else server.send(404, "text/plain", "FileNotFound");
25
}

3) und jetzt noch die Callback-Funktion für das Dateilisting:
1
void handleFileList() {
2
  File dir = SD.open("log");
3
  uint32_t usedsize=0, totalsize=0;
4
  String listing=F("<!DOCTYPE HTML>\r\n<html>\r\n<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" /></head>");
5
  listing += F("<body><img src=\"logo.png\"><br>");
6
  listing += F("<h2>Dateien von SD-card/log/</h2><br>Dateiname Gr&ouml;&szlig;e (Bytes)<br>");
7
  while(true) {
8
    File entry =  dir.openNextFile();
9
    if (! entry) {
10
      // no more files
11
      break;
12
    }
13
    listing +="<a h r e f=\"log\\";     //<<=== sch... Spamerkennung!!
14
    listing +=entry.name();
15
    listing +="\">"; 
16
    listing +=entry.name();
17
    listing +=" ";
18
    listing +=entry.size();
19
    usedsize +=entry.size()/512+1;   // round to next 512bByte block
20
    listing +="schliessendes A-Tag wurde von beschissener SPAM-Erkennung immer rausgekickt<br>";
21
    entry.close();
22
  }
23
  listing +="<br><h3>Total belegt: ";
24
  listing +=String(usedsize);
25
  listing +=" Bl&ouml;cke</h3>";
26
  //totalsize = card.blocksPerCluster() * card.clusterCount();
27
  //listing +="<br><h3>Total verf&uuml;gbar: ";
28
  //listing +=String(totalsize);
29
  //listing +=" Bl&ouml;cke</h3>";
30
  //listing +="<br><h3>Frei: ";
31
  //listing +=String(totalsize*100/usedsize);
32
  //listing +=" %</h3>";
33
  listing +="</body></html>";
34
  server.send(200, "text/html", listing);
35
}

Und hier wäre eine Idee gut, wie ich die Gesamtgröße der SD-Karte 
berechnen könnte...

: Bearbeitet durch User
von Bernhard S. (b_spitzer)


Lesenswert?

ES IST ECHT ZUM KOTZEN, WENN MAN HTML-TAGS IM QUELLCODE DURCH 
SCHEISSDRECK ERSETZEN MUSS UM DIE VERF***TE SPAMERKENNUNG ZU ÜBERLISTEN.

Ich habe jetzt echt 20 Versuche gebraucht, um rauszufinden, an welcher 
Zeile sich die Spamerkennung aufgeilt. Macht echt keinen Spass!

: Bearbeitet durch User
von Strange (Gast)


Lesenswert?

Auch in den codetags? Soll das so sein?

von Kolja L. (kolja82)


Lesenswert?

Bernhard S. schrieb:
> 20 Versuche

Ich hätte bei dem Link angefangen

von Sheeva P. (sheevaplug)


Lesenswert?

Stefan U. schrieb:
> Anstelle von NTP kann man Datum und Uhrzeit auch relativ einfach aus
> einem HTTP Header extrahieren:
>
1
> stefan@stefanspc:~$ curl -v ptb.de
2
>

Was ist eigentlich ein HTTP-HEAD-Request? ;-)

von Sheeva P. (sheevaplug)


Lesenswert?

Bernhard S. schrieb:
> ES IST ECHT ZUM KOTZEN, WENN MAN HTML-TAGS IM QUELLCODE DURCH
> SCHEISSDRECK ERSETZEN MUSS UM DIE VERF***TE SPAMERKENNUNG ZU ÜBERLISTEN.
>
> Ich habe jetzt echt 20 Versuche gebraucht, um rauszufinden, an welcher
> Zeile sich die Spamerkennung aufgeilt. Macht echt keinen Spass!

Möglicherweise wären ein oder mehrere Anhänge hier die bessere Wahl. ;-)

von Sascha W. (sascha-w)


Lesenswert?

Sheeva P. schrieb:
> Stefan U. schrieb:
>> Anstelle von NTP kann man Datum und Uhrzeit auch relativ einfach aus
>> einem HTTP Header extrahieren:
>>
1
>> stefan@stefanspc:~$ curl -v ptb.de
2
>>
>
> Was ist eigentlich ein HTTP-HEAD-Request? ;-)
Damit forderst du nur den Header an - um mal zu schauen ob die Datei 
verfügbar ist. Offensichtlich kann der Server aber den Inhalt der Seite 
trotzdem gleich im 1. Paket mitsenden wenn er zusammen mit dem Header in 
selbiges passt.

Sascha

von Sheeva P. (sheevaplug)


Lesenswert?

Sascha W. schrieb:
> Sheeva P. schrieb:
>> Stefan U. schrieb:
>>> Anstelle von NTP kann man Datum und Uhrzeit auch relativ einfach aus
>>> einem HTTP Header extrahieren:
>>>
1
>>> stefan@stefanspc:~$ curl -v ptb.de
2
>>>
>>
>> Was ist eigentlich ein HTTP-HEAD-Request? ;-)
> Damit forderst du nur den Header an - um mal zu schauen ob die Datei
> verfügbar ist.

Ja, ganz genau. Und in dem Header steht dann was? Richtig: der gesuchte 
Zeitstempel mit Datum, Uhrzeit, und einer Zeitzone.

> Offensichtlich kann der Server aber den Inhalt der Seite
> trotzdem gleich im 1. Paket mitsenden wenn er zusammen mit dem Header in
> selbiges passt.

Ja, das ist dann ein HTTP-GET-Request. Aber warum sollte man den Inhalt 
der Seite laden, wo man doch nur an einer Headerzeile interessiert ist?

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.