Embedded

How to parse query URLs using ESP-IDF webserver on ESP32

This utility class allows you to easily parse query URLs. In order to use it, you must add -std=gnu++17 or later to your compiler flags.

class QueryURLParser {
public:
    QueryURLParser(httpd_req_t *req) {
        size_t queryURLLen = httpd_req_get_url_query_len(req) + 1;

        if(queryURLLen > 1) {
            // Allocate temporary buffer to store the parameter
            char buf[queryURLLen] = {0};
            if (httpd_req_get_url_query_str(req, buf, queryURLLen) != ESP_OK) {
                ESP_LOGE("Query URL parser", "Failed to extract query URL");
                // Set string to empty string => "not found"
                this->queryURLString = "";
            } else {
                // Copy into a std::string
                this->queryURLString = std::string(buf, queryURLLen);
            }
            delete[] buf;
        }
    }


    std::string GetParameter(const char* key) {
        // Allocate enough space to store the parameter.
        // NOTE: The parameter can only be as long as the query URL itself.
        // Therefore, allocating "too much" space upfront will
        // avoid unneccessary copying
        size_t bufSize = queryURLString.size();
        char* buf = (char*)malloc(bufSize);

        /* Get value of expected key from query string */
        esp_err_t err = httpd_query_key_value(queryURLString.c_str(), key, buf, bufSize);
        if(err != ESP_OK) {
          ESP_LOGE("Query URL parser", "parsing URL");
          Serial.println(esp_err_to_name(err));
          free(buf);
          return "";
        }
        // Convert to std::string
        std::string param(buf);
        free(buf);
        // Shrink param so it fits.
        return param;
    }

private:
    std::string queryURLString;
};

Usage example:

static const httpd_uri_t setMQTTURLHandler = {
    .uri       = "/api/set-mqtt-url",
    .method    = HTTP_GET,
    .handler   = [](httpd_req_t *req) {
        QueryURLParser parser(req);
        std::string url = parser.GetParameter("url");
        if(url.empty()) {
            return SendStatusError(req, "Query parameter 'url' missing");
        }
        // TODO Do something with [url]
        return SendStatusOK(req);
    }
};

SendStatusError() and SendStatusOK() are from our blogpost ESP-IDF webserver: How to respond with JSON success or error message.

Posted by Uli Köhler in C/C++, ESP8266/ESP32

ESP-IDF webserver: How to respond with JSON success or error message

These utility functions allow you to easily respond with a {"status": "ok"} or {"status": "error", "error": "..."}.

