📂 Datei herunterladen, Downloader, HttpUtils_NonblockingGet

Begonnen von Torxgewinde, 21 November 2025, 23:46:42

Vorheriges Thema - Nächstes Thema

Torxgewinde

Ich wollte ein paar Dateien desöfteren mal mit FHEM herunterladen und per FHEMWEB Webserver verteilen. Dazu habe ich mir ein Snippet erstellt.

Will man zum Beispiel einige Tasmota Dateien herunterladen und speichern:
Falls das Attribut Filepath einen vollständigen Pfad zum Speicherort der Datei angibt, reicht auch nur die URL:
  • attr MyDownloader Filepath /opt/fhem/Pfad/zur/Datei.txt
    set MyDownloader job http://servername/dateiname.txt

defmod MyDownloader dummy
attr MyDownloader userattr Filepath
attr MyDownloader readingList job
attr MyDownloader devStateIcon {\
\
#Lade mehrere Dinge herunter, mach einfach großzügige Pausen:\
my $cmd = <<"CMD";;\
set $name job Filepath="/opt/fhem/www/tasmota.bin" https://ota.tasmota.com/tasmota/release/tasmota.bin;; sleep 5;;\
set $name job Filepath="/opt/fhem/www/tasmota.bin.gz" https://ota.tasmota.com/tasmota/release/tasmota.bin.gz;; sleep 5;;\
set $name job Filepath="/opt/fhem/www/tasmota-minimal.bin.gz" https://ota.tasmota.com/tasmota/release/tasmota-minimal.bin.gz;; sleep 5;;\
set $name job Filepath="/opt/fhem/www/tasmota32-zbbrdgpro.bin" https://ota.tasmota.com/tasmota32/release/tasmota32-zbbrdgpro.bin\
CMD\
\
#Zeichen für GET-Request tauschen, URL-encoden:\
$cmd =~ s/\n/ /g;;\
$cmd =~ s/\s+/ /g;;\
$cmd = urlEncode($cmd);;\
$cmd =~ s/\+/%%20/g;;\
\
#einfacher Link zum anklicken in FHEMWEB um den CMD oben auszulösen:\
#my $link = "<a href=\"${FW_ME}?XHR=1${FW_CSRF}&cmd=${cmd}\">📂</a>";;\
\
#Mit JS wird verhindert, dass der Browser navigiert:\
my $link = "<a href=\"#\" onclick=\"fetch('${FW_ME}?XHR=1${FW_CSRF}&cmd=${cmd}');; return false;;\">📂</a>";;\
\
return "<div>". ReadingsVal($name, "result", "???") ." $link</div>";;\
}
attr MyDownloader setList job
attr MyDownloader userReadings result:job:.* {\
    my $hash = $defs{$name};;\
    my $job = ReadingsVal($name, "job", 'https://fhem.de/www/images/default/fhemicon.png');;\
    my $path = AttrVal($name, "Filepath", "/opt/fhem/www/download.bin");;\
    \
    if ($job =~ s/(?:Filepath|Dateipfad)="(.*?)"//) {\
        $path = $1;;\
    }\
    \
    my $fnFct = sub {\
        my ($param, $err, $data) = @_;;\
        \
        my $len = $data ? length($data) : 0;;\
        my $error = $err ? $err : FileWrite($path, $data);;\
        \
        readingsBeginUpdate($hash);;\
        readingsBulkUpdate($hash, "busy", 0, 1);;\
        readingsBulkUpdate($hash, "result", "saved $len Bytes", 1) if !$error;;\
        readingsBulkUpdate($hash, "result", "error ($error, $err)", 1) if $error;;\
        readingsEndUpdate($hash, 1);;\
    };;\
    \
    if ( ReadingsVal($name, "busy", 0) == 0 ) {\
        readingsBulkUpdate($hash, "busy", 1, 1);;\
        $job =~ s/^\s+|\s+$//g;;\
        my $param = {\
                    url        => $job,\
                    timeout    => 10,\
                    hash       => $hash,\
                    method     => "GET",\
                    callback   => $fnFct\
                };;\
        HttpUtils_NonblockingGet($param);;\
        return "download job started";;\
    }\
    \
    return;;\
}

Edit #1: Kleines Extra: Klickt man im Status auf das Ordner-Emoji, so werden mehrere Tasmota-Firmware-Dateien nacheinander heruntergeladen. Mittels Javascript wir verhindert, dass der Browser die Seite verlässt. Der Link wird mit FHEMWEB Variablen zusammengebaut, damit er nicht an jede Installation angepasst werden muss.

Viel Spaß!

So sieht es aus, wenn man die Tasmota-Firmware-Dateien herunterlädt:
Du darfst diesen Dateianhang nicht ansehen.

