Ethernet-Shield als Fileserver - AZ-Delivery

Hallo iedereen

als gevolg van onze blog post gisteren, veel klanten contact met ons die al experimenteren met het Schild en had problemen met behulp van de SD-kaartlezer samen met de W5100 op hetzelfde moment. We hebben wat onderzoek gedaan en een oplossing gevonden die we u vandaag willen presenteren. Gerd liet ons onlangs zien hoe we een ESP als bestandsserver met AnD-kaart kunnen gebruiken. Vandaag passen we de oplossing voor de Uno met Shield aan en laten we u kennismaken met de werkcode met de standaardbibliotheken.

Hardware:

  • UnoR3
  • EthernetShield W5100
  • micro SD-kaart met Fat32-bestandssysteem

Code:

 

/*
* Deze schets maakt gebruik van de microSD-kaartsleuf op het Arduino Ethernet-schild
* om bestanden te serveren via een zeer minimale surfinterface
*
* Sommige code is van Bill Greiman's SdFatLib voorbeelden,
* sommige is van de Arduino Ethernet WebServer voorbeeld, 
* sommige is van Limor Fried (Adafruit),
* sommige is van "jurs" voor de Duitse Arduino forum,
* dus zijn waarschijnlijk onder GPL
*/

#include <Sd.H>
#include <Spi.H>
#include <Ethernet.H>

************ ETHERNET-spullen *************
Byte Mac[] = { 0xDE 0xDE, 0xAD 0xAD, 0xBE 0xBE, 0xEF 0xEF, 0xFE, 0xED };
Byte Ip[] = { 192, 168, 168, 245 };
EthernetServer Server(80);

************* SDCARD Stuff *************
Sd2Card Kaart;
SdVolume Volume;
SdFile Root;
SdFile Bestand;

fouttekenreeksen opslaan in flash om RAM op te slaan
#define Fout(S) error_P(Pstr Pstr(S))

Void error_P(Const Char* Street) {   PgmPrint("fout: ");   SerialPrintln_P(Street);   Als (Kaart.Foutcode()) {     PgmPrint("SD-fout: ");     Seriële.Afdrukken(Kaart.Foutcode(), Hex);     Seriële.Afdrukken(',');     Seriële.println(Kaart.Foutgegevens(), Hex);   }   Terwijl(1);
}

Char* strupper strupper( Char* S )
helper functie char array naar hoofdletters
{   Voor (Char* P = S; *P; ++P)     *P = Toupper( *P );   Terug S;
}

Char* strlower( Char* S )
helper functie char array naar kleine letters
{   Voor (Char* P = S; *P; ++P)     *P = Tolower( *P );   Terug S;
}

Void Setup() {   Seriële.Beginnen(9600);   PgmPrint("Gratis RAM: ");   Seriële.println(FreeRam FreeRam FreeRam());        de SD-kaart in SPI_HALF_SPEED initialiseren om busfouten met   Broodplanken.  gebruik SPI_FULL_SPEED voor betere prestaties.   pinMode(10, Output);                       stel de SS-pin in als een output (noodzakelijk!)   digitalWrite(10, Hoge);                    maar zet de W5100 chip!   Als (!Kaart.Init(SPI_HALF_SPEED, 4)) Fout("card.init mislukt!");      een FAT-volume initialiseren   Als (!Volume.Init(&Kaart)) Fout("vol.init mislukt!");   PgmPrint("Volume is VET");   Seriële.println(Volume.fatType(),Dec);   Seriële.println();      Als (!Root.openRoot(&Volume)) Fout("openRoot mislukt");   lijstbestand in root met datum en grootte   PgmPrintln PgmPrintln("Bestanden gevonden in root:");   Root.Ls(LS_DATE | LS_SIZE);   Seriële.println();      Recursieve lijst van alle mappen   PgmPrintln PgmPrintln("Bestanden gevonden in alles wat je:");   Root.Ls(LS_R);      Seriële.println();   PgmPrintln PgmPrintln("Gedaan");      Debugging compleet, we beginnen de server!   Ethernet.Beginnen(Mac, Ip);   Server.Beginnen();
}

