mobizt / ESP-Mail-Client

The comprehensive Arduino Email Client Library to send and read Email for Arduino devices. The library also supports other network shields or modules e.g., Wi-Fi, Ethernet, and GSM/4G modules.
https://mobizt.github.io/ESP-Mail-Client/
MIT License
199 stars 58 forks source link

Sending mail with attachment from SD slot TTGO lora V2.1 #257

Closed Alinus06 closed 1 year ago

Alinus06 commented 1 year ago

Build tool used:

Board used (ESP32/ESP8266/Arudino):

Other Libraries That are used:

#include "RTClib.h" // Librairie rtc pour DS1307 #include #include #include #include #include #include #include #include #include "FS.h" #include "SD_MMC.h **Description of problem:** Every time the receiver gets a message, it appens the "toto.txt" file in the SD card on board of the TTGO module. Every day at 23h59, it sends the "toto" file by Email. But after sending the message with the attachment, the SD slot is no longer accessible. I must unplug and re-plug the SD car or cutt the power to get it working again........ Sorry for my english........ **Share code snippet to reproduce the issue:** ```yaml PASTE .cpp / .ino code here #include "RTClib.h" // Librairie rtc pour DS1307 #include #include #include #include #include #include //#include #include #include #define SMTP_HOST "smtp.orange.fr" #define SMTP_PORT esp_mail_smtp_port_465 /* The log in credentials */ #define AUTHOR_EMAIL "SENDER MAIL" #define AUTHOR_PASSWORD "SENDER CODE" /* Recipient email address */ #define RECIPIENT_EMAIL "RECEIVER MAIL" /* Declare the global used SMTPSession object for SMTP transport */ SMTPSession smtp; /* Callback function to get the Email sending status */ void smtpCallback(SMTP_Status status); #if defined(ARDUINO_RASPBERRY_PI_PICO_W) WiFiMulti multi; #endif #define OLED_SDA 21 // Attention: Les broches SDA et SCL de l'affichage OLED sont inversées sur le dessin de LilyGO et sur GitHub. #define OLED_SCL 22 #define OLED_RST 12 // Note: Le TTGO Lora32 v2 n'utilise pas le signal reset, mais la librairie Adafruit_SSD1306, oui. #define SCREEN_WIDTH 128 // OLED display width, in pixels #define SCREEN_HEIGHT 64 // OLED display height, in pixels #define SD_CS 13 #define SD_SCK 14 #define SD_MOSI 15 #define SD_MISO 02 #include "FS.h" #include "SD_MMC.h" // Apres le téleversement mettre 1k ohm entre 5V et GPIO 02 pour acceder à la SD card !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! #define LED 25 const int csPin = 18; // LoRa radio chip select const int resetPin = 14; // LoRa radio reset const int irqPin = 26; // change for your board; must be a hardware interrupt pin const char *ssid = "Livebox-2D";//"TP-Link_1392" const char *pass = "941F768933EA25833152AA1F55";//"10540893"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! //WebServer server(80); #define MAX_SIZE 80 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RST); String loRaMessage; String temperature; String humidity; String password; String readingID; String bufeure; float hum; float temp; float rosee; float alpha; const char* ntpServer = "pool.ntp.org"; RTC_DS1307 rtc;//INITIALISATION DU MODULE RTC */ void listDir(fs::FS &fs, const char * dirname, uint8_t levels){ Serial.printf("Listing directory: %s\n", dirname); File root = fs.open(dirname); if(!root){ Serial.println("Failed to open directory"); return; } if(!root.isDirectory()){ Serial.println("Not a directory"); return; } File file = root.openNextFile(); while(file){ if(file.isDirectory()){ Serial.print(" DIR : "); Serial.println(file.name()); if(levels){ listDir(fs, file.name(), levels -1); } } else { Serial.print(" FILE: "); Serial.print(file.name()); Serial.print(" SIZE: "); Serial.println(file.size()); } file = root.openNextFile(); } } void createDir(fs::FS &fs, const char * path){ Serial.printf("Creating Dir: %s\n", path); if(fs.mkdir(path)){ Serial.println("Dir created"); } else { Serial.println("mkdir failed"); } } void removeDir(fs::FS &fs, const char * path){ Serial.printf("Removing Dir: %s\n", path); if(fs.rmdir(path)){ Serial.println("Dir removed"); } else { Serial.println("rmdir failed"); } } void readFile(fs::FS &fs, const char * path){ Serial.printf("Reading file: %s\n", path); File file = fs.open(path); if(!file){ Serial.println("Failed to open file for reading"); return; } Serial.print("Read from file: "); while(file.available()){ Serial.write(file.read()); } } void writeFile(fs::FS &fs, const char * path, const char * message){ Serial.printf("Writing file: %s\n", path); File file = fs.open(path, FILE_WRITE); if(!file){ Serial.println("Failed to open file for writing"); return; } if(file.print(message)){ Serial.println("File written"); } else { Serial.println("Write failed"); } } void appendFile(fs::FS &fs, const char * path, const char * message){ Serial.printf("Appending to file: %s\n", path); File file = fs.open(path, FILE_APPEND); if(!file){ Serial.println("Failed to open file for appending"); return; } if(file.print(message)){ Serial.println("Message appended"); } else { Serial.println("Append failed"); } } void renameFile(fs::FS &fs, const char * path1, const char * path2){ Serial.printf("Renaming file %s to %s\n", path1, path2); if (fs.rename(path1, path2)) { Serial.println("File renamed"); } else { Serial.println("Rename failed"); } } void deleteFile(fs::FS &fs, const char * path){ Serial.printf("Deleting file: %s\n", path); if(fs.remove(path)){ Serial.println("File deleted"); } else { Serial.println("Delete failed"); } } void testFileIO(fs::FS &fs, const char * path){ File file = fs.open(path); static uint8_t buf[512]; size_t len = 0; uint32_t start = millis(); uint32_t end = start; if(file){ len = file.size(); size_t flen = len; start = millis(); while(len){ size_t toRead = len; if(toRead > 512){ toRead = 512; } file.read(buf, toRead); len -= toRead; } end = millis() - start; Serial.printf("%u bytes read for %u ms\n", flen, end); file.close(); } else { Serial.println("Failed to open file for reading"); } file = fs.open(path, FILE_WRITE); if(!file){ Serial.println("Failed to open file for writing"); return; } size_t i; start = millis(); for(i=0; i<2048; i++){ file.write(buf, 512); } end = millis() - start; Serial.printf("%u bytes written for %u ms\n", 2048 * 512, end); file.close(); } void SendEmail() { // Mount SD card. SD_Card_Mounting(); // See src/extras/SDHelper.h Serial.println("Preparing SD file attachments..."); //const char *orangeImg = "iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAoUlEQVR42u3RMQ0AMAgAsCFgftHLiQpsENJaaFT+fqwRQoQgRAhChCBECEKECBGCECEIEYIQIQgRghCECEGIEIQIQYgQhCBECEKEIEQIQoQgBCFCECIEIUIQIgQhCBGCECEIEYIQIQhBiBCECEGIEIQIQQhChCBECEKEIEQIQhAiBCFCECIEIUIQghAhCBGCECEIEYIQIUKEIEQIQoQg5LoBGi/oCaOpTXoAAAAASUVORK5CYII="; // Write demo data to file static uint8_t buf[512]; // SDFat? /* Set the network reconnection option */ MailClient.networkReconnect(true); // The WiFi credentials are required for Pico W // due to it does not have reconnect feature. #if defined(ARDUINO_RASPBERRY_PI_PICO_W) MailClient.clearAP(); MailClient.addAP(WIFI_SSID, WIFI_PASSWORD); #endif /** Enable the debug via Serial port * 0 for no debugging * 1 for basic level debugging * * Debug port can be changed via ESP_MAIL_DEFAULT_DEBUG_PORT in ESP_Mail_FS.h */ smtp.debug(1); /* Set the callback function to get the sending results */ smtp.callback(smtpCallback); /* Declare the Session_Config for user defined session credentials */ Session_Config config; /* Set the session config */ config.server.host_name = SMTP_HOST; config.server.port = SMTP_PORT; config.login.email = AUTHOR_EMAIL; config.login.password = AUTHOR_PASSWORD; /** Assign your host name or you public IPv4 or IPv6 only * as this is the part of EHLO/HELO command to identify the client system * to prevent connection rejection. * If host name or public IP is not available, ignore this or * use generic host "mydomain.net". * * Assign any text to this option may cause the connection rejection. */ config.login.user_domain = F("mydomain.net"); /* Set the NTP config time */ config.time.ntp_server = F("pool.ntp.org,time.nist.gov"); config.time.gmt_offset = 3; config.time.day_light_offset = 0; /* Declare the message class */ SMTP_Message message; /* Enable the chunked data transfer with pipelining for large message if server supported */ message.enable.chunking = true; /* Set the message headers */ message.sender.name = F("ESP Mail"); message.sender.email = AUTHOR_EMAIL; message.subject = F("Test sending Email with attachments and inline images from SD card and Flash"); message.addRecipient(F("user1"), RECIPIENT_EMAIL); /** Two alternative content versions are sending in this example e.g. plain text and html */ String htmlMsg = "This message contains 1 attachment file."; message.html.content = htmlMsg; /** The HTML text message character set e.g. * us-ascii * utf-8 * utf-7 * The default value is utf-8 */ message.html.charSet = F("utf-8"); /** The content transfer encoding e.g. * enc_7bit or "7bit" (not encoded) * enc_qp or "quoted-printable" (encoded) * enc_base64 or "base64" (encoded) * enc_binary or "binary" (not encoded) * enc_8bit or "8bit" (not encoded) * The default value is "7bit" */ message.html.transfer_encoding = Content_Transfer_Encoding::enc_qp; message.text.content = F("This message contains 1 attachment file.\r\n"); message.text.charSet = F("utf-8"); message.text.transfer_encoding = Content_Transfer_Encoding::enc_base64; /** The message priority * esp_mail_smtp_priority_high or 1 * esp_mail_smtp_priority_normal or 3 * esp_mail_smtp_priority_low or 5 * The default value is esp_mail_smtp_priority_low */ message.priority = esp_mail_smtp_priority::esp_mail_smtp_priority_normal; /** The Delivery Status Notifications e.g. * esp_mail_smtp_notify_never * esp_mail_smtp_notify_success * esp_mail_smtp_notify_failure * esp_mail_smtp_notify_delay * The default value is esp_mail_smtp_notify_never */ // message.response.notify = esp_mail_smtp_notify_success | esp_mail_smtp_notify_failure | esp_mail_smtp_notify_delay; /* Set the custom message header */ message.addHeader(F("Message-ID: ")); /* The attachment data item */ SMTP_Attachment att[2]; int attIndex = 0; /** Set the inline image info e.g. * file name, MIME type, file path, file storage type, * transfer encoding and content encoding */ //att[attIndex].descr.filename = F("orange.png"); //att[attIndex].descr.mime = F("image/png"); //att[attIndex].file.path = F("/orange.png"); /** The file storage type e.g. * esp_mail_file_storage_type_none, * esp_mail_file_storage_type_flash, and * esp_mail_file_storage_type_sd */ att[attIndex].file.storage_type = esp_mail_file_storage_type_sd; /* Need to be base64 transfer encoding for inline image */ att[attIndex].descr.transfer_encoding = Content_Transfer_Encoding::enc_base64; /** The orange.png file is already base64 encoded file. * Then set the content encoding to match the transfer encoding * which no encoding was taken place prior to sending. */ att[attIndex].descr.content_encoding = Content_Transfer_Encoding::enc_base64; /* Add inline image to the message */ //message.addInlineImage(att[attIndex]); /** Set the attachment info e.g. * file name, MIME type, file path, file storage type, * transfer encoding and content encoding */ attIndex++; att[attIndex].descr.filename = F("toto.txt"); att[attIndex].descr.mime = F("application/octet-stream"); // binary data att[attIndex].descr.description = F("This is text file"); att[attIndex].file.path = F("/toto.txt"); att[attIndex].file.storage_type = esp_mail_file_storage_type_sd; att[attIndex].descr.transfer_encoding = Content_Transfer_Encoding::enc_base64; /* Add attachment to the message */ message.addAttachment(att[attIndex]); /* Connect to the server */ if (!smtp.connect(&config)) { ESP_MAIL_PRINTF("Connection error, Status Code: %d, Error Code: %d, Reason: %s", smtp.statusCode(), smtp.errorCode(), smtp.errorReason().c_str()); return; } if (smtp.isAuthenticated()) Serial.println("\nSuccessfully logged in."); else Serial.println("\nConnected with no Auth."); /* Start sending the Email and close the session */ if (!MailClient.sendMail(&smtp, &message, true)) ESP_MAIL_PRINTF("Error, Status Code: %d, Error Code: %d, Reason: %s", smtp.statusCode(), smtp.errorCode(), smtp.errorReason().c_str()); // to clear sending result log // smtp.sendingResult.clear(); ESP_MAIL_PRINTF("Free Heap: %d\n", MailClient.getFreeHeap()); } /* Callback function to get the Email sending status */ void smtpCallback(SMTP_Status status) { /* Print the current status */ Serial.println(status.info()); /* Print the sending result */ if (status.success()) { // ESP_MAIL_PRINTF used in the examples is for format printing via debug Serial port // that works for all supported Arduino platform SDKs e.g. AVR, SAMD, ESP32 and ESP8266. // In ESP8266 and ESP32, you can use Serial.printf directly. Serial.println("----------------"); ESP_MAIL_PRINTF("Message sent success: %d\n", status.completedCount()); ESP_MAIL_PRINTF("Message sent failed: %d\n", status.failedCount()); Serial.println("----------------\n"); for (size_t i = 0; i < smtp.sendingResult.size(); i++) { /* Get the result item */ SMTP_Result result = smtp.sendingResult.getItem(i); // In case, ESP32, ESP8266 and SAMD device, the timestamp get from result.timestamp should be valid if // your device time was synched with NTP server. // Other devices may show invalid timestamp as the device time was not set i.e. it will show Jan 1, 1970. // You can call smtp.setSystemTime(xxx) to set device time manually. Where xxx is timestamp (seconds since Jan 1, 1970) ESP_MAIL_PRINTF("Message No: %d\n", i + 1); ESP_MAIL_PRINTF("Status: %s\n", result.completed ? "success" : "failed"); ESP_MAIL_PRINTF("Date/Time: %s\n", MailClient.Time.getDateTimeString(result.timestamp, "%B %d, %Y %H:%M:%S").c_str()); ESP_MAIL_PRINTF("Recipient: %s\n", result.recipients.c_str()); ESP_MAIL_PRINTF("Subject: %s\n", result.subject.c_str()); } Serial.println("----------------\n"); // You need to clear sending result as the memory usage will grow up. smtp.sendingResult.clear(); } } void setup() { Serial.begin(115200); while (!Serial); esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR); esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF); Serial.println("Setup ESP32 to sleep for every " + String(TIME_TO_SLEEP) + " Seconds"); WiFi.persistent(false); WiFi.begin(ssid, pass); Serial.print("Tentative de connexion..."); while (WiFi.status() != WL_CONNECTED) { Serial.print(".");//si on veut que le programme continue sans Wifi, ne pas activer la boucle While delay(100); } Serial.println("\n"); Serial.println("Connexion etablie!"); Serial.print("Adresse IP: "); Serial.println(WiFi.localIP()); //server.on("/", handleRoot); // server.onNotFound(handleNotFound); //server.begin(); // Serial.println("Serveur web actif!"); pinMode(LED,OUTPUT); //initialize OLED Wire.begin(OLED_SDA, OLED_SCL); if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3c, false, false)) { // Address 0x3C for 128x32 Serial.println(F("SSD1306 allocation failed")); } display.clearDisplay(); display.display(); display.setTextColor(WHITE); display.setTextSize(2); display.setCursor(0,0); //Serial.println("LoRa Receiver"); // override the default CS, reset, and IRQ pins (optional) LoRa.setPins(csPin, resetPin, irqPin); // set CS, reset, IRQ pin if (!LoRa.begin(868E6)) { Serial.println("Starting LoRa failed!"); while (1); } LoRa.setSyncWord(0x97); //LoRa.setTxPower(20); //LoRa.setSpreadingFactor(8); configTzTime("CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00", ntpServer); //************************************************************************RTC******************************************************* if (! rtc.begin()) { Serial.println("Couldn't find RTC"); Serial.flush(); while (1) delay(10); } if (! rtc.isrunning()) { Serial.println("RTC is NOT running, let's set the time!"); } // When time needs to be re-set on a previously configured device, the // following line sets the RTC to the date & time this sketch was compiled //rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); // This line sets the RTC with an explicit date & time, for example to set // January 21, 2014 at 3am you would call: // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0)); //************************************************************************RTC******************************************************* } void loop() { // try to parse packet int packetSize = LoRa.parsePacket(); yield();// pour empecher le blocage apres 30 mn..... if (packetSize) { // Serial.println("Lora packet received: "); display.clearDisplay(); display.setCursor(0,0); // Read packet while (LoRa.available()) { String LoRaData = LoRa.readString(); // LoRaData format: readingID/temperature&soilMoisture#batterylevel // String example: 1/27.43&654#95.34 //Serial.print(LoRaData); // Get readingID, temperature and soil moisture int pos1 = LoRaData.indexOf('/'); int pos2 = LoRaData.indexOf('&'); int pos3 = LoRaData.indexOf('#'); readingID = LoRaData.substring(0, pos1); temperature = LoRaData.substring(pos1+1, pos2); humidity = LoRaData.substring(pos2+1, pos3); password = LoRaData.substring(pos3+1, LoRaData.length()); // calcul du point de rosée (formule de Heinrich Gustav Magnus-Tetens) float temp = temperature.toFloat();//Converti un Srting en Float float hum = humidity.toFloat();//Converti un Srting en Float alpha = log(hum / 100) + (17.27 * temp) / (237.3 + temp); rosee = (237.3 * alpha) / (17.27 - alpha); if (password == "1234") { Serial.print(readingID); Serial.print("\t"); Serial.print(temperature); Serial.print("\t"); Serial.print(humidity); Serial.print("\t"); //Serial.println(password); Serial.print(rosee,1); Serial.print("\t"); // Print hour and date DateTime now = rtc.now(); String horodatage = String(now.year(),DEC)+ "/" + String(now.month(),DEC)+ "/" + String(now.day(),DEC)+" "+ String(now.hour(),DEC)+":" + String(now.minute(),DEC)+":"+ String(now.second(), DEC); String dataMessage = String(readingID) + " " + String(temperature) + " " + String(humidity) + " " + String(rosee)+" "+ String(horodatage) + "\r\n";//mise en forme pour la carte DS Serial.print(now.year(), DEC); Serial.print('/'); Serial.print(now.month(), DEC); Serial.print('/'); Serial.print(now.day(), DEC); Serial.print("\t"); //Serial.print(daysOfTheWeek[now.dayOfTheWeek()]); //Serial.print(") "); Serial.print(now.hour(), DEC); Serial.print(':'); if (now.minute()<10){ Serial.print("0");} Serial.print(now.minute(), DEC); Serial.print(':'); Serial.print(now.second(), DEC); Serial.println(); delay(3000); //print to display display.println(readingID); display.print ("T: "); display.println((temperature) +" C"); display.print ("H: "); display.println((humidity)+" %"); display.print ("Dew: "); display.print(rosee,1); //display.println("CTd"); //display.println((password)+" hPa"); display.display(); if(!SD_MMC.begin()){ Serial.println("Card Mount Failed"); return; } uint8_t cardType = SD_MMC.cardType(); if(cardType == CARD_NONE){ Serial.println("No SD_MMC card attached"); return; } Serial.print("SD_MMC Card Type: "); if(cardType == CARD_MMC){ Serial.println("MMC"); } else if(cardType == CARD_SD){ Serial.println("SDSC"); } else if(cardType == CARD_SDHC){ Serial.println("SDHC"); } else { Serial.println("UNKNOWN"); } uint64_t cardSize = SD_MMC.cardSize() / (1024 * 1024); Serial.printf("SD_MMC Card Size: %lluMB\n", cardSize); listDir(SD_MMC, "/", 0); //createDir(SD_MMC, "/mydir"); listDir(SD_MMC, "/", 0); //removeDir(SD_MMC, "/mydir"); //listDir(SD_MMC, "/", 2); // writeFile(SD_MMC, "/hello.txt", "Hello "); // appendFile(SD_MMC, "/hello.txt", LoRaData.c_str()); // appendFile(SD_MMC, "/hello.txt", temperature.c_str()); appendFile(SD_MMC, "/toto.txt", dataMessage.c_str()); readFile(SD_MMC, "/toto.txt"); // deleteFile(SD_MMC, "/foo.txt"); // renameFile(SD_MMC, "/hello.txt", "/foo.txt"); // readFile(SD_MMC, "/foo.txt"); // testFileIO(SD_MMC, "/test.txt"); Serial.printf("Total space: %lluMB\n", SD_MMC.totalBytes() / (1024 * 1024)); Serial.printf("Used space: %lluMB\n", SD_MMC.usedBytes() / (1024 * 1024)); /*time_t timestamp = time( NULL ); char buffer[MAX_SIZE]; struct tm *pTime = localtime(×tamp ); strftime(buffer, MAX_SIZE, "%d/%m/%Y %H:%M:%S", pTime); bufeure = buffer; Serial.println(buffer); */ } } } // put the radio into receive mode //LoRa.receive(); //handleRoot(); //server.handleClient(); DateTime now = rtc.now(); if(now.hour()==23 && now.minute()==59 && now.second()==00){ digitalWrite(LED,HIGH); SendEmail(); } else {digitalWrite(LED,LOW);} } ``` **Additional information and things you've tried:**
mobizt commented 1 year ago

