Hauptmenü

Neueste Beiträge

#1
Codeschnipsel / Abfrage der Radiosender einer ...
Letzter Beitrag von Prof. Dr. Peter Henning - 21 Februar 2026, 05:27:48
Der nachfolgende Code schreibt das in eine Datei presets.xml für Bose-Boxen um. Kann leicht angepasst werden für andere Zwecke.

#!/usr/bin/env perl
use strict;
use warnings;
use utf8;

use Getopt::Long qw(GetOptions);
use LWP::UserAgent;
use URI;
use XML::LibXML;
use XML::Writer;
use IO::File;

# ---------------- CLI ----------------
my $location    = undef;  # Pflicht, z.B. http://192.168.0.254:49000/MediaServerDevDesc.xml
my $out_file    = "presets.xml";
my $start_id    = 1;
my $max_presets = 99;
my $timeout_s   = 12;
my $debug       = 0;

GetOptions(
  "location=s" => \$location,
  "out=s"      => \$out_file,
  "start=i"    => \$start_id,
  "max=i"      => \$max_presets,
  "timeout=i"  => \$timeout_s,
  "debug!"     => \$debug,
) or die "Usage: $0 --location <URL> [--out presets.xml] [--start 1] [--max 99] [--timeout 12] [--debug]\n";

die "Fehlt: --location (z.B. http://192.168.0.254:49000/MediaServerDevDesc.xml)\n"
  if !$location;