Note that the error message is not being escaped but sent as-is, so some error messages containing quotes (") or other special characters might break the JSON.

esp_err_t SendStatusOK(httpd_req_t *request) {
    httpd_resp_set_type(request, "application/json");
    httpd_resp_sendstr(request, "{\"status\":\"ok\"}");
    return ESP_OK;
}

esp_err_t SendStatusError(httpd_req_t *request, const char* description) {
    httpd_resp_set_type(request, "application/json");
    httpd_resp_send_chunk(request, "{\"status\":\"error\", \"error\": \"", HTTPD_RESP_USE_STRLEN);
    // NOTE: We silently assume that description does not have any special characters
    httpd_resp_send_chunk(request, description, HTTPD_RESP_USE_STRLEN);
    httpd_resp_send_chunk(request, "\"}", HTTPD_RESP_USE_STRLEN);
    httpd_resp_send_chunk(request, nullptr, 0); // Finished
    return ESP_OK;
}

 

Posted by Uli Köhler in C/C++, ESP8266/ESP32

Easy-to-use ESP32 IDF webserver C++ wrapper class

#include <esp_http_server.h>

/**
 * HTTP server.
 * Works with ESP-IDF library internally.
 * 
 * Usage:
 * First call http.StartServer()
 * Then call http.RegisterHandler(...) for all handlers
 */
class HTTPServer {
public:
    HTTPServer(): conf(HTTPD_DEFAULT_CONFIG()) {
    }

    void StartServer() {
        if (httpd_start(&server, &conf) != ESP_OK) {
            ESP_LOGE("HTTP server", "Error starting server!");
        }
    }

    void RegisterHandler(const httpd_uri_t *uri_handler) {
        httpd_register_uri_handler(this->server, uri_handler);
    }

    httpd_handle_t server = nullptr;
    httpd_config_t conf;
};

Usage example:

// Declare server globally
HTTPServer http;

// Example handler
static const httpd_uri_t rebootHandler = {
    .uri       = "/api/reboot",
    .method    = HTTP_GET,
    .handler   = [](httpd_req_t *req) {
        SendStatusOK(req);
        delay(20);
        ESP.restart();
        return ESP_OK;
    }
};

void setup() {
    // TODO setup wifi or Ethernet
    http.StartServer();
    http.RegisterHandler(&myHandler);
}

 

Posted by Uli Köhler in C/C++, Embedded, ESP8266/ESP32

ESP32 MAX31855 thermocouple LCD minimal example using PlatformIO

Important: In newer versions of PlatformIO, most ESP32 boards will crash with this example unless you create a custom lv_conf.h config file specifying to use internal memory instead of PSRAM. See How to fix ESP32 LVGL crash / reboot in lv_tlsf_create() / lv_mem_init() for instructions how to fix that.

In our previous example Adafruit ST7735R display minimal LVGL example for PlatformIO we showed how to use LVGL to display a simple text on a ST7735R-based SPI display.

In this post, we’ll extend this example by

See Minimal ESP32 PlatformIO TFT display example using Adafruit ST7735 for the recommended hardware configuration for the display. The code supports two MAX31855s but only one is used for this example. The pinout for the MAX31855 is:

constexpr int8_t Pin_MAX31855_CLK = 21;
constexpr int8_t Pin_MAX31855_MISO = 5;
constexpr int8_t Pin_MAX31855_A_CS = 18;

Using an ESP32 with a 128x160px ST7735R display, this code achieves a ~5Hz update rate. Note that this example can easily be modified to work with other LCD controllers or displays.

#include <Arduino.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <Adafruit_LvGL_Glue.h>
#include <Adafruit_MAX31855.h>
#include <string>
#include <sstream>

constexpr int8_t Pin_LCD_CS = 27;
constexpr int8_t Pin_LCD_DC = 23;
constexpr int8_t Pin_LCD_RST = 22;
constexpr int8_t Pin_LCD_SCLK = 14;
constexpr int8_t Pin_LCD_MISO = 12;
constexpr int8_t Pin_LCD_MOSI = 13;

constexpr int8_t Pin_MAX31855_CLK = 21;
constexpr int8_t Pin_MAX31855_MISO = 5;
// Two different Chip selects for two different MAX31855
constexpr int8_t Pin_MAX31855_A_CS = 18;
constexpr int8_t Pin_MAX31855_B_CS = 19;

SPIClass vspi(VSPI);
Adafruit_MAX31855 thermocouple1(Pin_MAX31855_A_CS, &vspi);
Adafruit_MAX31855 thermocouple2(Pin_MAX31855_B_CS, &vspi);

Adafruit_ST7735 lcd(Pin_LCD_CS, Pin_LCD_DC, Pin_LCD_MOSI, Pin_LCD_SCLK,
                                 Pin_LCD_RST);
Adafruit_LvGL_Glue glue;



// Number of consecutive NaN reads before we assume the thermocouple is actually disconnected
uint32_t consecutiveNaNReads = 0;
constexpr uint32_t maxConsecutiveNaNReads = 10;
std::string labelText = "";
lv_obj_t *label;


void lvgl_setup(void) {
  // Create simple label centered on screen
  label = lv_label_create(lv_scr_act());
  lv_label_set_text(label, "Initializing..");
  lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
}


template<typename T>
std::string celsiusToString(const T a_value, const int decimals = 1)
{
    std::ostringstream out;
    out.precision(decimals);
    out << std::fixed << a_value << " °C";
    return std::move(out).str();
}

bool IsValidReading(double reading) {
  return !isnan(reading) && reading > -40.0 && reading < 1000.0;
}

void ReadTemperature() {
  double celsius = thermocouple1.readCelsius();
  if(!IsValidReading(celsius)) {
    consecutiveNaNReads++;
    if(consecutiveNaNReads >= maxConsecutiveNaNReads) {
      // Thermocouple is disconnected
      lv_label_set_text(label, "Error");
    }
  } else {
    consecutiveNaNReads = 0;
    labelText = "T: " + celsiusToString(celsius);
    lv_label_set_text(label, labelText.c_str());
  }
}

void setup() {
  Serial.begin(115200);
  // Start MAX31855 SPI
  vspi.begin(Pin_MAX31855_CLK, Pin_MAX31855_MISO, Pin_MAX31855_A_CS);

  lcd.initR(INITR_BLACKTAB);      // Init ST7735S chip, black tab
  lcd.setRotation(1);

  LvGLStatus status = glue.begin(&lcd);
  if(status != LVGL_OK) {
    Serial.printf("Glue error %d\r\n", (int)status);
    ESP.restart();
  }

  lvgl_setup(); // Call UI-building function above
}

void loop() {
  ReadTemperature();
  lv_task_handler(); // Call LittleVGL task handler periodically
  delay(5);
}
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
lib_deps =
    adafruit/Adafruit GFX Library@^1.11.5
    adafruit/Adafruit ST7735 and ST7789 Library@^1.10.0
    adafruit/Adafruit LittlevGL Glue Library@^2.1.4
    adafruit/SdFat - Adafruit Fork@^2.2.1
    lvgl/lvgl@^8.3.7
    adafruit/Adafruit MAX31855 library@^1.4.0

 

 

Posted by Uli Köhler in Arduino, LVGL, PlatformIO

ST7735R LVGL live update chart example for PlatformIO

Important: In newer versions of PlatformIO, most ESP32 boards will crash with this example unless you create a custom lv_conf.h config file specifying to use internal memory instead of PSRAM. See How to fix ESP32 LVGL crash / reboot in lv_tlsf_create() / lv_mem_init() for instructions how to fix that.

In our previous example ST7735R minimal LVGL chart example for PlatformIO

See Minimal ESP32 PlatformIO TFT display example using Adafruit ST7735 for the recommended hardware configuration.

Using an ESP32 with a 128x160px ST7735R display, this code achieves a ~3Hz update rate. Note that this example can easily be modified to work with other LCD controllers or displays.

#include <Arduino.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <Adafruit_LvGL_Glue.h>
#include <lvgl.h>
#include <string>

constexpr int Pin_LCD_CS = 27;
constexpr int Pin_LCD_DC = 23;
constexpr int Pin_LCD_RST = 22;
constexpr int Pin_LCD_SCLK = 14;
constexpr int Pin_LCD_MISO = 12;
constexpr int Pin_LCD_MOSI = 13;

Adafruit_ST7735 lcd(Pin_LCD_CS, Pin_LCD_DC, Pin_LCD_MOSI, Pin_LCD_SCLK,
                                 Pin_LCD_RST);
Adafruit_LvGL_Glue glue;

lv_obj_t* chart;
lv_chart_series_t * ser1;

void lvgl_chart_setup() {
    /*Create a chart*/
    chart = lv_chart_create(lv_scr_act());
    lv_obj_set_size(chart, 160, 100);
    lv_obj_align(chart, LV_ALIGN_BOTTOM_MID, 0, -3);
    lv_chart_set_type(chart, LV_CHART_TYPE_LINE);   /*Show lines and points too*/

    /*Add two data series*/
    ser1 = lv_chart_add_series(chart, lv_palette_main(LV_PALETTE_RED), LV_CHART_AXIS_PRIMARY_Y);

    /*Set the next points on 'ser1'*/
    lv_chart_set_next_value(chart, ser1, 10);
    lv_chart_set_next_value(chart, ser1, 10);
    lv_chart_set_next_value(chart, ser1, 10);
    lv_chart_set_next_value(chart, ser1, 10);
    lv_chart_set_next_value(chart, ser1, 10);
    lv_chart_set_next_value(chart, ser1, 10);
    lv_chart_set_next_value(chart, ser1, 10);
    lv_chart_set_next_value(chart, ser1, 30);
    lv_chart_set_next_value(chart, ser1, 70);
    lv_chart_set_next_value(chart, ser1, 90);

    lv_chart_refresh(chart);
}

void lvgl_setup(void) {
  // Create simple label centered on screen
  lv_obj_t *label = lv_label_create(lv_scr_act());
  lv_label_set_text(label, "Temperature");
  lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 2);

  lvgl_chart_setup();
}

