W tym poście opiszę jak wysyłać email przez ESP32.
Email będzie przesyłany z wykorzystaniem konta Gmail.
Na samym początku na koncie google musimy włączyć uwierzytelnianie dwuetapowe:
To odblokuje możliwość dołożenia hasła do aplikacji:
Po wprowadzeniu nazwy wyświetli się hasło do aplikacji:
Arduino:
W Arduino wysyłanie email wygląda następująco:
- #include <WiFi.h>
- #include <ESP_Mail_Client.h>
- #include <time.h>
- const char* ssid = "SSID"; //SSID
- const char* password = "HASLO"; //PASS
- //SMTP dla GMAIL
- #define SMTP_HOST "smtp.gmail.com"
- #define SMTP_PORT 465
- //Adres email z którego przesyłamy dane
- #define AUTHOR_EMAIL "email_adres@gmail.com"
- //Hasło do aplikacji wygenerowane na koncie Google
- #define AUTHOR_PASSWORD "haslo_do_aplikacji"
- //Docelowy email na który zostanie wysłana wiadomość
- #define RECIPIENT_EMAIL "odbiorca@hsk.com.pl"
- SMTPSession smtp;
- void smtpCallback(SMTP_Status status);
- void setup() {
- Serial.begin(115200);
- Serial.println();
- //Połączenie WIFI
- WiFi.begin(ssid, password);
- Serial.print("Łączenie z WiFi");
- while (WiFi.status() != WL_CONNECTED) {
- Serial.print(".");
- delay(500);
- }
- Serial.println("Połączono z WiFi!");
- Serial.print("IP: ");
- Serial.println(WiFi.localIP());
- //pobranie czasu z NTP
- configTime(0, 0, "pool.ntp.org", "time.nist.gov");
- //Strefa PL
- setenv("TZ", "CET-1CEST,M3.5.0/2,M10.5.0/3", 1);
- tzset();
- //czekanie na ustawienie czasu
- Serial.print("Ustawianie czasu\n");
- time_t now = time(nullptr);
- while (now < 1700000000) {
- Serial.print(".");
- delay(500);
- now = time(nullptr);
- }
- Serial.println("Czas ustawiony!");
- smtp.debug(1);
- smtp.callback(smtpCallback);
- ESP_Mail_Session session;
- session.server.host_name = SMTP_HOST;
- session.server.port = SMTP_PORT;
- session.login.email = AUTHOR_EMAIL;
- session.login.password = AUTHOR_PASSWORD;
- session.login.user_domain = "";
- //Tworzenie wiadomości
- SMTP_Message message;
- message.sender.name = "ESP32";
- message.sender.email = AUTHOR_EMAIL;
- //Nagłówek
- message.subject = "ESP32 test wiadomosci";
- message.addRecipient("Odbiorca", RECIPIENT_EMAIL);
- //Treść emaila
- String textMsg = "Treść emaila - testowa wiadomosc";
- message.text.content = textMsg.c_str();
- message.text.charSet = "utf-8";
- message.text.transfer_encoding = Content_Transfer_Encoding::enc_7bit;
- //Połączenie i wysyłanie
- if (!smtp.connect(&session)) {
- Serial.println("Błąd połączenia SMTP!");
- return;
- }
- if (!MailClient.sendMail(&smtp, &message)) {
- Serial.print("Błąd wysyłania: \n");
- Serial.println(smtp.errorReason());
- } else {
- Serial.println("E-mail wysłany!\n");
- }
- }
- void loop() { }
- void smtpCallback(SMTP_Status status) {
- Serial.println(status.info());
- if (status.success()) {
- Serial.println("Status wiadomości: wysłano poprawnie");
- }
- }
Tutaj nawiązywane jest połączenie z WiFi, pobierany aktualny czas z NTP oraz ustawiana jest aktualna strefa czasowa. Następnie konfigurowana jest sesja SMTP z adresem serwera, portem, danymi logowania, hasła. Dalej tworzona jest wiadomość email z nadawcą, tematem, odbiorcą oraz treścią w formacie UTF-8. W kolejnym krotku nawiązywane jest połączenie TLS z serwerem Gmail. Automatycznie wykonywane jest uwierzytlnianie SMTP. Po czym następuje wysłanie wiadomości.
Callback (smtpCallback) zwraca status przesłania wiadomości.
ESP-IDF:
Drugi przykład będzie wykorzystywał ESP-IDF.
- #include <stdio.h>
- #include <string.h>
- #include <time.h>
- #include "freertos/FreeRTOS.h"
- #include "freertos/event_groups.h"
- #include "freertos/task.h"
- #include "esp_timer.h"
- #include "esp_event.h"
- #include "esp_log.h"
- #include "esp_netif.h"
- #include "esp_system.h"
- #include "esp_tls.h"
- #include "esp_crt_bundle.h"
- #include "nvs_flash.h"
- #include "esp_wifi.h"
- #include "mbedtls/base64.h"
- #include "lwip/apps/sntp.h"
- static const char *TAG = "APP";
- static EventGroupHandle_t s_wifi_ev;
- static const int WIFI_CONNECTED_BIT = BIT0;
- static const char *WIFI_SSID = "SSID";
- static const char *WIFI_PASS = "HASLO";
- // ====== SMTP (GMAIL SSL 465) ======
- #define SMTP_HOST "smtp.gmail.com"
- #define SMTP_PORT 465
- #define AUTHOR_EMAIL "ADRES_EMAIL@gmail.com"
- #define AUTHOR_PASSWORD "HASLO_DO_APLIKACJI"
- #define RECIPIENT_EMAIL "RECEIVER@adres.pl"
- static int b64_encode(char *out, size_t out_sz, const char *in)
- {
- size_t olen = 0;
- int rc = mbedtls_base64_encode((unsigned char *)out, out_sz, &olen,
- (const unsigned char *)in, strlen(in));
- if (rc != 0) return -1;
- out[olen] = 0;
- return (int)olen;
- }
- static int tls_write_all(struct esp_tls *tls, const char *data)
- {
- int to_write = (int)strlen(data);
- int written = 0;
- while (written < to_write) {
- int r = esp_tls_conn_write(tls, data + written, to_write - written);
- if (r <= 0) return -1;
- written += r;
- }
- return 0;
- }
- static int tls_read_line(struct esp_tls *tls, char *buf, size_t buf_sz, int timeout_ms)
- {
- size_t pos = 0;
- int64_t start = esp_timer_get_time();
- while (pos + 1 < buf_sz) {
- char c;
- int r = esp_tls_conn_read(tls, &c, 1);
- if (r == 1) {
- buf[pos++] = c;
- if (c == '\n') break;
- } else {
- int64_t now = esp_timer_get_time();
- if ((now - start) / 1000 > timeout_ms) break;
- vTaskDelay(pdMS_TO_TICKS(10));
- }
- }
- buf[pos] = 0;
- return (int)pos;
- }
- static int smtp_expect(struct esp_tls *tls, const char *prefix)
- {
- char line[512];
- for (;;) {
- int n = tls_read_line(tls, line, sizeof(line), 5000);
- if (n <= 0) return -1;
- ESP_LOGI(TAG, "S: %s", line);
- // SMTP multiline: "250-" ... kończy się na "250 "
- if (strlen(line) >= 4 && line[3] == ' ') {
- if (!prefix) return 0;
- return (strncmp(line, prefix, strlen(prefix)) == 0) ? 0 : -1;
- }
- }
- }
- static int smtp_send_cmd(struct esp_tls *tls, const char *cmd, const char *expect_prefix)
- {
- ESP_LOGI(TAG, "C: %s", cmd);
- if (tls_write_all(tls, cmd) != 0) return -1;
- return smtp_expect(tls, expect_prefix);
- }
- static int smtp_send_gmail_ssl(void)
- {
- ESP_LOGI(TAG, "Łączenie TLS do %s:%d ...", SMTP_HOST, SMTP_PORT);
- esp_tls_cfg_t cfg = {
- .crt_bundle_attach = esp_crt_bundle_attach,
- .timeout_ms = 10000,
- };
- struct esp_tls *tls = esp_tls_init();
- if (!tls) return -1;
- int rc = esp_tls_conn_new_sync(SMTP_HOST, (int)strlen(SMTP_HOST), SMTP_PORT, &cfg, tls);
- if (rc != 1) {
- ESP_LOGE(TAG, "TLS connect error: %d", rc);
- esp_tls_conn_destroy(tls);
- return -1;
- }
- if (smtp_expect(tls, "220") != 0) goto fail;
- if (smtp_send_cmd(tls, "EHLO esp32\r\n", "250") != 0) goto fail;
- if (smtp_send_cmd(tls, "AUTH LOGIN\r\n", "334") != 0) goto fail;
- char b64[256];
- if (b64_encode(b64, sizeof(b64), AUTHOR_EMAIL) < 0) goto fail;
- char line1[300];
- snprintf(line1, sizeof(line1), "%s\r\n", b64);
- if (smtp_send_cmd(tls, line1, "334") != 0) goto fail;
- if (b64_encode(b64, sizeof(b64), AUTHOR_PASSWORD) < 0) goto fail;
- char line2[300];
- snprintf(line2, sizeof(line2), "%s\r\n", b64);
- if (smtp_send_cmd(tls, line2, "235") != 0) goto fail;
- char cmd[300];
- snprintf(cmd, sizeof(cmd), "MAIL FROM:<%s>\r\n", AUTHOR_EMAIL);
- if (smtp_send_cmd(tls, cmd, "250") != 0) goto fail;
- snprintf(cmd, sizeof(cmd), "RCPT TO:<%s>\r\n", RECIPIENT_EMAIL);
- if (smtp_send_cmd(tls, cmd, "250") != 0) goto fail;
- if (smtp_send_cmd(tls, "DATA\r\n", "354") != 0) goto fail;
- const char *subject = "ESP32 test wiadomosci";
- const char *body = "Treść emaila - testowa wiadomosc";
- char datebuf[64];
- time_t now = time(NULL);
- struct tm tm_now;
- localtime_r(&now, &tm_now);
- strftime(datebuf, sizeof(datebuf), "%a, %d %b %Y %H:%M:%S %z", &tm_now);
- char msg[1500];
- snprintf(msg, sizeof(msg),
- "From: ESP32 <%s>\r\n"
- "To: <%s>\r\n"
- "Subject: %s\r\n"
- "Date: %s\r\n"
- "MIME-Version: 1.0\r\n"
- "Content-Type: text/plain; charset=utf-8\r\n"
- "Content-Transfer-Encoding: 7bit\r\n"
- "\r\n"
- "%s\r\n"
- "\r\n.\r\n",
- AUTHOR_EMAIL,
- RECIPIENT_EMAIL,
- subject,
- datebuf,
- body);
- ESP_LOGI(TAG, "C: [DATA] (%d bytes)", (int)strlen(msg));
- if (tls_write_all(tls, msg) != 0) goto fail;
- if (smtp_expect(tls, "250") != 0) goto fail;
- smtp_send_cmd(tls, "QUIT\r\n", "221");
- esp_tls_conn_destroy(tls);
- ESP_LOGI(TAG, "E-mail wysłany!");
- return 0;
- fail:
- ESP_LOGE(TAG, "Błąd SMTP/TLS");
- esp_tls_conn_destroy(tls);
- return -1;
- }
- //SNTP
- static void sntp_init_and_wait(void)
- {
- setenv("TZ", "CET-1CEST,M3.5.0/2,M10.5.0/3", 1);
- tzset();
- ESP_LOGI(TAG, "Ustawianie czasu (SNTP) ...");
- sntp_setoperatingmode(SNTP_OPMODE_POLL);
- sntp_setservername(0, "pool.ntp.org");
- sntp_setservername(1, "time.nist.gov");
- sntp_init();
- time_t now = 0;
- while (now < 1700000000) {
- vTaskDelay(pdMS_TO_TICKS(500));
- now = time(NULL);
- }
- ESP_LOGI(TAG, "Czas ustawiony.");
- }
- static void wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
- {
- if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
- esp_wifi_connect();
- return;
- }
- if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
- esp_wifi_connect();
- return;
- }
- if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
- xEventGroupSetBits(s_wifi_ev, WIFI_CONNECTED_BIT);
- return;
- }
- }
- static void wifi_init_sta(void)
- {
- s_wifi_ev = xEventGroupCreate();
- ESP_ERROR_CHECK(esp_netif_init());
- ESP_ERROR_CHECK(esp_event_loop_create_default());
- esp_netif_create_default_wifi_sta();
- wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
- ESP_ERROR_CHECK(esp_wifi_init(&cfg));
- ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL));
- ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL));
- wifi_config_t wifi_config = {0};
- strncpy((char *)wifi_config.sta.ssid, WIFI_SSID, sizeof(wifi_config.sta.ssid));
- strncpy((char *)wifi_config.sta.password, WIFI_PASS, sizeof(wifi_config.sta.password));
- wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
- ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
- ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
- ESP_ERROR_CHECK(esp_wifi_start());
- ESP_LOGI(TAG, "Łączenie z WiFi ...");
- xEventGroupWaitBits(s_wifi_ev, WIFI_CONNECTED_BIT, pdFALSE, pdTRUE, portMAX_DELAY);
- ESP_LOGI(TAG, "Połączono z WiFi.");
- }
- static void email_task(void *arg)
- {
- wifi_init_sta();
- sntp_init_and_wait();
- smtp_send_gmail_ssl();
- vTaskDelete(NULL);
- }
- void app_main(void)
- {
- ESP_ERROR_CHECK(nvs_flash_init());
- // stack ustawiony na 16 KB
- xTaskCreate(email_task, "email_task", 16 * 1024, NULL, 5, NULL);
- }
Głównym elementem powyższego programu jest email_task. Na samym początku wykonywana jest konfiguracja WiFi w trybie STA. Program oczekuje na adres IP. Po uzyskaniu dostępu do internetu uruchamiany jest SNTP, w celu ustawienia poprawnego czasu systemowego. TLS wymaga poprawnej daty w celu weryfikacji certyfikatów serwera.
Dalej przechodzi do głównego zadania czyli wysłania emaila. ESP nawiązuje bezpieczne połączenie TLS z serwerem smtp.gmail.com po porcie 465. Po zestawieniu połączenia wykonynywany jest klasyczny SMTP:
W programie nagłówki są budowane ręcznie wraz z treścią wiadomości. Wiadomość kończymy sekwencją \r\n.\r\n. Tak jak w protokole SMTP.
Do serwera dane wysyłane są przez TLS w pętli. Odpowiedź SMTP czytana jest linia po linii. Każdy z etapów sprawdza poprawny kod odpowiedzi. Po przesłaniu wiadomości program zamyka wszystko komendą QUIT. Czyszczone są zasoby TLS. Następnie task kończy działanie.