# ---------------- Helpers ----------------
sub norm    { my $s = shift // ""; $s =~ s/^\s+|\s+$//g; return $s; }
sub lc_norm { return lc(norm(shift)); }

# ---------------- HTTP Client ----------------
my $ua = LWP::UserAgent->new(
  agent   => "fritz-favs-to-presets/1.0",
  timeout => $timeout_s,
);
$ua->env_proxy;

# ---------------- 1) Device Description holen ----------------
my $desc_res = $ua->get($location);
die "GET DeviceDesc fehlgeschlagen: " . $desc_res->status_line . "\n"
  if !$desc_res->is_success;

my $desc_xml = $desc_res->decoded_content;
my $desc_doc = XML::LibXML->load_xml(string => $desc_xml);

# Base-URL für relative controlURL
my $loc_uri = URI->new($location);
my $base = $loc_uri->scheme . "://" . $loc_uri->host_port;

# UDN (ohne uuid:)
my ($udn_node) = $desc_doc->findnodes('//*[local-name()="UDN"]');
my $udn = $udn_node ? norm($udn_node->textContent) : "";
$udn =~ s/^uuid://i;
die "Konnte UDN nicht aus DeviceDesc lesen.\n" if !$udn;

# ContentDirectory Service finden -> controlURL
my ($cd_service) = $desc_doc->findnodes(
  '//*[local-name()="service"]/*[local-name()="serviceType" and contains(., "ContentDirectory:1")]/..'
);
die "Kein ContentDirectory:1 Service in DeviceDesc gefunden.\n" if !$cd_service;

my ($control_node) = $cd_service->findnodes('./*[local-name()="controlURL"]');
die "Kein controlURL im ContentDirectory Service.\n" if !$control_node;

my $control_url = norm($control_node->textContent);
$control_url = $base . $control_url if $control_url =~ m{^/};

print "Using FRITZ!Box ContentDirectory controlURL: $control_url\n" if $debug;
print "sourceAccount: $udn/0\n" if $debug;

# ---------------- 2) SOAP Browse ----------------
sub soap_browse {
  my (%p) = @_;
  my $object_id = $p{ObjectID}   // "0";
  my $flag      = $p{BrowseFlag} // "BrowseDirectChildren";

  my $body =
      qq{<?xml version="1.0" encoding="utf-8"?>\n}
    . qq{<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">\n}
    . qq{  <s:Body>\n}
    . qq{    <u:Browse xmlns:u="urn:schemas-upnp-org:service:ContentDirectory:1">\n}
    . qq{      <ObjectID>$object_id</ObjectID>\n}
    . qq{      <BrowseFlag>$flag</BrowseFlag>\n}
    . qq{      <Filter>*</Filter>\n}
    . qq{      <StartingIndex>0</StartingIndex>\n}
    . qq{      <RequestedCount>0</RequestedCount>\n}
    . qq{      <SortCriteria></SortCriteria>\n}
    . qq{    </u:Browse>\n}
    . qq{  </s:Body>\n}
    . qq{</s:Envelope>\n};

  my $req = HTTP::Request->new(POST => $control_url);
  $req->header('Content-Type' => 'text/xml; charset="utf-8"');
  $req->header('SOAPACTION'   => '"urn:schemas-upnp-org:service:ContentDirectory:1#Browse"');
  $req->content($body);

  my $res = $ua->request($req);
  if (!$res->is_success) {
    die "SOAP Browse fehlgeschlagen (ObjectID=$object_id): " . $res->status_line . "\n"
      . $res->decoded_content . "\n";
  }

  my $soap = XML::LibXML->load_xml(string => $res->decoded_content);

  # <Result> enthält DIDL-Lite als escaped XML
  my ($result_node) = $soap->findnodes('//*[local-name()="Result"]');
  my $result = $result_node ? $result_node->textContent : "";
  $result = norm($result);

  return $result;
}

# ---------------- 3) DIDL-Lite parsen ----------------
sub parse_didl {
  my ($didl_str) = @_;
  return ([], []) if !$didl_str;

  my $didl_doc = XML::LibXML->load_xml(string => $didl_str);

  my @containers;
  for my $c ($didl_doc->findnodes('//*[local-name()="container"]')) {
    my $id = $c->getAttribute("id") // "";
    my ($t) = $c->findnodes('./*[local-name()="title"]');
    my $title = $t ? norm($t->textContent) : "";
    push @containers, { id => norm($id), title => $title };
  }

  my @items;
  for my $it ($didl_doc->findnodes('//*[local-name()="item"]')) {
    my $id = $it->getAttribute("id") // "";
    my ($t) = $it->findnodes('./*[local-name()="title"]');
    my $title = $t ? norm($t->textContent) : "";

    my ($art) = $it->findnodes('.//*[local-name()="albumArtURI"]');
    my $albumart = $art ? norm($art->textContent) : "";

    push @items, { id => norm($id), title => $title, art => $albumart };
  }

  return (\@containers, \@items);
}

# ---------------- 4) Debug: Container/Items auflisten ----------------
sub dump_children_one_level {
  my ($parent_id, $limit_items) = @_;
  $limit_items //= 15;

  my $didl = soap_browse(ObjectID => $parent_id, BrowseFlag => "BrowseDirectChildren");
  my ($containers, $items) = parse_didl($didl);

  print "\n== Children of $parent_id ==\n";
  print "Containers: " . scalar(@$containers) . "\n";
  for my $c (@$containers) {
    printf "  [C] %-45s  id=%s\n", ($c->{title} // ""), ($c->{id} // "");
  }

  print "Items: " . scalar(@$items) . "\n";
  my $max = @$items < $limit_items ? @$items : $limit_items;
  for (my $i=0; $i<$max; $i++) {
    my $it = $items->[$i];
    printf "  [I] %-45s  id=%s\n", ($it->{title} // ""), ($it->{id} // "");
  }
}

# ---------------- 5) BFS: Container per Titel finden ----------------
sub find_container_bfs {
  my (%p) = @_;
  my $root      = $p{root}      // "0";
  my $want_re   = $p{want_re};              # regex auf lowercased title
  my $max_nodes = $p{max_nodes} // 12000;

  my @q = ($root);
  my %seen;
  my $visited = 0;

  while (@q) {
    my $cur = shift @q;
    next if $seen{$cur}++;
    last if ++$visited > $max_nodes;

    my $didl = soap_browse(ObjectID => $cur, BrowseFlag => "BrowseDirectChildren");
    my ($containers, undef) = parse_didl($didl);

    for my $c (@$containers) {
      my $t = lc_norm($c->{title});
      return $c->{id} if defined($want_re) && $t =~ $want_re;
      push @q, $c->{id};
    }
  }
  return undef;
}

# ---------------- 6) Fallback: "beste Senderliste" finden ----------------
# Heuristik: Container mit vielen Items und wenigen Subcontainern
sub find_best_station_list_container {
  my (%p) = @_;
  my $root      = $p{root}      // "0";
  my $max_nodes = $p{max_nodes} // 20000;

  my @q = ($root);
  my %seen;
  my $visited = 0;

  my $best_id    = undef;
  my $best_score = -1;
  my $best_items = 0;
  my $best_conts = 0;

  while (@q) {
    my $cur = shift @q;
    next if $seen{$cur}++;
    last if ++$visited > $max_nodes;

    my $didl = soap_browse(ObjectID => $cur, BrowseFlag => "BrowseDirectChildren");
    my ($containers, $items) = parse_didl($didl);

    my $num_items = scalar(@$items);
    my $num_conts = scalar(@$containers);

    # Favoritenlisten sind oft: viele Items, wenig Untercontainer
    my $score = $num_items * 10 - $num_conts;

    if ($num_items > 0 && $score > $best_score) {
      $best_score = $score;
      $best_id    = $cur;
      $best_items = $num_items;
      $best_conts = $num_conts;
    }

    # weiter runter
    for my $c (@$containers) {
      push @q, $c->{id};
    }
  }

  print "Fallback best container: $best_id (items=$best_items containers=$best_conts score=$best_score)\n"
    if $debug && defined $best_id;

  return $best_id;
}

# ---------------- 7) MAIN: Radio -> Favoriten -> Items ----------------
# 7.1: Radio/Internetradio Container finden (global)
my $radio_id = find_container_bfs(
  root      => "0",
  want_re   => qr/(internet\s*radio|internetradio|radio)/,
  max_nodes => 20000
) or die "Konnte keinen Container 'Internetradio/Radio' finden.\n";

print "Radio container found: $radio_id\n" if $debug;
dump_children_one_level($radio_id) if $debug;

# 7.2: Favoriten darunter finden (per Titel), sonst Fallback (beste Liste)
my $fav_id = find_container_bfs(
  root      => $radio_id,
  want_re   => qr/(favorit|favorite|lieblings|favourites)/,
  max_nodes => 20000
);

if (!$fav_id) {
  print "Kein Favoriten-Container per Titel gefunden – nutze Fallback (Container mit den meisten Sender-Items)...\n"
    if $debug;
  $fav_id = find_best_station_list_container(root => $radio_id, max_nodes => 30000);
}

die "Konnte keine Senderliste unterhalb von '$radio_id' finden.\n" if !$fav_id;

print "Station list container: $fav_id\n" if $debug;
dump_children_one_level($fav_id) if $debug;

# 7.3: Items (Sender) direkt aus Favoritencontainer lesen
my $fav_didl = soap_browse(ObjectID => $fav_id, BrowseFlag => "BrowseDirectChildren");
my (undef, $items) = parse_didl($fav_didl);

die "Keine Sender-Items im Container ($fav_id) gefunden.\n" if !@$items;

# ---------------- 8) presets.xml schreiben ----------------
my $fh = IO::File->new(">$out_file") or die "Kann $out_file nicht schreiben: $!\n";
binmode($fh, ":utf8");
my $w = XML::Writer->new(OUTPUT => $fh, DATA_MODE => 1, DATA_INDENT => 2, ENCODING => "UTF-8");

my $now = time();
$w->xmlDecl("UTF-8");
$w->startTag("presets");

my $pid   = int($start_id);
my $count = 0;

for my $st (@$items) {
  last if $count >= $max_presets;

  my $name = $st->{title} || "Unbenannt";
  my $oid  = $st->{id};
  next if !$oid;

  $w->startTag("preset", id => $pid, createdOn => $now, updatedOn => $now);

  $w->startTag("ContentItem",
    source        => "STORED_MUSIC",
    location      => $oid,
    sourceAccount => $udn . "/0",
    isPresetable  => "true",
  );

  $w->dataElement("itemName", $name);
  $w->dataElement("containerArt", $st->{art}) if $st->{art};

  $w->endTag("ContentItem");
  $w->endTag("preset");

  $pid++;
  $count++;
}

$w->endTag("presets");
$w->end();
$fh->close();

print "OK: $count Sender nach $out_file geschrieben.\n";
exit 0;

#2
Multimedia / Aw: [Neues Modul] BOSE SoundTo...
Letzter Beitrag von Prof. Dr. Peter Henning - 21 Februar 2026, 05:22:33
Zitat von: JoWiemann am 20 Februar 2026, 18:47:11welche Variable soll das denn sein?
Ihr hattet doch etwas geschrieben von $returnListMediaServers, oder $info. Beides wurde im BOSEST-Modul in keiner Weise angerührt, das Modul im FHEM-repository seit ziemlich langer Zeit überhaupt nicht verändert.

Zitat von: JoWiemann am 20 Februar 2026, 18:51:05dass würde mich interessieren. Ggf. kann ich das ja in das FritzBox Modul übernehmen.
Gerne doch => https://forum.fhem.de/index.php?topic=143976.0

LG

pah
#3
FRITZ!Box / Aw: 72_FRITZBOX.pm ab Version...
Letzter Beitrag von Prof. Dr. Peter Henning - 21 Februar 2026, 05:14:03
Zitat von: JoWiemann am 20 Februar 2026, 18:05:35Was  ich gefunden habe ist, im 98_BOSEST.pm gibt es $info->{info}
Sorry, das ist eine _lokale_ Variable, die kann mit gar nichts kollidieren.

Auch die Zeilen mit dem listMediaServer-Kram enthalten nur lokale Variablen.

LG

pah
#4
Sonstige Systeme / Aw: [gelöst] SIGNALduino - war...
Letzter Beitrag von Nobbynews - 21 Februar 2026, 04:47:46
Master-Branch ist ja jetzt auch aktualisiert.
Vielen Dank.

Norbert
#5
DOIF / Aw: set "lampe 1 bis 3" ?
Letzter Beitrag von Per - 21 Februar 2026, 00:25:33
In fhem kannst du das machen, auch
 Lampe.
würde gehen. Was MQTT draus macht, ist was ganz anderes.
#6
DOIF / Aw: set "lampe 1 bis 3" ?
Letzter Beitrag von TubeHead - 20 Februar 2026, 23:47:27
Danke Dir  8)
... immer drum rum geeiert ... Aber so ist das manchnal ^^

Ähhh... ne... geit net... Gerade mal bei einem MQTT-"machmal" getestet:

(set MQTT publish pixel[1-3]/custom/TMP {"icon":"tmp_ani_out_01","text":"[TMP]"})
generiert ein "Gerät" /pixel[1-3]


Gugst Du, was da so luschtiges auf dem MQTT-Explorer auftaucht... habe also schon reichlich Kombinationen durch  :))  :))  :))
#7
Automatisierung / Aw: Befehl an Shelly erneut se...
Letzter Beitrag von Hadl - 20 Februar 2026, 22:51:59
Zitat von: Starkstrombastler am 15 Februar 2026, 10:55:16Wenn der Befehl von Fhem an den Shelly nicht erfolgreich war, wird der State im Shelly-Device auf Error gesetzt. Dies kann durch ein Notify abgefragt werden um ein erneutes Senden des Befehls auszulösen.
Stimmt, aber im Notify auf "Error" weis ich den Befehl ja leider nichtmehr der fehlgeschlagen ist. Ich müsste erst einen Soll/Ist vergleich machen um den Befehl nochmals zu senden. Das ist relativ hoher Programieraufwand und ich wollte schauen ob es nicht einfacher geht.
Zitat von: Starkstrombastler am 15 Februar 2026, 10:55:16Da aber das Heizen mit einem Heizstab durchaus sicherheitsrelevant ist, sollte hier aber die Strategie geändert werden: statt mehrfach Ausschaltbefehle zu senden (in der Hoffnung, dass dies irgendwann funktioniert) sollte besser das Einschalten auf Gerätebasis zeitlich begrenzt werden und bedarfsweise nachgetriggert werden.
Ja, Sicherheit ist hier wirklich wichtig. Mein Heizstab hat eine Sicherheitsabschaltung mit separaten Relais bei Übertemperatur, eine "normale" Abschaltung per Thermostat einstellbarer Temperatur mit Relais. Und eben nun auch noch die Schaltung durch den Shelly über fhem Logik. Wenn das Ausschalten nicht klappt heizt mein Warmwasser dauerhaft immer auf ca. 60°C
Zitat von: Starkstrombastler am 15 Februar 2026, 10:55:16Für das zeitliche Begrenzen bietet sich auf Shelly-Modul-Basis der Befehl "on-for-timer" an. Dieser setzt den Shelly-internen Timer und kann mit neuem "on-for-timer" nachgetriggert werden. Ein eventuell auf dem Shelly eingerichteter Ausschalttimer wird dabei ignoriert. Der Shelly schaltet dann bei Ablauf der Zeitspanne aus - unabhängig von Fhem und Netzwerk.
Ja, damit krieg ich auf jeden Fall einen "timeout" beim Abschalten hin, muss dann nur schauen das ich den Befehl oft genug wiederhole. Aktuell sende ich mit einen DOIF nur Befehle bei einer Änderung direkt aus dem DOIF an das Shelly Device.
Zitat von: jkriegl am 15 Februar 2026, 19:01:00Hat der Shelly eine feste IP-Adresse?
Hatte ein vergleichbares Online-Problem mit Schalten einer Zirkulationspumpe. Seit 3 Mon. nicht mehr aufgetreten.
Ja, hat er.
Das Problem kommt aber sicherlich von der WLAN Qualität am Standort. Ich sehe das es der Shelly mit der geringsten RSSI im Haus ist, und auch der "disconnects" counter vom Device zählt manchmal munter hoch.
Ich werde das Problem auf jedem Fall mit nem besseren WLAN dort eindämmen.