void setup() {
  Serial.begin(115200);
  lcd.initR(INITR_BLACKTAB);      // Init ST7735S chip, black tab
  lcd.setRotation(1);

  LvGLStatus status = glue.begin(&lcd);
  if(status != LVGL_OK) {
    Serial.printf("Glue error %d\r\n", (int)status);
    ESP.restart();
  }

  lvgl_setup(); // Call UI-building function above
}

void UpdateChart() {
  // Fill chart data with sine wave data
  float sineOffset = millis() / 1000.0;
  int16_t maxValue = 50;
  size_t count = lv_chart_get_point_count(chart);

  for (size_t i = 0; i < count; i++) {
    lv_chart_set_value_by_id(chart, ser1,  i, maxValue * (1 + sin((sineOffset + i) * 0.7)));
  }

  lv_chart_refresh(chart);
}

void loop() {
  // Update display
  UpdateChart();
  lv_task_handler();
  delay(5);
}
[env:esp32dev]
platform = espressif32 
board = esp32dev
framework = arduino
monitor_speed = 115200
lib_deps =
    adafruit/Adafruit GFX Library@^1.11.5
    adafruit/Adafruit ST7735 and ST7789 Library@^1.10.0
    adafruit/Adafruit LittlevGL Glue Library@^2.1.4
    adafruit/SdFat - Adafruit Fork@^2.2.1
    lvgl/lvgl@^8.3.7

 

 

Posted by Uli Köhler in Arduino, LVGL, PlatformIO

ST7735R minimal LVGL chart example for PlatformIO

Important: In newer versions of PlatformIO, most ESP32 boards will crash with this example unless you create a custom lv_conf.h config file specifying to use internal memory instead of PSRAM. See How to fix ESP32 LVGL crash / reboot in lv_tlsf_create() / lv_mem_init() for instructions how to fix that.