Torxgewinde

Hi,
Ich habe es noch um eine Queue erweitert, damit Downloads gesammelt und nacheinander abgearbeitet werden. So braucht man dann nicht mit vermuteten "sleep"-Zeiten arbeiten:

defmod MyDownloader dummy
attr MyDownloader userattr Filepath
attr MyDownloader devStateIcon {\
\
#Lade mehrere Dinge herunter, füllt ggf. das Backlog:\
my $cmd = <<"CMD";;\
set $name job Filepath="/opt/fhem/www/tasmota.bin" https://ota.tasmota.com/tasmota/release/tasmota.bin;;\
set $name job Filepath="/opt/fhem/www/tasmota.bin.gz" https://ota.tasmota.com/tasmota/release/tasmota.bin.gz;;\
set $name job Filepath="/opt/fhem/www/tasmota-minimal.bin.gz" https://ota.tasmota.com/tasmota/release/tasmota-minimal.bin.gz;;\
set $name job Filepath="/opt/fhem/www/tasmota32-zbbrdgpro.bin" https://ota.tasmota.com/tasmota32/release/tasmota32-zbbrdgpro.bin\
CMD\
\
#Zeichen für GET-Request tauschen, URL-encoden:\
$cmd =~ s/\n/ /g;;\
$cmd =~ s/\s+/ /g;;\
$cmd = urlEncode($cmd);;\
$cmd =~ s/\+/%%20/g;;\
\
#einfacher Link zum anklicken in FHEMWEB um den CMD oben auszulösen:\
#my $link = "<a href=\"${FW_ME}?XHR=1${FW_CSRF}&cmd=${cmd}\">📂</a>";;\
\
#Mit JS wird verhindert, dass der Browser navigiert:\
my $link = "<a href=\"#\" onclick=\"fetch('${FW_ME}?XHR=1${FW_CSRF}&cmd=${cmd}');; return false;;\">📂</a>";;\
\
return "<div>". ReadingsVal($name, "result", "???") ." $link</div>";;\
}
attr MyDownloader readingList job
attr MyDownloader setList job
attr MyDownloader userReadings result:job:.* {\
my $hash = $defs{$name};;\
my $job = ReadingsVal($name, "job", 'https://fhem.de/www/images/default/fhemicon.png');;\
my $path = AttrVal($name, "Filepath", "/opt/fhem/www/download.bin");;\
\
if ($job =~ s/(?:Filepath|Dateipfad)="(.*?)"//) {\
$path = $1;;\
}\
\
# Entferne Whitespaces:\
$job =~ s/^\s+|\s+$//g;;\
\
# Wenn busy aktiv ist, dann den Job ins Backlog schieben:\
if (ReadingsVal($name, "busy", 0) != 0) {\
my $backlog = ReadingsVal($name, "backlog", "");;\
$backlog .= "Filepath=\"${path}\" ${job}\n";;\
readingsBulkUpdate($hash, "backlog", $backlog, 1);;\
return "queued job ($job)";;\
}\
\
# ab hier sind wir busy mit dem request:\
readingsBulkUpdate($hash, "busy", 1, 1);;\
\
# definiere Callback:\
my $fnFct = sub {\
my ($param, $err, $data) = @_;;\
\
my $len = $data ? length($data) : 0;;\
my $error = $err ? $err : FileWrite($path, $data);;\
\
readingsBeginUpdate($hash);;\
readingsBulkUpdate($hash, "result", "saved $len Bytes to $path", 1) if !$error;;\
readingsBulkUpdate($hash, "result", "ERROR ($error|$err|$path|$job)", 1) if $error;;\
\
# prüfe das Backlog:\
my $backlog = ReadingsVal($name, "backlog", "");;\
my @lines = grep { /\S/ } split(/\r?\n/, $backlog);;\
\
if (@lines) {\
my $next = shift @lines;;  # ersten Job holen\
my $newbacklog = join("\n", @lines);;\
readingsBulkUpdate($hash, "backlog", $newbacklog, 1);;\
\
# Nächsten Job starten wenn diese Funktion endet,\
# deswegen über "@cmdList","%sleepers" in fhem.pl\
fhem("sleep 0.0;; set $name job $next");;\
}\
\
readingsBulkUpdate($hash, "busy", 0, 1);;\
readingsEndUpdate($hash, 1);;\
};;\
\
# prepare and call HttpUtils:\
my $param = {\
url        => $job,\
timeout    => 10,\
hash       => $hash,\
method     => "GET",\
callback   => $fnFct\
};;\
HttpUtils_NonblockingGet($param);;\
\
return "download job started ($job)";;\
}

Zum testen liegt es auch bei Cooltux: https://demo-fhem.cooltux.net/fhem?detail=MyDownloader