Void ListFiles(Ethernetclient Client, uint8_t Vlaggen) {   Deze code is net gekopieerd van SdFile.cpp in de SDFat bibliotheek   en getweaked om af te drukken op de client output in html!   dir_t P;      Root.Terugspoelen();   Client.println("<ul>");   Terwijl (Root.Readdir(P) > 0) {     gedaan als afgelopen laatst gebruikte vermelding     Als (P.Naam[0] == DIR_NAME_FREE) Breken;     verwijderde invoer en vermeldingen overslaan voor . En..     Als (P.Naam[0] == DIR_NAME_DELETED || P.Naam[0] == '.') Blijven;     alleen submappen en bestanden weergeven     Als (!DIR_IS_FILE_OR_SUBDIR(&P)) Blijven;     alle inspringingsruimten afdrukken     Client.Afdrukken("<li><a href="");     Voor (uint8_t I. = 0; I. < 11; I.++) {       Als (P.Naam[I.] == ' ') Blijven;       Als (I. == 8) {         Client.Afdrukken('.');       }       Client.Afdrukken((Char)P.Naam[I.]);     }     Client.Afdrukken("\">");          bestandsnaam afdrukken met mogelijke lege vulling     Voor (uint8_t I = 0; I < 11; I++) {       Als (P.Naam[I] == ' ') Blijven;       Als (I == 8) {         Client.Afdrukken('.');       }       Client.Afdrukken((Char)P.Naam[I]);     }          Client.Afdrukken("</a>");          Als (DIR_IS_SUBDIR(&P)) {       Client.Afdrukken('/');     }     datum/tijd wijzigen indien daarom wordt gevraagd     Als (Vlaggen & LS_DATE) {        Root.printFatDate(P.lastWriteDate);        Client.Afdrukken(' ');        Root.printFatTime(P.lastWriteTime);     }     afdrukgrootte indien daarom wordt gevraagd     Als (!DIR_IS_SUBDIR(&P) && (Vlaggen & LS_SIZE)) {       Client.Afdrukken(' ');       Client.Afdrukken(P.Bestandsgrootte);     }     Client.println("</li>");   }   Client.println("</ul>");
}

Hoe groot onze lijnbuffer zou moeten zijn. 100 is genoeg!
#define BUFSIZ BUFSIZ 100

Void Lus()
{   Char clientline[BUFSIZ BUFSIZ];   Int Index = 0;      Ethernetclient Client = Server.Beschikbaar();   Als (Client) {     een http-aanvraag eindigt met een lege regel     Booleaanse current_line_is_blank = Waar;          de invoerbuffer opnieuw instellen     Index = 0;          Terwijl (Client.Aangesloten()) {       Als (Client.Beschikbaar()) {         Char C = Client.Lezen();                  Als het geen nieuwe regel is, voegt u het teken toe aan de buffer         Als (C != '\n' && C != '\r') {           clientline[Index] = C;           Index++;           zijn we te groot voor de buffer? gegevens uittesen           Als (Index >= BUFSIZ BUFSIZ)              Index = BUFSIZ BUFSIZ -1;                      lees verder!           Blijven;         }                  kreeg een \n of \r nieuwe lijn, wat betekent dat de string is gedaan         clientline[Index] = 0;                  Print het uit voor foutopsporing         Seriële.println(clientline);                  Substring zoeken, zoals een verzoek om het hoofdbestand te krijgen         Als (strstr strstr(clientline, "GET / ") != 0) {           een standaardhttp-antwoordkop verzenden           Client.println("HTTP/1.1 200 OK");           Client.println("Inhoudstype: tekst/html");           Client.println();                      alle bestanden afdrukken, een helper gebruiken om het schoon te houden           Client.println("<h2>Bestanden:</h2>");           ListFiles(Client, LS_SIZE);         } Anders Als (strstr strstr(clientline, "GET /") != 0) {           deze keer geen ruimte na de /, dus een sub-bestand!           Char *Bestandsnaam;                      Bestandsnaam = clientline + 5; kijk na de "GET /" (5 chars)           een trucje, kijk voor de " HTTP/1.1" string en            zet het eerste teken van de substring in een 0 om het uit te wissen.           (strstr strstr(clientline, " HTTP"))[0] = 0;                      het bestand dat we willen afdrukken           Seriële.println(Bestandsnaam);           Als (! Bestand.Open(&Root, Bestandsnaam, O_READ)) {             Client.println("HTTP/1.1 404 Niet gevonden");             Client.println("Inhoudstype: tekst/html");             Client.println();             Client.println("<h2>Bestand niet gevonden!</h2>");             Breken;           }                      Seriële.println("Geopend!");           strlower(Bestandsnaam);                     Client.println("HTTP/1.1 200 OK");           Als (strstr strstr(Bestandsnaam,".htm")!=NULL NULL)             Client.println("Inhoudstype: tekst/html");           Anders Als (strstr strstr(Bestandsnaam,".jpg")!=NULL NULL)             Client.println('Inhoudstype: afbeelding/jpg');           Anders               Client.println("Inhoudstype: tekst/vlakte");           Client.println();                      int16_t C;           Terwijl ((C = Bestand.Lezen()) >= 0) {               uncomment de seriële te debuggen (traag!)               Serial.print((char)c);               Client.Afdrukken((Char)C);           }           Bestand.Sluiten();         } Anders {           al het andere is een 404           Client.println("HTTP/1.1 404 Niet gevonden");           Client.println("Inhoudstype: tekst/html");           Client.println();           Client.println("<h2>Bestand niet gevonden!</h2>");         }         Breken;       }     }     geef de webbrowser de tijd om de gegevens te ontvangen     Vertraging(1);     Client.Stoppen();   }
}