This example builds on our previous post Adafruit ST7735R display minimal LVGL example for PlatformIO

See Minimal ESP32 PlatformIO TFT display example using Adafruit ST7735 for the recommended hardware configuration.

Note that this example can easily be modified to work with other LCD controllers or displays.

 

#include <Arduino.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <Adafruit_LvGL_Glue.h>
#include <lvgl.h>
#include <string>

constexpr int Pin_LCD_CS = 27;
constexpr int Pin_LCD_DC = 23;
constexpr int Pin_LCD_RST = 22;
constexpr int Pin_LCD_SCLK = 14;
constexpr int Pin_LCD_MISO = 12;
constexpr int Pin_LCD_MOSI = 13;

Adafruit_ST7735 lcd(Pin_LCD_CS, Pin_LCD_DC, Pin_LCD_MOSI, Pin_LCD_SCLK,
                                 Pin_LCD_RST);
Adafruit_LvGL_Glue glue;

void lvgl_chart_setup() {
    /*Create a chart*/
    lv_obj_t * chart;
    chart = lv_chart_create(lv_scr_act());
    lv_obj_set_size(chart, 160, 100);
    lv_obj_align(chart, LV_ALIGN_BOTTOM_MID, 0, -3);
    lv_chart_set_type(chart, LV_CHART_TYPE_LINE);   /*Show lines and points too*/

    /*Add two data series*/
    lv_chart_series_t * ser1 = lv_chart_add_series(chart, lv_palette_main(LV_PALETTE_RED), LV_CHART_AXIS_PRIMARY_Y);
    lv_chart_series_t * ser2 = lv_chart_add_series(chart, lv_palette_main(LV_PALETTE_GREEN), LV_CHART_AXIS_SECONDARY_Y);

    /*Set the next points on 'ser1'*/
    lv_chart_set_next_value(chart, ser1, 10);
    lv_chart_set_next_value(chart, ser1, 10);
    lv_chart_set_next_value(chart, ser1, 10);
    lv_chart_set_next_value(chart, ser1, 10);
    lv_chart_set_next_value(chart, ser1, 10);
    lv_chart_set_next_value(chart, ser1, 10);
    lv_chart_set_next_value(chart, ser1, 10);
    lv_chart_set_next_value(chart, ser1, 30);
    lv_chart_set_next_value(chart, ser1, 70);
    lv_chart_set_next_value(chart, ser1, 90);

    /*Directly set points on 'ser2'*/
    ser2->y_points[0] = 90;
    ser2->y_points[1] = 70;
    ser2->y_points[2] = 65;
    ser2->y_points[3] = 65;
    ser2->y_points[4] = 65;
    ser2->y_points[5] = 65;
    ser2->y_points[6] = 65;
    ser2->y_points[7] = 65;
    ser2->y_points[8] = 65;
    ser2->y_points[9] = 65;

    lv_chart_refresh(chart); /*Required after direct set*/
}

void lvgl_setup(void) {
  // Create simple label centered on screen
  lv_obj_t *label = lv_label_create(lv_scr_act());
  lv_label_set_text(label, "Temperature");
  lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 2);

  lvgl_chart_setup();
}

void setup() {
  lcd.initR(INITR_BLACKTAB);      // Init ST7735S chip, black tab
  lcd.setRotation(1);

  LvGLStatus status = glue.begin(&lcd);
  if(status != LVGL_OK) {
    Serial.printf("Glue error %d\r\n", (int)status);
    ESP.restart();
  }

  lvgl_setup(); // Call UI-building function above
}

void loop() {
  lv_task_handler(); // Call LittleVGL task handler periodically
  delay(5);
}

 

[env:esp32dev]
platform = espressif32 
board = esp32dev
framework = arduino
lib_deps =
    adafruit/Adafruit GFX Library@^1.11.5
    adafruit/Adafruit ST7735 and ST7789 Library@^1.10.0
    adafruit/Adafruit LittlevGL Glue Library@^2.1.4
    adafruit/SdFat - Adafruit Fork@^2.2.1
    lvgl/lvgl@^8.3.7

 

 

Posted by Uli Köhler in Arduino, LVGL, PlatformIO

Adafruit ST7735R display minimal LVGL example for PlatformIO

Important: In newer versions of PlatformIO, most ESP32 boards will crash with this example unless you create a custom lv_conf.h config file specifying to use internal memory instead of PSRAM. See How to fix ESP32 LVGL crash / reboot in lv_tlsf_create() / lv_mem_init() for instructions how to fix that.

This example builds on the hardware & software setup outlined in Minimal ESP32 PlatformIO TFT display example using Adafruit ST7735 but uses the LVGL library to provide more advanced rendering possibilities.

See Minimal ESP32 PlatformIO TFT display example using Adafruit ST7735 for the hardware configuration.

