- Pin
IO25
is connected toDAC_1
- Pin
IO26
is connected toDAC_2
Source: ESP32 technical reference manual, Table 4-4
IO25
is connected to DAC_1
IO26
is connected to DAC_2
Source: ESP32 technical reference manual, Table 4-4
This httpd_uri_t
handler just answers Hello World!
:
static const httpd_uri_t rootHandler = { .uri = "/", .method = HTTP_GET, .handler = [](httpd_req_t *req) { httpd_resp_set_type(req, "text/html"); httpd_resp_send(req, "<h1>Hello World!</h1>", HTTPD_RESP_USE_STRLEN); return ESP_OK; } };
The following script installs esp-idf
, the required dependencies and then builds MicroPython for the ESP32.
apt -y update && apt -y install python3 python3-pip cmake # Install esp-idf git clone --depth 1 -b v4.4.3 https://github.com/espressif/esp-idf.git /opt/esp-idf cd /opt/esp-idf && git submodule update --init --recursive && ./install.sh # Clone micropython git clone --depth 1 -b v1.19.1 https://github.com/micropython/micropython.git /opt/micropython # Build micropython cd /opt/micropython/ && make -C mpy-cross # Build micropython@esp32 cd /opt/micropython/ports/esp32 && source /opt/esp-idf/export.sh && make submodules && make -j4
The following example reads a HTTPS/TLS certificate from LittleFS (cert.pem
and privkey.pem
– these are just standard X.509 PEM files) and uses those to initialize a HTTPS server. Most of this example is based on the esp-idf https_server
example.
Our code to initialize the HTTPS server uses code from the following posts:
The core initialization sequence works like this:
// These need to be GLOBAL variables, they still need to exist // after initialization! std::string cert; std::string privkey; httpd_ssl_config_t conf = HTTPD_SSL_CONFIG_DEFAULT(); httpd_handle_t server = nullptr; void InitializeHTTPServer() { std::string cert = ReadFileToString("/cert.pem"); std::string privkey = ReadFileToString("/privkey.pem"); conf.cacert_pem = (const uint8_t*)this->cert.c_str(); conf.cacert_len = this->cert.size() + 1; conf.prvtkey_pem = (const uint8_t*)this->privkey.c_str(); conf.prvtkey_len = this->privkey.size() + 1; esp_err_t ret = httpd_ssl_start(&server, &conf); if (ESP_OK != ret) { ESP_LOGI(TAG, "Error starting server!"); return; } }
However it is typically far more convenient to use our HTTPSServer
class:
class HTTPSServer { public: HTTPSServer(): conf(HTTPD_SSL_CONFIG_DEFAULT()) { } void ReadCertificateInfoFromFilesystem() { cert = ReadFileToString("/cert.pem"); privkey = ReadFileToString("/privkey.pem"); } void StartServer() { // Start the httpd server ESP_LOGI(TAG, "Starting server"); ReadCertificateInfoFromFilesystem(); conf.cacert_pem = (const uint8_t*)this->cert.c_str(); conf.cacert_len = this->cert.size() + 1; conf.prvtkey_pem = (const uint8_t*)this->privkey.c_str(); conf.prvtkey_len = this->privkey.size() + 1; esp_err_t ret = httpd_ssl_start(&server, &conf); if (ESP_OK != ret) { ESP_LOGI(TAG, "Error starting server!"); return; } } void RegisterHandler(const httpd_uri_t *uri_handler) { httpd_register_uri_handler(this->server, uri_handler); } httpd_handle_t server = nullptr; httpd_ssl_config_t conf; std::string cert; std::string privkey; };
void setup() { InitFilesystem(); // TODO setup wifi network httpsServer.StartServer(); // Register your httpsServer.RegisterHandler(&root); }
#include <Arduino.h> #include <WiFi.h> #include <LittleFS.h> #include <string> #include <esp_https_server.h> #include "esp_tls.h" //AsyncWebServer server(80); static const char *TAG = "https-littlefs-example"; volatile bool filesystemOK = false; void InitFilesystem() { // Initialize LittleFS if (!LittleFS.begin(false /* false: Do not format if mount failed */)) { Serial.println("Failed to mount LittleFS"); if (!LittleFS.begin(true /* true: format */)) { Serial.println("Failed to format LittleFS"); } else { Serial.println("LittleFS formatted successfully"); filesystemOK = true; } } else { // Initial mount success filesystemOK = true; } } /* An HTTP GET handler */ static esp_err_t root_get_handler(httpd_req_t *req) { httpd_resp_set_type(req, "text/html"); httpd_resp_send(req, "<h1>Hello Secure World!</h1>", HTTPD_RESP_USE_STRLEN); return ESP_OK; } static const httpd_uri_t root = { .uri = "/", .method = HTTP_GET, .handler = root_get_handler }; size_t LittleFSFilesize(const char* filename) { auto file = LittleFS.open(filename, "r"); size_t filesize = file.size(); // Don't forget to clean up! file.close(); return filesize; } std::string ReadFileToString(const char* filename) { auto file = LittleFS.open(filename, "r"); size_t filesize = file.size(); // Read into temporary Arduino String String data = file.readString(); // Don't forget to clean up! file.close(); return std::string(data.c_str(), data.length()); } class HTTPSServer { public: HTTPSServer(): conf(HTTPD_SSL_CONFIG_DEFAULT()) { } void ReadCertificateInfoFromFilesystem() { cert = ReadFileToString("/cert.pem"); privkey = ReadFileToString("/privkey.pem"); } void StartServer() { // Start the httpd server ESP_LOGI(TAG, "Starting server"); ReadCertificateInfoFromFilesystem(); conf.cacert_pem = (const uint8_t*)this->cert.c_str(); conf.cacert_len = this->cert.size() + 1; conf.prvtkey_pem = (const uint8_t*)this->privkey.c_str(); conf.prvtkey_len = this->privkey.size() + 1; esp_err_t ret = httpd_ssl_start(&server, &conf); if (ESP_OK != ret) { ESP_LOGI(TAG, "Error starting server!"); return; } // Set URI handlers ESP_LOGI(TAG, "Registering URI handlers"); httpd_register_uri_handler(server, &root); return; } void RegisterHandler(const httpd_uri_t *uri_handler) { httpd_register_uri_handler(this->server, uri_handler); } httpd_handle_t server = nullptr; httpd_ssl_config_t conf; std::string cert; std::string privkey; }; HTTPSServer httpsServer; void setup() { Serial.begin(115200); delay(500); InitFilesystem(); // Connect Wifi, restart if not connecting // https://techoverflow.net/2021/01/21/how-to-fix-esp32-not-connecting-to-the-wifi-network/ WiFi.begin("MyWifi", "Lobae1tie5achokef8riequohgohx"); uint32_t notConnectedCounter = 0; while (WiFi.status() != WL_CONNECTED) { delay(100); Serial.println("Wifi connecting..."); notConnectedCounter++; if(notConnectedCounter > 150) { // Reset board if not connected after 5s Serial.println("Resetting due to Wifi not connecting..."); ESP.restart(); } } Serial.print("Wifi connected, IP address: "); Serial.println(WiFi.localIP()); httpsServer.StartServer(); httpsServer.RegisterHandler(&root); } void loop() { // put your main code here, to run repeatedly: delay(1000); }
[env:esp32dev] platform = espressif32 platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.5 board = esp32dev framework = arduino board_build.filesystem = littlefs monitor_speed = 115200
The following utility function reads . As a prerequisite, you need to initialize the LittleFS filesystem and configure PlatformIO to use LittleFS as filesystem when uploading the filesystem image.
Note: Before using any of the functions below, you need to call InitFilesystem()
in setup()
in order to mount the filesystem. You can find this function in our previous post on how to initialize LittleFS.
#include <string> std::string ReadFileToString(const char* filename) { auto file = LittleFS.open(filename, "r"); size_t filesize = file.size(); // Read into temporary Arduino String String data = file.readString(); // Don't forget to clean up! file.close(); return std::string(data.c_str(), data.length()); }
std::string cert = ReadFileToString("/cert.pem");
You can configure PlatformIO to use the LittleFS
filesystem as opposed to the default SPIFFS
using
board_build.filesystem = littlefs
If you are ussing arduino-esp32
, you might also need to specify to use a recent arduino-esp32
framework version, but I suggest to first try it out without using that option:
platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.5
platformio.ini
example[env:esp32dev] platform = espressif32 platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.5 board = esp32dev framework = arduino board_build.filesystem = littlefs monitor_speed = 115200
While trying to use TLS on the ESP32, you are using a certificate and private key e.g. from NVS or from the filesystem. However when you try to connect using SSL, you see error messages like
E (9774) esp-tls-mbedtls: mbedtls_x509_crt_parse returned -0x2180 E (9775) esp-tls-mbedtls: Failed to set server pki context E (9775) esp-tls-mbedtls: Failed to set server configurations, returned [0x8015] (ESP_ERR_MBEDTLS_X509_CRT_PARSE_FAILED) E (9786) esp-tls-mbedtls: create_ssl_handle failed, returned [0x8015] (ESP_ERR_MBEDTLS_X509_CRT_PARSE_FAILED) E (9795) esp_https_server: esp_tls_create_server_session failed
-0x2180
means MBEDTLS_ERR_X509_INVALID_FORMAT
The most common issue here is that conf.cacert_len
and conf.prvtkey_len
must include the NUL terminator in the length.
conf.cacert_pem = (const uint8_t*)cert.c_str(); conf.cacert_len = this->cert.size() + 1; conf.prvtkey_pem = (const uint8_t*)privkey.c_str(); conf.prvtkey_len = this->privkey.size() + 1;
Note the + 1
here: Without the + 1
, you’ll see the mbedtls_x509_crt_parse returned -0x2180
If the length isn’t the issue, you likely have a malformed certificate. I suggest to print out the certificate’s content via the serial port, saving it to file we’ll call cert_esp32.pem
and then running
openssl x509 -in cert_esp32.pem -noout -text
to verify that the certificate is correct. You can do the same for the private key, but typically either both the private key and the certificate are fine or both are not.
In order to use the LittleFS library that comes with Arduino on the ESP32, use the following function
#include <LittleFS.h> bool filesystemOK = false; void InitFilesystem() { // Initialize LittleFS if (!LittleFS.begin(false /* false: Do not format if mount failed */)) { Serial.println("Failed to mount LittleFS"); if (!LittleFS.begin(true /* true: format */)) { Serial.println("Failed to format LittleFS"); } else { Serial.println("LittleFS formatted successfully"); filesystemOK = true; } } else { // Initial mount success filesystemOK = true; } }
Additionally, you need to configure PlatformIO to use LittleFS:
board_build.filesystem = littlefs
Now, in setup()
, call
InitFilesystem();
If you have errors including LittleFS.h
, you might need to explicitly use a recent arduino-esp32
version in platformio.ini
:
platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.5
In case you absolutely need to use an older arduino-esp32
version that doesn’t support LittleFS out of the box, you can use the lorol/LittleFS
library as outlined in our previous post: How to initialize LittleFS in PlatformIO on the ESP32 using the lorol/LittleFS library
platformio.ini
example:[env:esp32dev] platform = espressif32 platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.5 board = esp32dev framework = arduino board_build.filesystem = littlefs monitor_speed = 115200
Yes, I verified that mbed-tls (which is used in the esp-idf framework, which is in turn used by Arduino/PlatformIO) works fine when using a secp384r1
-keyed Let’s encrypt TLS server certificate.
The version used in arduino-esp32 v2.0.5 with PlatformIO 6.1.5.
For new projects, you should exclusively use LittleFS, period.
The only reason why you would you SPIFFS at all if you have data stored on SPIFFS and can’t easily migrate to LittleFS.
LittleFS is just better than SPIFFS in every regard and SPIFFS will at some point in time be deprecated anyways.
After you have initialized LittleFS (see ESP32 Filesystem initialization code example (LittleFS)) you can get the filesize by first opening the file, and then calling .size()
on the opened file. Don’t forget to close the file afterwards.
auto file = LittleFS.open(filename, "r"); size_t filesize = file.size(); // Don't forget to clean up! file.close();
size_t LittleFSFilesize(const char* filename) { auto file = LittleFS.open(filename, "r"); size_t filesize = file.size(); // Don't forget to clean up! file.close(); return filesize; }
Serial.println(LittleFSFilesize("/cert.pem"));
When compiling your PlatformIO project, you see a compiler error like
src/main.cpp:6:10: fatal error: esp_https_server.h: No such file or directory
at the following line:
#include <esp_https_server.h>
The ESP32 HTTPS server library is not included with the older versions of arduino-espressif32
. The solution therefore is to use a more recent version of the platform library such as version 2.0.4
. In order to do that, add the following line to your platformio.ini
:
platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.4
platformio.ini
example:[env:esp32dev] platform = espressif32 platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.4 board = esp32dev framework = arduino monitor_speed = 115200
Before you call WiFi.begin(...)
, call this:
if (!WiFi.config( IPAddress(192, 168, 19, 5), // ESP's IP address IPAddress(192, 168, 19, 1), // Gateway IPAddress(255, 255, 255, 0), // IP Address IPAddress(192, 168, 19, 1) // DNS server )) { Serial.println("Failed to set static IP"); }
and replace the IP address etc with the static
This is based on our previous post on How to fix ESP32 not connecting to the Wifi network:
#include <Arduino.h> #include <WiFi.h> void setup() { Serial.begin(115200); WiFi.begin("MyWifiSSID", "MyWifiPassword"); // Set static IP if (!WiFi.config( IPAddress(192, 168, 19, 5), // ESP's IP address IPAddress(192, 168, 19, 2), // Gateway IPAddress(255, 255, 255, 0), // IP Address IPAddress(192, 168, 19, 1) // DNS server )) { Serial.println("Failed to set static IP"); } // Wait for wifi to be connected uint32_t notConnectedCounter = 0; while (WiFi.status() != WL_CONNECTED) { delay(100); Serial.println("Wifi connecting..."); notConnectedCounter++; if(notConnectedCounter > 150) { // Reset board if not connected after 5s Serial.println("Resetting due to Wifi not connecting..."); ESP.restart(); } } Serial.print("Wifi connected, IP address: "); Serial.println(WiFi.localIP()); } void loop() { // put your main code here, to run repeatedly: }
Currently there is no pip install ...
way of installing espota.py
. The easiest way of installing it is to download it from GitHub:
wget https://raw.githubusercontent.com/espressif/arduino-esp32/master/tools/espota.py
When flashing an ESP32, especially when flashing remotely, you see the following log message
Serial port rfc2217://10.1.2.3.105:4418 Connecting... Device PID identification is only supported on COM and /dev/ serial ports. .. Chip is ESP32-S2 Features: WiFi, No Embedded Flash, No Embedded PSRAM, ADC and temperature sensor calibration in BLK2 of efuse V2 Crystal is 40MHz MAC: 60:55:f9:f0:3a:16 Uploading stub... Running stub... Traceback (most recent call last): File "/usr/local/bin/esptool.py", line 34, in <module> esptool._main() File "/usr/local/lib/python3.8/dist-packages/esptool/__init__.py", line 1004, in _main main() File "/usr/local/lib/python3.8/dist-packages/esptool/__init__.py", line 684, in main esp = esp.run_stub() File "/usr/local/lib/python3.8/dist-packages/esptool/loader.py", line 912, in run_stub p = self.read() File "/usr/local/lib/python3.8/dist-packages/esptool/loader.py", line 307, in read return next(self._slip_reader) StopIteration
This issue occurs not because of a hardware defect but because the latency of the connection is quite high and therefore the timeout occurs before the communication between the script and the bootloader can happen.
In order to fix it, you need to edit the hard-coded default timeout in the esptool.py
installation!
First, you need to identify the location of loader.py
in your installation. You can simply do that from the following part of the stack trace:
File "/usr/local/lib/python3.8/dist-packages/esptool/loader.py", line 912, in run_stub p = self.read()
In this case, loader.py
is located at
/usr/local/lib/python3.8/dist-packages/esptool/loader.py
You need to edit the following line:
MEM_END_ROM_TIMEOUT = 0.05 # short timeout for ESP_MEM_END, as it may never respond
and increase the timeout – my recommendation is to increase it to 2.5
seconds.
… or you can simply change the timeout using the following command:
sed -i -e 's/MEM_END_ROM_TIMEOUT = 0.05/MEM_END_ROM_TIMEOUT = 2.5/g' /usr/local/lib/python3.8/dist-packages/esptool/loader.py
After that, your upload should work just fine.
Also see: How to read ESP32 NVS value into std::string
Writing a std::string
into NVS is pretty easy. You can just use .c_str()
to obtain a const char*
pointer and then proceed as shown in How to write string (const char*) to ESP32 NVS
You can use this utility function to write a string (const char*
) into the NVS:
bool NVSWriteString(nvs_handle_t nvs, const std::string& key, const std::string& value) { esp_err_t err; if((err = nvs_set_blob(nvs, key.c_str(), value.data(), value.size())) != ESP_OK) { Serial.printf("Failed to write NVS key %s: %srn", key, esp_err_to_name(err)); return false; } return true; }
This example is based on How to initialize NVS on ESP32. Most importantly, it assumes you have already initialized the NVS and myNVS
exists and is valid.
char mySerialNo[128] = {0}; _NVS_WriteString(myNVS, "SerialNo", "0000001");
This will write the value 0000001
to the NVS with key SerialNo
Note that NVS strings are length-delimited (as in: The length is stored in the NVS) and not neccessarily NUL-terminated. This code does not store the NUL terminator in NVS.
In our previous post, we discussed How to get the length / size of NVS value on ESP32. Based on that, we can read a NVS value into a std::string
.
std::string
from valueIn case the key does not exist in NVS, this function will return the empty string (""
).
#include <nvs.h> #include <string> std::string ReadNVSValueAsStdString(nvs_handle_t nvs, const char* key) { /** * Strategy: * 1. Determine size of value in NVS * 2. Allocate temporary buffer of determined size * 3. Read value from NVS into temporary buffer * 4. Create std::string from value * 5. Cleanup */ // Step 1: Get size of key esp_err_t err; size_t value_size = 0; if((err = nvs_get_str(nvs, _key.c_str(), nullptr, &value_size)) != ESP_OK) { if(err == ESP_ERR_NVS_NOT_FOUND) { // Not found, no error return ""; } else { printf("Failed to get size of NVS key %s: %s\r\n", key, esp_err_to_name(err)); return; } } // Step 2: Allocate temporary buffer to read from char* buf = (char*)malloc(value_size); // Step 3: Read value into temporary buffer. esp_err_t err; if((err = nvs_get_str(nvs, _key.c_str(), buf, &value_size)) != ESP_OK) { // "Doesn't exist" has already been handled before, so this is an actual error. // We assume that the value did not change between reading the size (step 1) and now. // In case that assumption is value, this will fail with ESP_ERR_NVS_INVALID_LENGTH. // This is extremely unlikely in all usage scenarios, however. printf("Failed to read NVS key %s: %s\r\n", key, esp_err_to_name(err)); free(buf); return ""; } // Step 4: Make string std::string value = std::string(buf, value_size); // Step 5: cleanup free(buf); return value; }
This assumes that you have setup myNvs
as we have shown in our previous post How to initialize NVS on ESP32
std::string value = ReadNVSValueAsStdString(myNvs, "MyKey");
Starting from C++17, you can possibly create a std::string
directly instead of using the temporary buffer, since there is an overload of .data()
that returns a non-const
pointer – so you can write directly to the std::string
‘s buffer.
However, since my PlatformIO-based toolchain currently doesn’t support that, I have not written that code yet.
You can find the size of a value stored in ESP32 NVS by running nvs_get_blob()
with length
set to a pointer to a variable with content 0
, and out_value
set to nullptr
. nvs_get_str()
works as well, the only difference is the type of the out_value
argument, which does not matter at all.
It’s easier to understand if you look at this example function:
size_t NVSGetSize(nvs_handle_t nvs, const char* key) { esp_err_t err; size_t valueSize = 0; if((err = nvs_get_str(nvs, key, nullptr, &valueSize)) != ESP_OK) { if(err == ESP_ERR_NVS_NOT_FOUND) { // Not found, no error return 0; } else { // Actual error, log & return 0 printf("Failed to get size of NVS key %s: %s\r\n", key, esp_err_to_name(err)); return 0; } } return valueSize; }
This assumes that you have setup myNvs
as we have shown in our previous post How to initialize NVS on ESP32
size_t myKeySize = NVSGetSize(myNvs, "MyKey");
Also see: How to read string (const char*) from ESP32 NVS
You can use this utility function to write a string (const char*
) into the NVS:
bool _NVS_WriteString(nvs_handle_t nvs, const char* key, const char* value) { esp_err_t err; if((err = nvs_set_str(nvs, key, value)) != ESP_OK) { Serial.printf("Failed to write NVS key %s: %s\r\n", key, esp_err_to_name(err)); return false; } return true; }
This example is based on How to initialize NVS on ESP32. Most importantly, it assumes you have already initialized the NVS and myNVS
exists and is valid.
char mySerialNo[128] = {0}; _NVS_WriteString(myNVS, "SerialNo", "0000001");
This will write the value 0000001
to the NVS with key SerialNo
Note that NVS strings are length-delimited (as in: The length is stored in the NVS) and not neccessarily NUL-terminated. This code does not store the NUL terminator in NVS.
Also see: How to write string (const char*) to ESP32 NVS
You can use this utility function to read a string:
bool _NVS_ReadString(nvs_handle_t nvs, const char* key, char* value, size_t maxSize) { esp_err_t err; if((err = nvs_get_str(nvs, key, value, &maxSize)) != ESP_OK) { Serial.printf("Failed to read NVS key %s: %s\r\n", key, esp_err_to_name(err)); return false; } return true; }
This example is based on How to initialize NVS on ESP32. Most importantly, it assumes you have already initialized the NVS and myNVS
exists and is valid.
char mySerialNo[128] = {0}; _NVS_ReadString(myNVS, "SerialNo", mySerialNo, 128-1);
This will read the NVS entry with key SerialNo
into the buffer mySerialNo
. Note that we are using 128-1
as size, even though the buffer has a size of 128
. This is to ensure that there is ALWAYS a terminating NUL character at the last value of the buffer.
Note that NVS strings are length-delimited (as in: The length is stored in the NVS) and not neccessarily NUL-terminated. I recommend to not store the NUL terminator in the NVS.