Ethernet-Shield als Fileserver - AZ-Delivery

Bonjour à tous

en raison de notre blog hier, de nombreux clients nous ont contactés qui expérimentaient déjà avec le bouclier et avait des problèmes en utilisant le lecteur de carte SD avec le W5100 en même temps. Nous avons fait des recherches et trouvé une solution que nous aimerions vous présenter aujourd’hui. Gerd nous a récemment montré comment utiliser un ESP comme serveur de fichiers avec une carte AnD. Aujourd’hui, nous adaptons la solution pour l’Uno avec Shield et vous présentons le code de travail avec les bibliothèques standard.

Matériel:

  • UnoR3
  • EthernetShield W5100
  • micro carte SD avec système de fichiers Fat32

Code:

 

/*
Cette esquisse utilise la fente de carte microSD sur le bouclier Arduino Ethernet
- pour servir des fichiers sur une interface de navigation très minimale
*
Certains codes proviennent des exemples SdFatLib de Bill Greiman,
certains proviennent de l’exemple Arduino Ethernet WebServer, 
Certains sont de Limor Fried (Adafruit),
- certains sont de "jurs" pour le forum allemand Arduino,
- donc c’est probablement sous GPL
*/

#include <Sd.H (en)>
#include <Spi.H (en)>
#include <Ethernet.H (en)>

ETHERNET STUFF
Octet Mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE (en), 0xED };
Octet Ip[] = { 192, 168, 168, 245 };
EthernetServer (en anglais) Serveur(80);

SDCARD
Sd2Card (Sd2Card) Carte;
SdVolume (SdVolume) Volume;
SdFile (SdFile) Racine;
SdFile (SdFile) Fichier;

stocker des chaînes d’erreur en flash pour sauver ram
#define Erreur(S) error_P(Pstr (Pstr)(S))

Vide error_P(Const Char Char* Rue) {   PgmPrint("erreur: ");   SerialPrintln_P(Rue);   Si (Carte.Errorcode()) {     PgmPrint("Erreur SD: ");     Série.Imprimer(Carte.Errorcode(), Hexagonale);     Série.Imprimer(',');     Série.println(Carte.Errordata(), Hexagonale);   }   Tandis que(1);
}

Char Char* strupper( Char Char* S )
tableau d’omble de fonction d’aide aux lettres de majuscules
{   Pour (Char Char* P = S; *P; ++P)     *P = Toupper( *P );   Retour S;
}

Char Char* strlower( Char Char* S )
tableau d’omble de fonction d’aide aux lettres de minuscules
{   Pour (Char Char* P = S; *P; ++P)     *P = Tolower( *P );   Retour S;
}

Vide Configuration() {   Série.Commencer(9600);   PgmPrint("Ram gratuit: ");   Série.println(FreeRam (en)());        initialiser la carte SD à SPI_HALF_SPEED pour éviter les erreurs de bus   Des planches à pain.  utiliser SPI_FULL_SPEED pour de meilleures performances.   pinMode(10, Sortie);                       définir la broche SS comme une sortie (nécessaire!)   digitalWrite (en)(10, Haute);                    mais éteignez la puce W5100!   Si (!Carte.Init(SPI_HALF_SPEED, 4)) Erreur("card.init a échoué!");      initialiser un volume FAT   Si (!Volume.Init(&Carte)) Erreur("vol.init a échoué!");   PgmPrint("Le volume est FAT");   Série.println(Volume.fatType (fatType)(),Dec);   Série.println();      Si (!Racine.openRoot(&Volume)) Erreur("openRoot échoué");   fichier de liste dans la racine avec la date et la taille   PgmPrintln("Fichiers trouvés dans la racine:");   Racine.Ls(LS_DATE | LS_SIZE);   Série.println();      Liste récursive de tous les répertoires   PgmPrintln("Fichiers trouvés dans tout ce que vous:");   Racine.Ls(LS_R);      Série.println();   PgmPrintln("Fait");      Debugging complet, nous commençons le serveur!   Ethernet.Commencer(Mac, Ip);   Serveur.Commencer();
}