Note that it’s just a minimal template to get started with LVGL, besides centered text rendering, no advanced LVGL techniques are being used.

#include <Arduino.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <Adafruit_LvGL_Glue.h>
#include <string>

constexpr int Pin_LCD_CS = 27;
constexpr int Pin_LCD_DC = 23;
constexpr int Pin_LCD_RST = 22;
constexpr int Pin_LCD_SCLK = 14;
constexpr int Pin_LCD_MISO = 12;
constexpr int Pin_LCD_MOSI = 13;

Adafruit_ST7735 lcd(Pin_LCD_CS, Pin_LCD_DC, Pin_LCD_MOSI, Pin_LCD_SCLK,
                                 Pin_LCD_RST);
Adafruit_LvGL_Glue glue;

void lvgl_setup(void) {
  // Create simple label centered on screen
  lv_obj_t *label = lv_label_create(lv_scr_act());
  lv_label_set_text(label, "Hello LVGL!");
  lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
}

void setup() {
  lcd.initR(INITR_BLACKTAB);      // Init ST7735S chip, black tab
  lcd.setRotation(1);

  LvGLStatus status = glue.begin(&lcd);
  if(status != LVGL_OK) {
    Serial.printf("Glue error %d\r\n", (int)status);
    ESP.restart();
  }

  lvgl_setup(); // Call UI-building function above
}

void loop() {
  lv_task_handler(); // Call LittleVGL task handler periodically
  delay(5);
}
[env:esp32dev]
platform = espressif32 
board = esp32dev
framework = arduino
lib_deps =
    adafruit/Adafruit GFX Library@^1.11.5
    adafruit/Adafruit ST7735 and ST7789 Library@^1.10.0
    adafruit/Adafruit LittlevGL Glue Library@^2.1.4
    adafruit/SdFat - Adafruit Fork@^2.2.1
    lvgl/lvgl@^8.3.7

 

 

Posted by Uli Köhler in Arduino, LVGL, PlatformIO

Adafruit ST7735R TFT display minimal text example for PlatformIO

This example builds on the hardware & software setup outlined in Minimal ESP32 PlatformIO TFT display example using Adafruit ST7735. See there for the PlatformIO example & hardware setup.

Our code displays a counter on the display that is updated every second. Only the rectangle from the last text draw is cleared, facilitating much faster screen updates than if clearing the entire screen. Also, this technique allows you to flexibly update only part of the screen instead of having to re-draw all other segments on update.

#include <Arduino.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <string>

constexpr int Pin_LCD_CS = 27;
constexpr int Pin_LCD_DC = 23;
constexpr int Pin_LCD_RST = 22;
constexpr int Pin_LCD_SCLK = 14;
constexpr int Pin_LCD_MISO = 12;
constexpr int Pin_LCD_MOSI = 13;

Adafruit_ST7735 lcd(Pin_LCD_CS, Pin_LCD_DC, Pin_LCD_MOSI, Pin_LCD_SCLK,
                                 Pin_LCD_RST);

uint16_t backgroundColor = ST7735_WHITE;
uint16_t textColor = ST7735_RED;

void setup() {
  lcd.initR(INITR_BLACKTAB);      // Init ST7735S chip, black tab
  lcd.enableDisplay(true);        // Enable display
  // Fill screen with background color
  lcd.fillScreen(backgroundColor);
}

uint32_t counter = 0;

/**
 * The rectangle to clear the previous text
 * This is computed
 * 
 * @return int 
 */
struct Rect {
  int16_t x1;
  int16_t y1;
  uint16_t w;
  uint16_t h;
};
Rect lastTextRect;

void loop() {
  // The text we'll draw
  std::string text = "#" + std::to_string(counter);

  // Clear previous text
  if(!lastTextRect.w == 0 || !lastTextRect.h == 0) {
    lcd.fillRect(
      lastTextRect.x1,
      lastTextRect.y1,
      lastTextRect.w,
      lastTextRect.h,
      backgroundColor
    );
  }

  // Set position & parameters
  lcd.setCursor(0, 0);
  lcd.setTextColor(textColor);
  lcd.setTextWrap(true);
  lcd.setTextSize(5);

  // Compute bounding box of text text we'll draw
  // (so we can clear it later)
  lcd.getTextBounds(
    text.c_str(),
    lcd.getCursorX(),
    lcd.getCursorY(),
    &lastTextRect.x1,
    &lastTextRect.y1,
    &lastTextRect.w,
    &lastTextRect.h
  );
  
  lcd.print(text.c_str());
  delay(1000);
  counter++;
}

 

Posted by Uli Köhler in Arduino, PlatformIO

Minimal ESP32 PlatformIO TFT display example using Adafruit ST7735

This example code is for the KMR-1.8 SPI display (128x160px) and provides a minimal example using the Adafruit-ST7735 library that toggles the screen from black to white repeatedly. You can use this as a check if your hardware works correctly.

