ftp_log.tc¶
ftp_log.tc — Messzeilen sammeln und alle paar Minuten an eine Datei auf
// @name: FTP-Logger (FRITZ!NAS)
// @info: https://github.com/gemu2015/Sonoff-Tasmota/blob/universal/tasmota/tinyc/TinyC_Reference_DE.md#ftp-client-eine-datei-oder-einen-puffer-zum-nas
// ─────────────────────────────────────────────────────────────────────
// ftp_log.tc — Messzeilen sammeln und alle paar Minuten an eine Datei auf
// einem FTP-Server ANHAENGEN (FRITZ!NAS, Synology, jeder Rechner mit FTP).
//
// Rolfs Frage (22.09.2026): kann der ESP32 seine „Datenbank" auf dem
// Fritzbox-Speicher fuehren? Ja -- ueber FTP (der Medienserver ist DLNA
// und nur lesend; FTP unter Heimnetz -> Speicher (NAS) einschalten).
//
// Die Zugangsdaten stehen NICHT im Quelltext, sondern in /ftp.cfg auf dem
// Geraet (vier Zeilen: Host, Benutzer, Passwort, Zielpfad), z. B.
// 192.168.178.1 (die IP, nicht fritz.box: ueber IPv6 verweigert die Box PASV)
// esp
// geheim
// /FRITZ/mediabox/klima.csv (interner Speicher; ein Stick heisst wie sein Datentraeger)
// Beim Start wird die Datei gelesen; fehlt sie, gelten die Vorgaben unten.
//
// Konsole (Praefix FTP):
// FTPSET host;user;pw;/pfad/datei.csv Ziel setzen und neu anmelden (bis Neustart)
// FTPSAVE das Gesetzte nach /ftp.cfg schreiben
// FTPGO gesammelte Zeilen jetzt senden
// FTPSTAT Puffer, letzter Rueckgabewert, Ziel
//
// Gesendet wird aus TaskLoop(), nicht aus EverySecond (dort hielte die
// Uebertragung den VM-Mutex und froere alle Slots ein) und NICHT aus
// einem spawnTask-Worker: der hat auf dem ESP32 seine eigene VM und sieht
// den Zeilenpuffer nicht -- ein char[] ueber 16 Zeichen liegt im Heap, und
// Heap-Objekte teilt ein Worker nicht (nur skalare Globals). TaskLoop laeuft
// auf dem VM-Task, teilt alles und gibt den Mutex waehrend der Uebertragung
// frei; EverySecond sammelt derweil weiter.
// ─────────────────────────────────────────────────────────────────────
char host[64] = "192.168.178.1";
char user[32] = "esp";
char pw[32] = "geheim";
char remote[96] = "/FRITZ/mediabox/klima.csv";
char zeilen[768]; // gesammelte Zeilen, noch nicht gesendet
char stamp[24];
int go = 0; // 1 -> Sender soll jetzt senden
int letzter = -999; // letzter Rueckgabewert von ftpPutStr
int gesendet = 0; // Bytes insgesamt
void EverySecond() {
if (tasm_uptime % 60 == 0 && strlen(zeilen) < 700) {
char z[64];
timeStamp(stamp);
sprintf(z, "%s;%d;%.1f;%.1f\n", stamp, tasm_uptime, tasm_temp, tasm_hum);
strcat(zeilen, z);
}
if (tasm_uptime % 300 == 0) go = 1; // alle 5 Minuten von selbst
}
void TaskLoop() {
delay(1000);
if (!go) return;
go = 0;
if (strlen(zeilen) == 0) return;
int r = ftpPutStr(remote, zeilen, 1); // 1 = anhaengen; Sitzung aus main()
letzter = r;
if (r >= 0) { gesendet = gesendet + r; zeilen[0] = 0; }
// bei Fehler bleibt der Puffer stehen: naechster Versuch in 5 Minuten
}
// Zeilenende und Leerzeichen abschneiden (ein Editor unter Windows haengt \r an)
void trim(char s[]) {
int n = strlen(s);
while (n > 0 && (s[n - 1] == '\r' || s[n - 1] == ' ' || s[n - 1] == '\n')) { n--; s[n] = 0; }
}
// /ftp.cfg lesen: 1 = gelesen, 0 = keine Datei
int cfgLesen() {
char cfg[256];
int f = fileOpen("/ftp.cfg", 0);
if (f < 0) return 0;
int n = fileRead(f, cfg, 255);
fileClose(f);
if (n <= 0) return 0;
cfg[n] = 0;
if (webParse(cfg, "\n", 1, host) > 0) trim(host);
if (webParse(cfg, "\n", 2, user) > 0) trim(user);
if (webParse(cfg, "\n", 3, pw) > 0) trim(pw);
if (webParse(cfg, "\n", 4, remote) > 0) trim(remote);
return 1;
}
int cfgSchreiben() {
char cfg[256];
sprintf(cfg, "%s\n%s\n%s\n%s\n", host, user, pw, remote);
int f = fileOpen("/ftp.cfg", 1);
if (f < 0) return -1;
int n = fileWrite(f, cfg, strlen(cfg));
fileClose(f);
return n;
}
void Command(char cmd[]) {
char resp[160];
if (cmd[0] == 'S' && cmd[1] == 'E' && cmd[2] == 'T') {
// FTPSET host;user;pw;remote
char rest[200];
int i = 3; int k = 0;
while (cmd[i] != 0 && k < 199) { rest[k] = cmd[i]; i++; k++; }
rest[k] = 0;
int n = webParse(rest, ";", 0, host)
+ webParse(rest, ";", 1, user)
+ webParse(rest, ";", 2, pw)
+ webParse(rest, ";", 3, remote);
int r = ftpOpen(host, user, pw); // neue Sitzung mit den neuen Daten
sprintf(resp, "host=%s user=%s remote=%s open=%d", host, user, remote, r);
responseCmnd(resp);
} else if (strcmp(cmd, "SAVE") == 0) {
sprintf(resp, "ftp.cfg: %d Byte", cfgSchreiben());
responseCmnd(resp);
} else if (strcmp(cmd, "GO") == 0) {
go = 1;
responseCmnd("sende");
} else if (strcmp(cmd, "STAT") == 0) {
sprintf(resp, "puffer=%d letzter=%d gesendet=%d ziel=%s%s", strlen(zeilen), letzter, gesendet, host, remote);
responseCmnd(resp);
} else {
responseCmnd("FTPSET host;user;pw;/pfad | FTPSAVE | FTPGO | FTPSTAT");
}
}
int main() {
zeilen[0] = 0;
if (cfgLesen()) addLog("ftp_log: /ftp.cfg gelesen"); else addLog("ftp_log: keine /ftp.cfg, Vorgaben gelten");
tcpConnectTimeout(3000);
addCommand("FTP");
int r = ftpOpen(host, user, pw); // einmal anmelden; die Sitzung bleibt
char meldung[96]; // (nicht stamp[24]: die Zeile ist laenger)
sprintf(meldung, "ftp_log: ftpOpen %s = %d", host, r); addLog(meldung);
return 0;
}