Hat jemand von euch eine automatische Befehlswiederholung, oder einen Soll/Ist Vergleich mit Befehl bei Abweichung implementiert?
Bei den Homematic Geräten kenne ich sowas ähnliches als "CMDs_pending"

Viele Grüße

Hadl



#8
DOIF / Aw: set "lampe 1 bis 3" ?
Letzter Beitrag von Otto123 - 20 Februar 2026, 22:45:49
nahe dran: lampe[1-3] :)
#9
DOIF / Aw: Wie gestalte ich die Bedin...
Letzter Beitrag von Damian - 20 Februar 2026, 21:58:51
Thema wegen zu vielen Off-topics geschlossen. Funktionierende Lösungen wurden bereits aufgezeigt.
#10
FHEM Development / HttpUtils_NonblockingGet: Bild...
Letzter Beitrag von Dr. Boris Neubert - 20 Februar 2026, 21:54:57
Hallo,

ich will mit HttpUtils_NonblockingGet ein Bild hochladen. Diesen Beitrag kenne ich.

Auf der Kommandozeile geht es so mit curl:
curl -s -X POST 'http://10.21.2.143/doUpload?dir=%2Fimage%2F' -H 'Content-Type: multipart/form-data'  -F "file=@sensorfeed.jpg;filename=fhem.jpg"
Zurück kommt dann HTML-Kode.