Vide ListesFiles(EthernetClient Client, uint8_t Drapeaux) {   Ce code vient d’être copié à partir de SdFile.cpp dans la bibliothèque SDFat   et modifié pour imprimer à la sortie du client en html!   dir_t P;      Racine.Rembobiner();   Client.println("Lt;ul’gt;");   Tandis que (Racine.Readdir(P) > 0) {     fait si passé dernière entrée utilisée     Si (P.Nom[0] == DIR_NAME_FREE) Pause;     sauter l’entrée supprimée et les entrées pour . Et..     Si (P.Nom[0] == DIR_NAME_DELETED || P.Nom[0] == '.') Continuer;     ne dressez que des sous-directives et des fichiers     Si (!DIR_IS_FILE_OR_SUBDIR(&P)) Continuer;     imprimer tous les espaces en retrait     Client.Imprimer("Lt;li’gt;lt;a href"");     Pour (uint8_t Ⅰ. = 0; Ⅰ. < 11; Ⅰ.++) {       Si (P.Nom[Ⅰ.] == ' ') Continuer;       Si (Ⅰ. == 8) {         Client.Imprimer('.');       }       Client.Imprimer((Char Char)P.Nom[Ⅰ.]);     }     Client.Imprimer("\">");          nom de fichier d’impression avec le remplissage vide possible     Pour (uint8_t  = 0;  < 11; ++) {       Si (P.Nom[] == ' ') Continuer;       Si ( == 8) {         Client.Imprimer('.');       }       Client.Imprimer((char)P.Nom[]);     }          Client.Imprimer("Lt;/a-gt;");          Si (DIR_IS_SUBDIR(&P)) {       Client.Imprimer('/');     }     modifier la date/heure si demandé     Si (Drapeaux & LS_DATE) {        Racine.printFatDate(P.lastWriteDate);        Client.Imprimer(' ');        Racine.printFatTime(P.lastWriteTime lastWriteTime lastWriteTime lastW);     }     taille d’impression si demandé     Si (!DIR_IS_SUBDIR(&P) && (Drapeaux & LS_SIZE)) {       Client.Imprimer(' ');       Client.Imprimer(P.Taille);     }     Client.println("Lt;/li-gt;");   }   Client.println("Lt;/ul’gt;");
}

La taille de notre tampon de ligne devrait être. 100, c’est beaucoup !
#define BUFSIZ BUFSIZ 100

Vide Boucle()
{   char ligne client[BUFSIZ BUFSIZ];   Int Index = 0;      EthernetClient Client = Serveur.Disponible();   Si (Client) {     une demande http se termine par une ligne blanche     Boolean current_line_is_blank = Vrai;          réinitialiser le tampon d’entrée     Index = 0;          Tandis que (Client.Connecté()) {       Si (Client.Disponible()) {         char C = Client.Lire();                  S’il ne s’agit pas d’une nouvelle ligne, ajoutez le personnage au tampon         Si (C != 'n' && C != 'r') {           ligne client[Index] = C;           Index++;           sommes-nous trop gros pour le tampon? commencer à lancer des données           Si (Index >= BUFSIZ BUFSIZ)              Index = BUFSIZ BUFSIZ -1;                      continuer à lire plus de données!           Continuer;         }                  obtenu une nouvelle ligne de n ou de r, ce qui signifie que la chaîne est faite         ligne client[Index] = 0;                  Imprimez-le pour débogage         Série.println(ligne client);                  Recherchez une sous-corde telle qu’une demande d’obtenir le fichier racine         Si (strstr(ligne client, "GET / ") != 0) {           envoyer un en-tête de réponse http standard           Client.println("HTTP/1.1 200 OK");           Client.println("Content-Type: texte/html");           Client.println();                      imprimer tous les fichiers, utiliser une aide pour le garder propre           Client.println("Lt;h2 'gt;Files:lt;/h2 'gt;");           ListesFiles(Client, LS_SIZE);         } Autre Si (strstr(ligne client, "OBTENIR /") != 0) {           cette fois pas d’espace après le /, donc un sous-fichier!           char *Fichier;                      Fichier = ligne client + 5; s’occuper de la "GET /" (5 chars)           un petit truc, cherchez la chaîne " HTTP/1.1 " et            transformer le premier personnage du sous-corde en un 0 pour l’effacer.           (strstr(ligne client, "HTTP"))[0] = 0;                      imprimer le fichier que nous voulons           Série.println(Fichier);           Si (! Fichier.Ouvert(&Racine, Fichier, O_READ)) {             Client.println("HTTP/1.1 404 Non trouvé");             Client.println("Content-Type: texte/html");             Client.println();             Client.println("Lt;h2-gt;File Not Found!);             Pause;           }                      Série.println("Ouvert !");           strlower(Fichier);                     Client.println("HTTP/1.1 200 OK");           Si (strstr(Fichier,".htm")!=Null)             Client.println("Content-Type: texte/html");           Autre Si (strstr(Fichier,".jpg")!=Null)             Client.println("Content-Type: image/jpg");           Autre               Client.println("Content-Type: texte/plaine");           Client.println();                      int16_t C;           Tandis que ((C = Fichier.Lire()) >= 0) {               décompresser la série de déboiffer (lent!)               Serial.print((char)c);               Client.Imprimer((char)C);           }           Fichier.Proche();         } Autre {           tout le reste est un 404           Client.println("HTTP/1.1 404 Non trouvé");           Client.println("Content-Type: texte/html");           Client.println();           Client.println("Lt;h2-gt;File Not Found!);         }         Pause;       }     }     donner au navigateur Web le temps de recevoir les données     Retard(1);     Client.Arrêter();   }
}

Viel Spamd beim Nachbasteln!

Ich freue mich 'ber Ihre Feedback, und verabschiede mich bis zum n’chiesten Mal.
Moritz Spranger


Für arduinoProjekte für anfänger

3 commentaires

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 ?

Laisser un commentaire

Tous les commentaires sont modérés avant d'être publiés

Articles de blog recommandés

  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