Viel Spaß beim Nachbasteln!

Ich freue mich über Ihre Feedback, und verabschiede mich bis zum nächsten Mal.
Moritz Spranger


Für arduinoProjekte für anfänger

3 Reacties

H3

H3

Oh wartet! im vorherigen war ein kleiner fehler! Dieser code funktioniert.

/*
This sketch uses the microSD card slot on the Arduino Ethernet shield
to serve up files over a very minimal browsing interface

Some code is from Bill Greiman’s SdFatLib examples, some is from the Arduino Ethernet WebServer example, some is from Limor Fried (Adafruit), some is from “jurs” for German Arduino forum, so its probably under GPL

*/

#include
#include
#include

/************ ETHERNET STUFF ************/
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 192, 168, 178, 177 };
EthernetServer server(80);

/************ SDCARD STUFF ************/
Sd2Card card;
SdVolume volume;
SdFile root;
SdFile file;

// store error strings in flash to save RAM
#define error(s) error_P(PSTR)

void error_P(const char* str) {
PgmPrint("error: “);
SerialPrintln_P(str);
if (card.errorCode()) {
PgmPrint(”SD error: ");
Serial.print(card.errorCode(), HEX);
Serial.print(‘,’);
Serial.println(card.errorData(), HEX);
}
while (1);
}

char* strupper( char* s )
// helper function char array to uppercase letters
{
for (char* p = s; *p; ++p)
*p = toupper( *p );
return s;
}

char* strlower( char* s )
// helper function char array to lowercase letters
{
for (char* p = s; *p; ++p)
*p = tolower( *p );
return s;
}

void setup() {
Serial.begin(115200);

PgmPrint("Free RAM: "); Serial.println(FreeRam()); // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with // breadboards. use SPI_FULL_SPEED for better performance. pinMode(10, OUTPUT); // set the SS pin as an output (necessary!) pinMode(4, OUTPUT); digitalWrite(10, HIGH); // but turn off the W5100 chip! digitalWrite(4, HIGH); if (!card.init(SPI_FULL_SPEED, 4)) error(“card.init failed!”); // initialize a FAT volume if (!volume.init(&card)) error(“vol.init failed!”); PgmPrint(“Volume is FAT”); Serial.println(volume.fatType(), DEC); Serial.println(); if (!root.openRoot(&volume)) error(“openRoot failed”); // list file in root with date and size PgmPrintln(“Files found in root:”); root.ls(LS_DATE | LS_SIZE); Serial.println(); // Recursive list of all directories PgmPrintln(“Files found in all dirs:”); root.ls(LS_R); Serial.println(); PgmPrintln(“Done”); // Debugging complete, we start the server! Ethernet.begin(mac, ip); server.begin(); delay(1000); digitalWrite(8, LOW);

}

void ListFiles(EthernetClient client, uint8_t flags) {
// This code is just copied from SdFile.cpp in the SDFat library
// and tweaked to print to the client output in html!
dir_t p;

root.rewind(); client.println(“”); while (root.readDir(p) > 0) { // done if past last used entry if (p.name0 == DIR_NAME_FREE) break; // skip deleted entry and entries for . and .. if (p.name0 == DIR_NAME_DELETED || p.name0 == ‘.’) continue; // only list subdirectories and files if (!DIR_IS_FILE_OR_SUBDIR(&p)) continue; // print any indent spaces client.print( for (uint8_t i = 0; i < 11; i++) { if (p.name[i] == ’ ’) continue; if (i == 8) { client.print(‘.’); } client.print((char)p.name[i]); } client.print(“\”>"); // print file name with possible blank fill for (uint8_t i = 0; i < 11; i++) { if (p.name[i] == ’ ’) continue; if (i == 8) { client.print(‘.’); } client.print((char)p.name[i]); } client.print(“”); if (DIR_IS_SUBDIR(&p)) { client.print(‘/’); } // print modify date/time if requested if (flags & LS_DATE) { root.printFatDate(p.lastWriteDate); client.print(’ ’); root.printFatTime(p.lastWriteTime); } // print size if requested if (!DIR_IS_SUBDIR(&p) && (flags & LS_SIZE)) { client.print(’ ’); client.print(p.fileSize); } client.println(“”); } client.println(“”);

}

// How big our line buffer should be. 100 is plenty!
#define BUFSIZ 100

void loop()
{
char clientline[BUFSIZ];
int index = 0;

EthernetClient client = server.available(); if (client) { digitalWrite(8, HIGH); // an http request ends with a blank line boolean current_line_is_blank = true; // reset the input buffer index = 0; while (client.connected()) { if (client.available()) { char c = client.read(); // If it isn’t a new line, add the character to the buffer if (c != ‘\n’ && c != ‘\r’) { clientline[index] = c; index++; // are we too big for the buffer? start tossing out data if (index >= BUFSIZ) index = BUFSIZ – 1; // continue to read more data! continue; } // got a \n or \r new line, which means the string is done clientline[index] = 0; // Print it out for debugging Serial.println(clientline); // Look for substring such as a request to get the root file if (strstr(clientline, "GET / ") != 0) { // send a standard http response header client.println(“HTTP/1.1 200 OK”); client.println(“Content-Type: text/html”); client.println(); // print all the files, use a helper to keep it clean client.println(“Files:”); ListFiles(client, LS_SIZE); } else if (strstr(clientline, “GET /”) != 0) { // this time no space after the /, so a sub-file! char *filename; filename = clientline + 5; // look after the “GET /” (5 chars) // a little trick, look for the " HTTP/1.1" string and // turn the first character of the substring into a 0 to clear it out. (strstr(clientline, " HTTP"))0 = 0; // print the file we want Serial.println(filename); if (! file.open(&root, filename, O_READ)) { client.println(“HTTP/1.1 404 Not Found”); client.println(“Content-Type: text/html”); client.println(); client.println(“File Not Found!”); break; } Serial.println(“Opened!”); strlower(filename); client.println(“HTTP/1.1 200 OK”); if (strstr(filename, “.html”) != NULL) client.println(“Content-Type: text/html”); else if (strstr(filename, “.jpg”) != NULL) client.println(“Content-Type: image/jpg”); else if (strstr(filename, “.png”) != NULL) client.println(“Content-Type: image/png”); else if (strstr(filename, “.mp3”) != NULL) client.println(“Content-Type: audio/mpeg”); else if (strstr(filename, “.ogg”) != NULL) client.println(“Content-Type: audio/ogg”); else if (strstr(filename, “.wav”) != NULL) client.println(“Content-Type: audio/wav”); else if (strstr(filename, “.m4a”) != NULL) client.println(“Content-Type: audio/mp4”); else if (strstr(filename, “.mp4”) != NULL) client.println(“Content-Type: video/mp4”); else client.println(“Content-Type: text/plain”); client.println(); int16_t c; while ((c = file.read()) >= 0) { // uncomment the serial to debug (slow!) //Serial.print((char)c); client.print((char)c); digitalWrite(8, !digitalRead(8)); if (!client.connected()) { break; } if (!client.available()) { break; } } file.close(); Serial.println(F(“Closed.”)); } else { // everything else is a 404 client.println(“HTTP/1.1 404 Not Found”); client.println(“Content-Type: text/html”); client.println(); client.println(“File Not Found!”); } break; } } // give the web browser time to receive the data delay(1); client.stop(); digitalWrite(8, LOW); }

}

H3

H3

Hab ein paar verbesserungen getätigt.
u.a. stoppt die übertragung jetzt wenn der client disconnected. außerdem gibt es jetzt eine Info LED auf pin 8 die aktivität anzeigt.

/*
This sketch uses the microSD card slot on the Arduino Ethernet shield
to serve up files over a very minimal browsing interface

Some code is from Bill Greiman’s SdFatLib examples, some is from the Arduino Ethernet WebServer example, some is from Limor Fried (Adafruit), some is from “jurs” for German Arduino forum, so its probably under GPL

*/

#include
#include
#include

/************ ETHERNET STUFF ************/
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 192, 168, 178, 177 };
EthernetServer server(80);

/************ SDCARD STUFF ************/
Sd2Card card;
SdVolume volume;
SdFile root;
SdFile file;

// store error strings in flash to save RAM
#define error(s) error_P(PSTR)

void error_P(const char* str) {
PgmPrint("error: “);
SerialPrintln_P(str);
if (card.errorCode()) {
PgmPrint(”SD error: ");
Serial.print(card.errorCode(), HEX);
Serial.print(‘,’);
Serial.println(card.errorData(), HEX);
}
while (1);
}

char* strupper( char* s )
// helper function char array to uppercase letters
{
for (char* p = s; *p; ++p)
*p = toupper( *p );
return s;
}

char* strlower( char* s )
// helper function char array to lowercase letters
{
for (char* p = s; *p; ++p)
*p = tolower( *p );
return s;
}

void setup() {
Serial.begin(115200);

PgmPrint("Free RAM: "); Serial.println(FreeRam()); // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with // breadboards. use SPI_FULL_SPEED for better performance. pinMode(10, OUTPUT); // set the SS pin as an output (necessary!) pinMode(4, OUTPUT); digitalWrite(10, HIGH); // but turn off the W5100 chip! digitalWrite(4, HIGH); if (!card.init(SPI_FULL_SPEED, 4)) error(“card.init failed!”); // initialize a FAT volume if (!volume.init(&card)) error(“vol.init failed!”); PgmPrint(“Volume is FAT”); Serial.println(volume.fatType(), DEC); Serial.println(); if (!root.openRoot(&volume)) error(“openRoot failed”); // list file in root with date and size PgmPrintln(“Files found in root:”); root.ls(LS_DATE | LS_SIZE); Serial.println(); // Recursive list of all directories PgmPrintln(“Files found in all dirs:”); root.ls(LS_R); Serial.println(); PgmPrintln(“Done”); // Debugging complete, we start the server! Ethernet.begin(mac, ip); server.begin(); delay(1000); digitalWrite(8, LOW);

}

void ListFiles(EthernetClient client, uint8_t flags) {
// This code is just copied from SdFile.cpp in the SDFat library
// and tweaked to print to the client output in html!
dir_t p;

root.rewind(); client.println(“”); while (root.readDir(p) > 0) { // done if past last used entry if (p.name0 == DIR_NAME_FREE) break; // skip deleted entry and entries for . and .. if (p.name0 == DIR_NAME_DELETED || p.name0 == ‘.’) continue; // only list subdirectories and files if (!DIR_IS_FILE_OR_SUBDIR(&p)) continue; // print any indent spaces client.print( for (uint8_t i = 0; i < 11; i++) { if (p.name[i] == ’ ’) continue; if (i == 8) { client.print(‘.’); } client.print((char)p.name[i]); } client.print(“\”>"); // print file name with possible blank fill for (uint8_t i = 0; i < 11; i++) { if (p.name[i] == ’ ’) continue; if (i == 8) { client.print(‘.’); } client.print((char)p.name[i]); } client.print(“”); if (DIR_IS_SUBDIR(&p)) { client.print(‘/’); } // print modify date/time if requested if (flags & LS_DATE) { root.printFatDate(p.lastWriteDate); client.print(’ ’); root.printFatTime(p.lastWriteTime); } // print size if requested if (!DIR_IS_SUBDIR(&p) && (flags & LS_SIZE)) { client.print(’ ’); client.print(p.fileSize); } client.println(“”); } client.println(“”);

}

// How big our line buffer should be. 100 is plenty!
#define BUFSIZ 100

void loop()
{
char clientline[BUFSIZ];
int index = 0;

EthernetClient client = server.available(); if (client) { digitalWrite(8, HIGH); // an http request ends with a blank line boolean current_line_is_blank = true; // reset the input buffer index = 0; while (client.connected()) { if (client.available()) { char c = client.read(); // If it isn’t a new line, add the character to the buffer if (c != ‘\n’ && c != ‘\r’) { clientline[index] = c; index++; // are we too big for the buffer? start tossing out data if (index >= BUFSIZ) index = BUFSIZ – 1; // continue to read more data! continue; } // got a \n or \r new line, which means the string is done clientline[index] = 0; // Print it out for debugging Serial.println(clientline); // Look for substring such as a request to get the root file if (strstr(clientline, "GET / ") != 0) { // send a standard http response header client.println(“HTTP/1.1 200 OK”); client.println(“Content-Type: text/html”); client.println(); // print all the files, use a helper to keep it clean client.println(“Files:”); ListFiles(client, LS_SIZE); } else if (strstr(clientline, “GET /”) != 0) { // this time no space after the /, so a sub-file! char *filename; filename = clientline + 5; // look after the “GET /” (5 chars) // a little trick, look for the " HTTP/1.1" string and // turn the first character of the substring into a 0 to clear it out. (strstr(clientline, " HTTP"))0 = 0; // print the file we want Serial.println(filename); if (! file.open(&root, filename, O_READ)) { client.println(“HTTP/1.1 404 Not Found”); client.println(“Content-Type: text/html”); client.println(); client.println(“File Not Found!”); break; } Serial.println(“Opened!”); strlower(filename); client.println(“HTTP/1.1 200 OK”); if (strstr(filename, “.html”) != NULL) client.println(“Content-Type: text/html”); else if (strstr(filename, “.jpg”) != NULL) client.println(“Content-Type: image/jpg”); else if (strstr(filename, “.png”) != NULL) client.println(“Content-Type: image/png”); else if (strstr(filename, “.mp3”) != NULL) client.println(“Content-Type: audio/mpeg”); else if (strstr(filename, “.ogg”) != NULL) client.println(“Content-Type: audio/ogg”); else if (strstr(filename, “.wav”) != NULL) client.println(“Content-Type: audio/wav”); else if (strstr(filename, “.m4a”) != NULL) client.println(“Content-Type: audio/mp4”); else if (strstr(filename, “.mp4”) != NULL) client.println(“Content-Type: video/mp4”); else client.println(“Content-Type: text/plain”); client.println(); int16_t c; while ((c = file.read()) >= 0) { // uncomment the serial to debug (slow!) //Serial.print((char)c); client.print((char)c); digitalWrite(8, !digitalRead(8)); if (!client.connected()) { return; } if (!client.available()) { return; } } file.close(); Serial.println(F(“Closed.”)); } else { // everything else is a 404 client.println(“HTTP/1.1 404 Not Found”); client.println(“Content-Type: text/html”); client.println(); client.println(“File Not Found!”); } break; } } // give the web browser time to receive the data delay(1); client.stop(); digitalWrite(8, LOW); }

}

Magnus

Magnus

I just bought from you a EthernetShield W5100 and wanted to test it with your code but I always get SD error: 1, FF with the code in the blog.
I have tried the code in the eBook. The WebServer example code works well, but the “Serving the web page from SD card, with AJAX” gives always a SD error.
Any clue to sort this out ?

Laat een reactie achter

Alle opmerkingen worden voor publicatie gecontroleerd door een moderator

Aanbevolen blogberichten

  1. ESP32 jetzt über den Boardverwalter installieren - AZ-Delivery
  2. Internet-Radio mit dem ESP32 - UPDATE - AZ-Delivery
  3. Arduino IDE - Programmieren für Einsteiger - Teil 1 - AZ-Delivery
  4. ESP32 - das Multitalent - AZ-Delivery