Hardware connection

Note that you can use any pin number on the ESP32 ; if you use other pins, ensure to change them in the code

  • Connect A0 or DC (whichever one exists on your board) to D23 on the ESP32 board.
  • Connect SCL (which is actually SPI SCK – the pin labeled SCK is just connected to the SD card!) to D14 on the ESP32 board.
  • Connect SDA (which is actually MOSI – the pin labeled MOSI is just connected to the SD card!) to D12 on the ESP32 board.
  • Connect CS to D27 on the ESP32 board.
  • ConnectRESET to D22 on the ESP32 board.
  • Connect LED- to GND on the ESP32 board.
  • Connect VCC to 3V3 on the ESP32 board.
  • Connect LED+ to 3V3 on the ESP32 board.
  • Connect Vcc to 3V3 on the ESP32 board.

Do not connect any pin to VIn or 5V. This can easily damage your display!

PlatformIO source code

#include <Arduino.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>

constexpr int Pin_LCD_CS = 27;
constexpr int Pin_LCD_DC = 23;
constexpr int Pin_LCD_RST = 22;
constexpr int Pin_LCD_SCLK = 14;
constexpr int Pin_LCD_MISO = 12;
constexpr int Pin_LCD_MOSI = 13;

Adafruit_ST7735 lcd(Pin_LCD_CS, Pin_LCD_DC, Pin_LCD_MOSI, Pin_LCD_SCLK,
                                 Pin_LCD_RST);

void setup() {
  lcd.initR();      // Init ST7735S chip, black tab
  lcd.enableDisplay(true);        // Enable display
}

void loop() {
  delay(500);
  lcd.fillScreen(ST7735_BLACK);
  delay(500);
  lcd.fillScreen(ST7735_WHITE);
}
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
lib_deps =
    adafruit/Adafruit GFX Library@^1.11.5
    adafruit/Adafruit ST7735 and ST7789 Library@^1.10.0

 

Posted by Uli Köhler in Arduino, PlatformIO

heatshrink compression library: How to compress data from a buffer

This is a starting point for how to compress data from a buffer using heatshrink and write the data into an output buffer:

Note that this has not been thoroughly tested, but at least it doesn’t crash 🙂

// TODO Your code goes here
const size_t outbuf_size = filesize + 128;
char* outbuf = new char[outbuf_size];
heatshrink_encoder* encoder = heatshrink_encoder_alloc(10, 4);
size_t tosink = filesize;
size_t output_size = 0;
while(tosink > 0) {
    size_t sunk = 0;
    auto err = heatshrink_encoder_sink(encoder, reinterpret_cast<uint8_t*>(buf), (size_t)filesize, &sunk);
    if(err != HSER_SINK_OK) {
        std::cerr << "Error sinking data" << std::endl;
        break;
    }
    if(sunk == 0) {
        std::cerr << "No data sunk" << std::endl;
        break;
    }
    // Check how much has been sunk & update tosink
    tosink -= sunk;
    // Poll for output
    size_t polled = 0;
    auto err2 = heatshrink_encoder_poll(encoder, reinterpret_cast<uint8_t*>(outbuf + output_size), outbuf_size - output_size, &polled);
    if(err2 == HSER_POLL_ERROR_NULL || err2 == HSER_POLL_ERROR_MISUSE) {
        std::cerr << "Error polling data" << std::endl;
        break;
    }
    output_size += polled;
}
// Input data finished
auto err3 = heatshrink_encoder_finish(encoder);
// Poll for final output
// Poll for output
size_t polled = 0;
auto err2 = heatshrink_encoder_poll(encoder, reinterpret_cast<uint8_t*>(outbuf + output_size), outbuf_size - output_size, &polled);
if(err2 == HSER_POLL_ERROR_NULL || err2 == HSER_POLL_ERROR_MISUSE) {
    std::cerr << "Error finally polling data" << std::endl;
}
output_size += polled;

cout << "Original size: " << filesize << ", compressed size: " << output_size << endl;

 

Posted by Uli Köhler in C/C++, Embedded

Systemd service to use a DS3231 RTC on the Raspberry Pi

The following systemd service will automatically. See this guide for more information on the setup and ensure sudo i2cdetect -y 1 detects the RTC with address 0x68.

This is an automatic service installation & enable script based on A simple systemd service autoinstall script . This script will automatically enable the service on boot:

#!/bin/bash
# This script installs and enables/starts a systemd service
# It also installs the service file
export NAME=ConfigureRTC

cat >/etc/systemd/system/${NAME}.service <<EOF
[Unit]
Description=${NAME}

[Service]
ExecStart=/bin/bash -c 'echo ds1307 0x68 > /sys/class/i2c-adapter/i2c-1/new_device && hwclock -s'
Restart=always

