ftp_browse.tc¶
ftp_browse.tc — Dateien auf einem FTP-Server (FRITZ!NAS, Synology, …)
// @name: FTP-Browser (FRITZ!NAS)
// @info: https://github.com/gemu2015/Sonoff-Tasmota/blob/universal/tasmota/tinyc/TinyC_Reference_DE.md#ftp-client-dateien-auf-dem-nas
// ─────────────────────────────────────────────────────────────────────
// ftp_browse.tc — Dateien auf einem FTP-Server (FRITZ!NAS, Synology, …)
// von der Tasmota-Konsole aus ansehen, holen, ablegen und aufraeumen.
// Das Gegenstueck zu ftp_log.tc: dort schreibt das Geraet, hier liest es.
//
// Zugangsdaten aus /ftp.cfg auf dem Geraet (vier Zeilen):
// 192.168.178.1 Host (IP, nicht fritz.box — siehe Handbuch)
// esp Benutzer mit NAS-Zugang
// geheim Passwort
// /FRITZ/mediabox Startverzeichnis (bei ftp_log: die Zieldatei)
//
// Konsole (Praefix FTP), Pfade ohne fuehrenden Schraegstrich sind relativ
// zum Startverzeichnis:
// FTPLS [verz] Namen im Verzeichnis (Fritzbox: volle Pfade)
// FTPCAT datei Textdatei anzeigen (die ersten 1000 Zeichen)
// FTPSIZE datei Groesse in Bytes
// FTPGET datei [lokal] vom Server auf die SD-Karte/Flash holen
// FTPPUT lokal datei lokale Datei auf den Server legen (ersetzen)
// FTPDEL datei loeschen
// FTPMKDIR verz Verzeichnis anlegen
// FTPREN von nach umbenennen / verschieben
// FTPCD verz Startverzeichnis wechseln (bis Neustart)
// FTPOPEN neu anmelden
//
// ⚠️ Die Arbeit tut TaskLoop(), nicht Command(): Command() laeuft auf der
// Hauptschleife, und dort hielte eine Uebertragung den VM-Mutex — Web und
// alle Slots stuenden still. Command() merkt sich nur den Auftrag; das
// Ergebnis kommt als Logzeilen in die Konsole.
// ⚠️ EINE FTP-Sitzung je Geraet: nicht gleichzeitig mit ftp_log.tc in
// einem anderen Slot laufen lassen, beide teilen sich die Verbindung.
// ─────────────────────────────────────────────────────────────────────
char host[64] = "192.168.178.1";
char user[32] = "esp";
char pw[32] = "geheim";
char basis[96] = "/FRITZ/mediabox";
char auftrag[16]; // was TaskLoop tun soll ("" = nichts)
char a1[128]; // erstes Argument, schon mit Basis zum vollen Pfad
char a2[128]; // zweites Argument
char puffer[1024]; // Listen und Dateiinhalte
char zeile[200];
int offen = -10;
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; }
}
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, basis) > 0) trim(basis);
// ftp_log.tc traegt in Zeile 4 eine DATEI ein; hier zaehlt ihr Verzeichnis
int k = strlen(basis);
if (k > 1 && basis[k - 1] != '/') {
int i = k - 1;
while (i > 0 && basis[i] != '/') { i--; }
if (i > 0 && basis[i + 1] != 0) {
// nur abschneiden, wenn hinter dem letzten '/' ein Punkt steht (Dateiname)
int j = i + 1; int punkt = 0;
while (basis[j] != 0) { if (basis[j] == '.') punkt = 1; j++; }
if (punkt) basis[i] = 0;
}
}
return 1;
}
// Argument n (1 = erstes Wort nach dem Befehl) aus "CMD arg1 arg2" holen.
// Von Hand, nicht mit webParse: das liefert fuer einen Index hinter dem
// letzten Segment das LETZTE Segment statt nichts -- "FTPLS" ohne Argument
// bekam so "LS" als Verzeichnis.
int wort(char cmd[], int n, char dst[]) {
dst[0] = 0;
int i = 0;
while (cmd[i] != 0 && cmd[i] != ' ') { i++; } // das Befehlswort
int t = 1;
while (t <= n) {
while (cmd[i] == ' ') { i++; }
if (cmd[i] == 0) return 0;
if (t == n) break;
while (cmd[i] != 0 && cmd[i] != ' ') { i++; }
t++;
}
int k = 0;
while (cmd[i] != 0 && cmd[i] != ' ' && k < 126) { dst[k] = cmd[i]; k++; i++; }
dst[k] = 0;
return k;
}
// relativen Pfad an die Basis haengen
void voll(char p[]) {
if (p[0] == '/' || p[0] == 0) return;
char t[128];
sprintf(t, "%s/%s", basis, p);
strcpy(p, t);
}
// Mehrzeiligen Puffer als einzelne Logzeilen ausgeben (hoechstens max Zeilen)
void zeigen(char text[], int max) {
int i = 0; int k = 0; int zeilen = 0;
while (text[i] != 0 && zeilen < max) {
char c = text[i];
if (c == '\n' || k >= 190) {
zeile[k] = 0;
if (k > 0) addLog(zeile);
k = 0; zeilen++;
} else if (c != '\r') {
zeile[k] = c; k++;
}
i++;
}
if (k > 0 && zeilen < max) { zeile[k] = 0; addLog(zeile); }
}
void TaskLoop() {
delay(200);
if (auftrag[0] == 0) return;
char was[16];
strcpy(was, auftrag);
auftrag[0] = 0;
int r = 0;
if (strcmp(was, "OPEN") == 0) {
offen = ftpOpen(host, user, pw);
sprintf(zeile, "ftp: ftpOpen %s als %s = %d", host, user, offen); addLog(zeile);
} else if (strcmp(was, "LS") == 0) {
r = ftpList(a1, puffer);
sprintf(zeile, "ftp: %s — %d Eintraege", a1, r); addLog(zeile);
if (r > 0) zeigen(puffer, 40);
} else if (strcmp(was, "CAT") == 0) {
r = ftpGetStr(a1, puffer);
sprintf(zeile, "ftp: %s — %d Bytes", a1, r); addLog(zeile);
if (r > 0) zeigen(puffer, 30);
} else if (strcmp(was, "SIZE") == 0) {
r = ftpSize(a1);
sprintf(zeile, "ftp: %s — %d Bytes", a1, r); addLog(zeile);
} else if (strcmp(was, "GET") == 0) {
r = ftpGet(a1, a2);
sprintf(zeile, "ftp: %s -> %s: %d", a1, a2, r); addLog(zeile);
} else if (strcmp(was, "PUT") == 0) {
r = ftpPut(a2, a1, 0);
sprintf(zeile, "ftp: %s -> %s: %d", a1, a2, r); addLog(zeile);
} else if (strcmp(was, "DEL") == 0) {
r = ftpDelete(a1);
sprintf(zeile, "ftp: loeschen %s = %d", a1, r); addLog(zeile);
} else if (strcmp(was, "MKDIR") == 0) {
r = ftpMkdir(a1);
sprintf(zeile, "ftp: anlegen %s = %d", a1, r); addLog(zeile);
} else if (strcmp(was, "REN") == 0) {
r = ftpRename(a1, a2);
sprintf(zeile, "ftp: %s -> %s = %d", a1, a2, r); addLog(zeile);
}
}
void Command(char cmd[]) {
char resp[160];
char c[16];
webParse(cmd, " ", 1, c);
if (auftrag[0] != 0) { responseCmnd("noch beschaeftigt"); return; }
a1[0] = 0; a2[0] = 0;
if (strcmp(c, "LS") == 0) {
wort(cmd, 1, a1); if (a1[0] == 0) strcpy(a1, basis); voll(a1);
strcpy(auftrag, "LS");
} else if (strcmp(c, "CAT") == 0 || strcmp(c, "SIZE") == 0 || strcmp(c, "DEL") == 0 || strcmp(c, "MKDIR") == 0) {
wort(cmd, 1, a1);
if (a1[0] == 0) { responseCmnd("Pfad fehlt"); return; }
voll(a1);
strcpy(auftrag, c);
} else if (strcmp(c, "GET") == 0) {
wort(cmd, 1, a1); wort(cmd, 2, a2);
if (a1[0] == 0) { responseCmnd("FTPGET datei [lokal]"); return; }
voll(a1);
if (a2[0] != 0 && a2[0] != '/') { // lokaler Name ohne "/": davor setzen
char t[128]; sprintf(t, "/%s", a2); strcpy(a2, t);
}
if (a2[0] == 0) { // lokal: "/" + der blosse Dateiname
int i = strlen(a1);
while (i > 0 && a1[i - 1] != '/') { i--; }
a2[0] = '/';
int k = 1;
while (a1[i] != 0 && k < 126) { a2[k] = a1[i]; k++; i++; }
a2[k] = 0;
}
strcpy(auftrag, "GET");
} else if (strcmp(c, "PUT") == 0) {
wort(cmd, 1, a1); wort(cmd, 2, a2);
if (a1[0] == 0 || a2[0] == 0) { responseCmnd("FTPPUT lokal datei"); return; }
voll(a2);
strcpy(auftrag, "PUT");
} else if (strcmp(c, "REN") == 0) {
wort(cmd, 1, a1); wort(cmd, 2, a2);
if (a1[0] == 0 || a2[0] == 0) { responseCmnd("FTPREN von nach"); return; }
voll(a1); voll(a2);
strcpy(auftrag, "REN");
} else if (strcmp(c, "CD") == 0) {
wort(cmd, 1, a1);
if (a1[0] != 0) { voll(a1); strcpy(basis, a1); }
sprintf(resp, "Basis: %s", basis); responseCmnd(resp); return;
} else if (strcmp(c, "OPEN") == 0) {
strcpy(auftrag, "OPEN");
} else {
responseCmnd("FTPLS [verz] | FTPCAT datei | FTPSIZE datei | FTPGET datei [lokal] | FTPPUT lokal datei | FTPDEL | FTPMKDIR | FTPREN von nach | FTPCD verz | FTPOPEN");
return;
}
sprintf(resp, "%s %s %s -> siehe Konsole", c, a1, a2);
responseCmnd(resp);
}
int main() {
auftrag[0] = 0;
if (cfgLesen()) addLog("ftp_browse: /ftp.cfg gelesen"); else addLog("ftp_browse: keine /ftp.cfg, Vorgaben gelten");
tcpConnectTimeout(3000);
addCommand("FTP");
offen = ftpOpen(host, user, pw);
sprintf(zeile, "ftp_browse: ftpOpen %s als %s = %d, Basis %s", host, user, offen, basis); addLog(zeile);
return 0;
}