Das ist mein versuchtes Äquivalent in FHEM mit Perl:

    HttpUtils_NonblockingGet({
      loglevel => 1,
      url => $url,
      method => "POST",
      hideurl => 0,
      noshutdown => 1,
      #data => { 'file' => $image, 'filename' => "fhem.jpg" },
      data => $image,
      header => { 'Content-Type' => "multipart/form-data",
      },
      callback => \&push2giftv_callback,
    });

$image enthält das Bild im JPG-Format (Binärdaten, kein Dateiname!).

Die Callback-Funktion meint dazu empty answer received.

curl sendet folgendes:

POST /doUpload?dir=%2Fimage%2F HTTP/1.1
Host: localhost:9999
User-Agent: curl/8.14.1
Accept: */*
Content-Length: 3566
Content-Type: multipart/form-data; boundary=------------------------Lrzt5LS0vsj95Lw7zOnH4L

--------------------------Lrzt5LS0vsj95Lw7zOnH4L
Content-Disposition: form-data; name="file"; filename="fhem.jpg"
Content-Type: image/jpeg

����JFIF``��>CREATOR: gd-jpeg v1.0 (using IJG JPEG v62), default quality
��C             





▒▒ $.' ",#(7),01444'9=82<.342��C


▒2!!22222222222222222222222222222222222222222222222222���"��

���}!1AQa"q2��#B��R��$3br�
▒▒%&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz���������������������������������������������������������������������������

���w!1AQaq"2B����       #3R�br�
$4�%�▒▒&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz��������������������������������������������������������������������������
                                                                                                                            ?������#ލ�_+[�m-�P�ŭ�\����1$���_��������4M���9�,nV9$(.���c;VU�������魣i�6�]Ć�C�1�
                                                                  ���q�Ws�iP��3Ԟ�W���N��s▒��U�%���aj:]�h�Za�HMɵ�
                                                                                                                �Wd}���P��3�`���EPEPEPEPEPEPEPEPEPEPEPEP����bk�Z���}�Q'��n�v�?*~���ck?�G���S�߷��.s��v
ˢ�7a�����-n�ߴM��6�      ���a�i�7Я�I⏶��>dz�`�Ǜ�'l��>n��\�ѿ�����D�+Awj���!;��0(▒p}j����>��afֶQ�ga$�l�HF2͵F�Sֱ���(��(��(��(��(��(��(��(��(��(��(��(�     ���/�㵴���C�D&���z��n������2J���,��}�5J/3��c|�&rs�8��+��?xe�    ▒I��̗�V�(;#��$�t�
                                                                                        ���X���Z~������1������c�^��5�������|� ~�+��|��It�;G�z�3���<1�\[�4V����"��A,��匝�=�5�Yy���x��9��/���S��ۏ¨x^a$~)�������?=��>T_�FO@(��x<1�\[�4V����"��A,��匝�=�5�<�qq$�i▒�q�s]7��G��Q�l�\}�s��̣�E�d`d�b�0��&�S�M�JR
                 �I"Ɖ���b�ny�A��Nn.�]�#�3��8
                                            �s�ElK)��uq0<����"W���T�"�����v��$��▒L�i��v��/�s���{�����0&)#�dG�]���DZ�g�5+kO����iS�
z1�;���1]Ě}�ƃ��6;��o�����̎��uUpOs������ϊ<Od�y���nn�����$����O `
                                                              du�>��(��(��(��(��(��(��(��(��(֝�]iZ�7��amѹEm��
姘#�癍���*��7Ri�i�.m"��H�����*��w�=Z����.�G Q�^P�7����c��se,6��³i��l_@Y �Z(�▒�ݲ^$,ix�\걮w�▒��:b����c��se,6��³i��l_@Y"rz��Z(���}���m$j��bhQ�|����qڧ��.�k�˪�w��P���I�#▒����+&��}�\j�M�9g����PO�V.<M��ZIm5���BJ�4H��ws���EQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEU������c�_1�pFp��[x_Ѭ�$��[Q$��-�f1����{.{�▒w������▒�<�:_�7n�6y���o���4��]��-��▒j6�j�o�$E�����@��W;@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@�˛[MN�����������6y�����j���c��Kx-vc��o���|�IRy%[��T��8_��d���TW�[�@-�xu▒�2���`�p0�G'p皥Et�x�Z▒&��\j�
  4��LJ��(9
           #�z�����t�L{�Q�)[�▒����#�T��ukR�.5]F����O;�l
                                                       �`:
�EQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEV�����bIUcDpK?P~��V!�g�i\�e$�Χ=��c�����k;}�1���6�2�3#3.ᰒ1���[�+���?������Bntԙ�i���=��(����
(��
(��
(��
(��
(��
(��
(��
(��
(��
(��
(��
(��
���>b�|:��l<?wqI$!▒���3���T�#y;x,-lMU▒1�k_�/���!�r���k���[�J�9ax�U6�䃜�A\����Đ��b�����]5�G��8Z���+N6�����tQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE��
--------------------------Lrzt5LS0vsj95Lw7zOnH4L--


Und da verließen sie ihn.

Wie kann ich mit HttpUtils ein Bild per Formular wie mit curl hochladen?

Viele Grüße
Boris