[Install]
WantedBy=multi-user.target
EOF

# Enable and start service
systemctl enable --now ${NAME}.service

This is just the systemd service:

[Unit]
Description=ConfigureRTC

[Service]
ExecStart=/bin/bash -c 'echo ds1307 0x68 > /sys/class/i2c-adapter/i2c-1/new_device && hwclock -s'
Restart=always

[Install]
WantedBy=multi-user.target

 

Posted by Uli Köhler in Linux, Raspberry Pi

How to enable or disable NTP time synchronization on the Raspberry Pi

Disable NTP:

sudo systemctl disable --now systemd-timesyncd

Enable NTP:

sudo systemctl enable --now systemd-timesyncd

Verifying if NTP is active

You can verify if NTP is active or not by running

timedatectl

Then look for these lines:

System clock synchronized: yes
NTP service: active

System clock synchronized will tell you if the NTP service has successfully synchronized the system time to a NTP time server: yes if synchronized, no if not synchronized.

NTP service will tell you if the NTP service is running, i.e. if it is trying to synchronize the system time to a NTP time server: active if running, inactive when not running

Output with NTP active:

               Local time: Tue 2023-03-14 16:49:28 CET
           Universal time: Tue 2023-03-14 15:49:28 UTC
                 RTC time: Tue 2023-03-14 15:49:28
                Time zone: Europe/Berlin (CET, +0100)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no

Output with NTP inactive:

               Local time: Tue 2023-03-14 16:48:01 CET
           Universal time: Tue 2023-03-14 15:48:01 UTC
                 RTC time: Tue 2023-03-14 15:48:01
                Time zone: Europe/Berlin (CET, +0100)
System clock synchronized: no
              NTP service: inactive
          RTC in local TZ: no

 

Posted by Uli Köhler in Linux, Raspberry Pi

How to enable I2C port on the Raspberry Pi using raspi-config

sudo raspi-config nonint do_i2c 0

Now load the relevant modules:

sudo modprobe "i2c-bcm2835"
sudo modprobe "i2c-dev"
sudo modprobe "rtc-ds1307"

and now check if the I2C device file exists using stat /dev/i2c-1:

$ stat /dev/i2c-1
  File: /dev/i2c-1
  Size: 0               Blocks: 0          IO Block: 4096   character special file
Device: 5h/5d   Inode: 169         Links: 1     Device type: 59,1
Access: (0660/crw-rw----)  Uid: (    0/    root)   Gid: (  998/     i2c)
Access: 2023-03-14 16:23:06.643999999 +0100
Modify: 2023-03-14 16:23:06.643999999 +0100
Change: 2023-03-14 16:23:06.643999999 +0100
 Birth: -

If you instead see

ls: cannot access '/dev/i2c-1': No such file or directory

either the driver is not loaded properly or I2C is disabled. Try rebooting the system and repeating the commands above and possibly checking dmesg for any error messages.

Posted by Uli Köhler in Raspberry Pi

How to fix Raspberry Pi i2cdetect: command not found

Problem:

When trying to detect I2C devices on the Raspberry Pi (Raspbian) using i2cdetect, you see the following error:

$ i2cdetect
bash: i2cdetect: command not found

Solution:

Install i2c-tools using

sudo apt -y install i2c-tools

After installing i2c-tools , you can use i2cdetect and other related tools such as i2cget.

Posted by Uli Köhler in Embedded, Linux, Raspberry Pi

ESP-IDF HTTP webserver minimal ArduinoJson serialization example

static const httpd_uri_t valueHandler = {
    .uri       = "/api/value",
    .method    = HTTP_GET,
    .handler   = [](httpd_req_t *req) {
        httpd_resp_set_type(req, "application/json");
        // create json docuemnt
        DynamicJsonDocument json(1024);
        json["value"] = 1.0;
        // Serialize JSON to string
        std::string buf;
        serializeJson(json, buf);
        // Send response
        httpd_resp_send(req, buf.c_str(), buf.length());
        return ESP_OK;
    }
};

In order to add the ArduinoJson to PlatformIO, add the following lib_deps to platformio.ini:

lib_deps =
    [email protected]

 

Posted by Uli Köhler in Arduino, ESP8266/ESP32

How to fix erpc ValueError: The generated shim code version … is different to the rest of eRPC code

Problem:

When trying to import your erpc project generated Python code using e.g.

from erpc_myproject import *

You see the following error message:

Traceback (most recent call last)
Cell In [1], line 1
----> 1 from erpc_myproject import *

File ./erpc_myproject/__init__.py:13
     11     version = "unknown"
     12 if version != "1.9.1":
---> 13     raise ValueError("The generated shim code version (1.9.1) is different to the rest of eRPC code (%s). \
     14 Install newer version by running \"python setup.py install\" in folder erpc/erpc_python/." % repr(version))
     16 from . import common
     17 from . import client