You should show library debug printing message on Serial to see that the card mounting is success or failed.

If you read the comment in examples carefully, you will see the comment about the SD configuration requirement. https://github.com/mobizt/ESP-Mail-Client/blob/149100d7d6343b4175ddba153053e3b3a03c76b3/examples/SMTP/Send_Attachment_File/Send_Attachment_File.ino#L35-L37

Which you have to config this file to let the library knows for your SD card type and filesystem you used first.

https://github.com/mobizt/ESP-Mail-Client/blob/149100d7d6343b4175ddba153053e3b3a03c76b3/src/ESP_Mail_FS.h#L90-L124

In file SDHelper.h, the second argument of function MailClient.sdMMCBegin using at line number 147 was set for 4-bit mode

https://github.com/mobizt/ESP-Mail-Client/blob/149100d7d6343b4175ddba153053e3b3a03c76b3/src/extras/SDHelper.h#L147

If your MMC card interface is 4-bit, you don't have to do anything with that SDHelper.h file.

Unless you connect MMC card in 1-bit mode, the line number 147 should change to

if (!MailClient.sdMMCBegin("/sdcard", true, true))

The function sdMMCBegin is to let the library knows the type of your SD filesystem which the function arguments are the same as SD_MMC.begin in the SD_MMC.h core library.

For SPI, 1-bit mode and 4-bit mode interfaces, please see this.