ValueError: The generated shim code version (1.9.1) is different to the rest of eRPC code ('unknown'). Install newer version by running "python setup.py install" in folder erpc/erpc_python/.

Solution:

Either you have not installed the erpc Python library (if the error message lists ... different to the rest of eRPC code ('unknown')) or you have installed the wrong version (e.g. ... (1.9.1) is different to the rest of eRPC code ('1.10.0')).

If you have not installed erpc at all, simply use

pip install erpc

and retry running your script.

If you have installed the wrong version, you have two options:

Option 1 (preferred): Re-generate the code

Just use the original command (some erpcgen call) you’ve used to re-generate the code using the currently installed version.

Option 2: Install the correct version

For this, you need to determine what the correct version is. Let’s consider the following error message:

ValueError: The generated shim code version (1.9.1) is different to the rest of eRPC code ('1.10.0'). Install newer version by running "python setup.py install" in folder erpc/erpc_python/.

From this message, we can read that the shim code version is 1.9.1 whereas you have 1.10.0 installed. Therefore, in order to make it work, we need to install erpc version 1.9.1.

Install it using

pip install -U erpc==1.9.1

and then retry your command. If you are using jupyter notebooks or similar, you need to restart your kernel to load the new library!

Posted by Uli Köhler in Embedded, Python

How to read length-prefixed binary message from Serial using Arduino

The following function allows you to read a binary message, prefixed by a single length byte, from Serial:

#include <Arduino.h>

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

void HandleMessage(String msg) {
    // TODO: Your code to handle the message goes here.
    // See https://techoverflow.net/2022/11/15/how-to-print-string-as-sequence-of-hex-bytes-in-arduino/
    // for an example of how to print the message as a sequence of hex bytes.
}

void ReadMessageFromSerial() {
    // Wait until the length byte is available on Serial
    while (Serial.available() == 0);

    // Read the length of the message
    int length = Serial.read();

    // Read the rest of the message
    String message = "";
    for (int i = 0; i < length; i++) {
      while (Serial.available() == 0);
      message += char(Serial.read());
    }

    // Handle the message
    HandleMessage(message);
}

void loop() {
    ReadMessageFromSerial();
}

 

 

Posted by Uli Köhler in Allgemein, Arduino, Electronics, Embedded

How to fix ESP32 error: ‘ESP_LOGE’ was not declared in this scope

Problem:

When trying compile your ESP32 project, you see an error message such as

.pio/libdeps/esp32dev/HumanESPHTTP/src/QueryURLParser.cpp:29:9: error: 'ESP_LOGE' was not declared in this scope
         ESP_LOGE("Query URL parser", "parsing URL");
         ^~~~~~~~

Solution:

At the top of the file where the error occurs (QueryURLParser.cpp in this example), add the following line:

#include <esp_log.h>

 

 

Posted by Uli Köhler in C/C++, ESP8266/ESP32

How to fix ESP32 Last error reported from esp-tls: 0x8008

Problem:

While trying to use TLS such as MQTTS or HTTPS on the ESP32, you see an error message like

E (333183) MQTT_CLIENT: mqtt_message_receive: transport_read() error: errno=119
[328153][E][MyMQTT.cpp:80] log_error_if_nonzero(): [MQTT] Last error reported from esp-tls: 0x8008
E (333191) MQTT_CLIENT: mqtt_process_receive: mqtt_message_receive() returned -1

Solution:

0x8008 means  ESP_ERR_ESP_TLS_TCP_CLOSED_FIN. In other words, a TCP connection had been established successfully but unexpectedly, the connection has been closed by the server.

This is often caused by the server software crashing, or restarting in some way. When a server process is terminated, the operating system will cleanup after it and close all connections.

In order to debug the issue, start by checking the log of your server message and/or system log to check for unintended crashes. If that doesn’t help, it’s sometimes helpful to packet capture the communication between the ESP32 and the server. You can also write a software script doing the same communication with the server as the ESP32. This will often allow you to try out changes much more easily than on the microcontroller and observe what’s happening using a debugger.

Posted by Uli Köhler in C/C++, Embedded, ESP8266/ESP32, Networking

How to fix ESP32 TRANSPORT_WS: Sec-WebSocket-Accept not found

Problem:

Your ESP32 running a MQTT client is printing the following error messages:

E (285025) TRANSPORT_WS: Sec-WebSocket-Accept not found
E (285025) MQTT_CLIENT: Error transport connect

Solution:

You’re using MQTT over websockets (ws:// or wss://) but on the given MQTT URL, no MQTT-over-websocket server is running.

This is often caused by using a wrong URL (possibly the URL is missing the path), but it might also be caused by a misconfiguraton of the server or the reverse proxy.

It’s often best to try using a software websocket client to test the correct settings.

Posted by Uli Köhler in ESP8266/ESP32, MQTT, Networking