ayushsharma82 / ElegantOTA

OTA updates made slick and simple for everyone!
https://elegantota.pro
GNU Affero General Public License v3.0
643 stars 119 forks source link

MakerFabs ESP32 S3 in bootloop #205

Closed yesnoj closed 3 months ago

yesnoj commented 4 months ago

I'm writing a small project using this board that include a 3.5" display. I'm runing FreeRTOS for some stuff on a separate core, and LVGL on first core, to manage all the UI things. I don't know why ESP32 S3 the board keeps crashing. I've noticed that doesn't crash instantly first time, but takes 4-5 seconds, after this, it goes in this bootloop. I've used the correct dependencies specified here https://docs.elegantota.pro/async-mode/ ,the .zip files were installed using Arduino IDE. The option to use another IDE is not contempled, as the project is quite finished. Don't know if the LVLG/FreeRTOS stuff can , in someway, cause some issues, the error is the following : "22:01:04.359 -> Guru Meditation Error: Core 0 panic'ed (StoreProhibited). Exception was unhandled."

The code is super simple...

#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <ElegantOTA.h>

AsyncWebServer otaServer(80);

void connectOta(){
  if (!WiFi.softAP(ssid, password)) {
    LV_LOG_USER("Soft AP creation failed.");
    while(1);
  }
  IPAddress myIP = WiFi.softAPIP();
  LV_LOG_USER("AP IP address: %s",myIP.toString());

  ElegantOTA.setAutoReboot(false);
  ElegantOTA.setAuth(user, pass);
  ElegantOTA.begin(&otaServer);
  otaServer.begin();
  LV_LOG_USER("Server started");

  // ElegantOTA callbacks
  ElegantOTA.onStart(onOTAStart);
  ElegantOTA.onProgress(onOTAProgress);
  ElegantOTA.onEnd(onOTAEnd);
}

in the loop there is : ElegantOTA.loop();

I've also followed the recommendations in the two dependencies library but nothing, it keeps crashing :

-D CONFIG_ASYNC_TCP_MAX_ACK_TIME=3000 -D CONFIG_ASYNC_TCP_PRIORITY=10 -D CONFIG_ASYNC_TCP_QUEUE_SIZE=128 -D CONFIG_ASYNC_TCP_RUNNING_CORE=1 -D CONFIG_ASYNC_TCP_STACK_SIZE=4096

Any suggestion!? :/

yesnoj commented 4 months ago

I've done a test without other parts of code, just the following one, and is working perfectly. Maybe there is something that disturb the Async stuff...really don't know what think... Here below that is the code that works :

#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <ElegantOTA.h>

const char* WIFI_SSID = "ESP32-Access-Point";
const char* WIFI_PASSWORD = "your_password";

#define USERNAME  "username"
#define PASSWORD  "password"

AsyncWebServer server(80);
unsigned long ota_progress_millis = 0;

void onOTAStart() {
  Serial.println("OTA update started!");
}

void onOTAProgress(size_t current, size_t final) {
  // Log every 1 second
  if (millis() - ota_progress_millis > 1000) {
    ota_progress_millis = millis();
    Serial.printf("OTA Progress Current: %u bytes, Final: %u bytes\n", current, final);
  }
}

void onOTAEnd(bool success) {
  if (success) {
    Serial.println("OTA update finished successfully!");
  } else {
    Serial.println("There was an error during OTA update!");
  }
}

void connectOtaAP(){
  WiFi.softAP(WIFI_SSID, WIFI_PASSWORD);

  IPAddress myIP = WiFi.softAPIP();
  Serial.printf("AP IP address: %s\n", myIP.toString().c_str());

  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send(200, "text/plain", "Hello, this is the ESP32 S3 Access Point!");
  });

  ElegantOTA.setAutoReboot(true);
  ElegantOTA.setAuth("username", "password");

  ElegantOTA.begin(&server);

  ElegantOTA.onStart(onOTAStart);
  ElegantOTA.onProgress(onOTAProgress);
  ElegantOTA.onEnd(onOTAEnd);

  // Avvia il server web
  server.begin();
  Serial.println("HTTP server started");
}

 void setup(void) {
  Serial.begin(115200);
  Serial.println("Booting...");

  connectOtaAP();
}

void loop() {
 ElegantOTA.loop();
}
mathieucarbou commented 4 months ago

I don't know what your application does, but when doing an OTA, it is advised to use the onBefore callback and stop most of the things running in your app (except the UI), especially if you have peripheral and/or code using interrupts.

Also, ElegantOTA.loop(); is only required if you tell ElegantOTA to trigger itself a reboot after flash. If you use the callback to listen for the end of OTA, you can do some stuff, then reboot yourself. In that case, no need for ElegantOTA.loop();.

ayushsharma82 commented 4 months ago

Decoding exception trace which is printed on crash will tell you what’s causing it. Without a decoded exception trace, we both are clueless.

ayushsharma82 commented 4 months ago

and like @mathieucarbou suggested, it’s probably because you got some core intensive task running in background while your device tried to OTA update. You should stop any running tasks using the callback, the device should be in the most “calm” state when update is happening.

yesnoj commented 4 months ago

I don't know what your application does, but when doing an OTA, it is advised to use the onBefore callback and stop most of the things running in your app (except the UI), especially if you have peripheral and/or code using interrupts.

Also, ElegantOTA.loop(); is only required if you tell ElegantOTA to trigger itself a reboot after flash. If you use the callback to listen for the end of OTA, you can do some stuff, then reboot yourself. In that case, no need for ElegantOTA.loop();.

Thanks for your reply, i've commented the ElegantOTA.loop() in the loop function, but still crashes. :/ I'm sure is something related with the tasks LVGL and FreeRTOS...just because with a simple stand-alone stuff works perfectly. I can try to use onBefore callback , but for now, the issue is that my applications goes in bootloop from the beginning. I've also attempted to add the ElegantOTA.loop() task to the second core of the ESP32, but the behaviour is the same. But maybe isn't that one the issue,is possible to execute on a different core the stuff related the ElegantOTA?Maybe this can solve the issue...i don't know :/

mathieucarbou commented 4 months ago

just run esptool.py erase_flash to erase everything, it shoud work even if your app is in a bootloop.

yesnoj commented 4 months ago

i've found that commenting the task that update the LVGL UI, this works without crashing. The problem is, how these two stuff can work together?!

void loop() { //lv_task_handler(); / let the GUI do its work / delay(5); }

However, there is another problem, because i see the SSID , but can't connect...appears that i write the wrong password. All works in a separate sketch, with just the ElegantOTA stuff :(

mathieucarbou commented 4 months ago

Like I said... While updating, you better stop your peripheral. LVGL is using a screen - this is a peripheral. it's because writing to disk on esp32 can delay interrupts and can have an effect on code dealing with peripheral and interrupts.

The right way to do that:

yesnoj commented 4 months ago

Like I said... While updating, you better stop your peripheral. LVGL is using a screen - this is a peripheral. it's because writing to disk on esp32 can delay interrupts and can have an effect on code dealing with peripheral and interrupts.

The right way to do that:

  • use the elegantota before callback to draw something to the screen, then stop updating it in the loop while the ota is in progress
  • when the elegantota callback is called to notify the end, then you can start redrawing, and reboot.

Thanks Mathieu i will try this approach, but where is defined this callback "onBefore"? i can't find it ... :/ Are you referring to "onStart"?I can find just onStart, onProgress and onEnd callbacks. Sorry for the noob question, Thanks in advance!

mathieucarbou commented 4 months ago

Like I said... While updating, you better stop your peripheral. LVGL is using a screen - this is a peripheral. it's because writing to disk on esp32 can delay interrupts and can have an effect on code dealing with peripheral and interrupts. The right way to do that:

  • use the elegantota before callback to draw something to the screen, then stop updating it in the loop while the ota is in progress
  • when the elegantota callback is called to notify the end, then you can start redrawing, and reboot.

Thanks Mathieu i will try this approach, but where is defined this callback "onBefore"? i can't find it ... :/ Are you referring to "onStart"?I can find just onStart, onProgress and onEnd callbacks. Sorry for the noob question, Thanks in advance!

read the doc and header ;-)

yesnoj commented 4 months ago

Like I said... While updating, you better stop your peripheral. LVGL is using a screen - this is a peripheral. it's because writing to disk on esp32 can delay interrupts and can have an effect on code dealing with peripheral and interrupts. The right way to do that:

  • use the elegantota before callback to draw something to the screen, then stop updating it in the loop while the ota is in progress
  • when the elegantota callback is called to notify the end, then you can start redrawing, and reboot.

Thanks Mathieu i will try this approach, but where is defined this callback "onBefore"? i can't find it ... :/ Are you referring to "onStart"?I can find just onStart, onProgress and onEnd callbacks. Sorry for the noob question, Thanks in advance!

read the doc and header ;-)

ciao Mathieu, the problem is that i've done this research, and also inside the whole repository the word "before" isn't used for any kind of method : https://github.com/search?q=repo%3Aayushsharma82%2FElegantOTA%20before&type=code :/ Usually i try to find stuff before ask hahaha :D

mathieucarbou commented 4 months ago

It is called onStart sorry.

You could have made the effort to open the header, like I siggested... A header in C++ is the documentation of the API.... Always look at the header when you use a library!

https://github.com/ayushsharma82/ElegantOTA/blob/master/src/ElegantOTA.h#L114-L116

yesnoj commented 4 months ago

Bonjour Mathieu, thanks for your answer! As i'm using the Async mode for ElegantOTA, i've opened and read the ElegantOTA.h to enable the ELEGANTOTA_USE_ASYNC_WEBSERVER. However i've tried yesterday to use the onStart but didn't worked. onStart() is executed one time at the beginning, when server is started. There isn't a method that runs always before the upload of the .bin file, so i can catch that moment when i need to stop other stuff to do just the upload. Maybe the problem is that i'm using WiFi.softAPIP instead of use WIFI_STA ...but i need the AP mode in my project :/

mathieucarbou commented 4 months ago

No it works, and both on STA and AP mode. That's what I am using in all my projects, plus the callbacks. Anyway, whatever the issue, the discussion will go nowhere unless you show your code.

yesnoj commented 4 months ago

Mathieu, this is the code i've for the "main.ino" file : Basically here are initialized all the stuff required, hope this can be helpfull to understand where the issue is located :/

#include <lvgl.h>
#include <driver/i2c.h>

#include "SD.h"
#include "SPI.h"

#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <ElegantOTA.h>

#include "include/definitions.h"
#include "include/accessory.c"

const char* WIFI_SSID     = "FILMACHINE_WIFI";
const char* WIFI_PASSWORD = "password";
const char* USERNAME      = "username";
const char* PASSWORD      = "password";

AsyncWebServer otaServer(80);
unsigned long ota_progress_millis = 0;

lv_display_t *lvDisplay;
lv_indev_t *lvInput;

LGFX lcd;

bool stopMotorManTask = false;
uint8_t initErrors = 0;

/*LVGL draw into this buffer, 1/10 screen size usually works well. The size is in bytes*/
const unsigned int lvBufferSize = (TFT_HOR_RES * TFT_VER_RES * 2) / 10;
void *lvBuffer1 = heap_caps_malloc(lvBufferSize, MALLOC_CAP_INTERNAL);
void *lvBuffer2 = heap_caps_malloc(lvBufferSize, MALLOC_CAP_INTERNAL);

#define LVGL_TICK_PERIOD_MS    1
static void increase_lvgl_tick( void *arg ) {
  lv_tick_inc(LVGL_TICK_PERIOD_MS);
}

#define MEM_MSG_DISPLAY_TIME  20000
void sysMan( void *arg ) {

  uint16_t  msg;

    while(1) {  // This is a task which runs for ever
    /* This will time out after MEM_MSG_DISPLAY_TIME and print memory then wait again */
        if( xQueueReceive( gui.sysActionQ, &msg, pdMS_TO_TICKS(MEM_MSG_DISPLAY_TIME) ) ) { 
            switch(msg) {

            case SAVE_PROCESS_CONFIG:
          writeConfigFile(SD, FILENAME_SAVE, false);
          break;
      // Add Further processor intensive tasks here to keep them out of the GUI execution path
            case SAVE_MACHINE_STATS:
            LV_LOG_USER("Save On EEPROM!");
          writeMachineStats(&gui.page.tools.machineStats);
          break;
      case RELOAD_CFG:
          LV_LOG_USER("Reload FilMachine.cfg from backup");
          if(copyAndRenameFile(SD, FILENAME_BACKUP, FILENAME_SAVE))
              rebootBoard();
      default:
          LV_LOG_USER( "Unknown System Manager Request!");
          break;
      }
    } 
    #if FILM_USE_LOG != 0
    else {  
      LV_LOG_USER("\nFree Heap: %u bytes\n"
        "  MALLOC_CAP_SPIRAM    %7zu bytes\n"
        "  MALLOC_CAP_INTERNAL  %7zu bytes\n",
        xPortGetFreeHeapSize(),
        heap_caps_get_free_size(MALLOC_CAP_SPIRAM),
        heap_caps_get_free_size(MALLOC_CAP_INTERNAL)
      );
    }
    #endif
    }
}

void motorMan(void *arg) {
    int8_t rotation = 1;
    uint8_t prevRotation = rotation;
    uint16_t msg;

    while(!stopMotorManTask) {
        TickType_t interval = pdMS_TO_TICKS(1000);

        if (xQueueReceive(gui.sysActionQ, &msg, interval)) {
        } else {
            switch(rotation) {
                case 1:
                    runMotorFW(MOTOR_IN1_PIN, MOTOR_IN2_PIN);
                    prevRotation = 1;
                    rotation = 0;
                    interval = pdMS_TO_TICKS(getRandomRotationInterval() * 1000);
                    break;
                case -1:
                    runMotorRV(MOTOR_IN1_PIN, MOTOR_IN2_PIN);
                    prevRotation = -1;
                    rotation = 0;
                    interval = pdMS_TO_TICKS(getRandomRotationInterval() * 1000);
                    break;
                case 0:
                default:
                    stopMotor(MOTOR_IN1_PIN, MOTOR_IN2_PIN);
                    if (prevRotation == 1) {
                        rotation = -1;
                    } else if (prevRotation == -1) {
                        rotation = 1;
                    } else {
                        rotation = 1; // Valore iniziale se prevRotation non è 1 o -1
                    }
                    prevRotation = 0;
                    interval = pdMS_TO_TICKS(1000);
                    break;
            }
        }
        vTaskDelay(interval);
    }

    stopMotor(MOTOR_IN1_PIN, MOTOR_IN2_PIN);
    vTaskDelete(NULL); // Termina il task
}

void stopMotorTask() {
    stopMotorManTask = true;
}

void runMotorTask() {
    xTaskCreatePinnedToCore( motorMan, "motorMan", 4096, NULL, 8,  NULL, 0 ); 
    stopMotorManTask = false;
}

void onOTAStart() {
  LV_LOG_USER("OTA update started!");
}

void onOTAProgress(size_t current, size_t final) {
  // Log every 1 second
  uint8_t percentage;
  float_t cur,fin;

  cur = (float_t)(current);
  fin = (float_t)(final);
  if (millis() - ota_progress_millis > 1000) {
    ota_progress_millis = millis();
    percentage = (uint8_t)((cur / fin) * 100);
    LV_LOG_USER("OTA Progress Current: %u %%, %u bytes, Final: %u bytes\n", percentage, current, final);
  }
}

void onOTAEnd(bool success) {
  if (success) {
    LV_LOG_USER("OTA update finished successfully!");
  } else {
    LV_LOG_USER("There was an error during OTA update!");
  }
}

void connectOtaAP(){

  WiFi.softAP(WIFI_SSID, WIFI_PASSWORD);

  IPAddress myIP = WiFi.softAPIP();
  LV_LOG_USER("AP IP address: %s",myIP.toString());

  ElegantOTA.setAutoReboot(true);
  ElegantOTA.setAuth(USERNAME, PASSWORD);

  ElegantOTA.begin(&otaServer);

 // ElegantOTA callbacks
  ElegantOTA.onStart(onOTAStart);
  ElegantOTA.onProgress(onOTAProgress);
  ElegantOTA.onEnd(onOTAEnd);

  otaServer.begin();
  LV_LOG_USER("Server started");
}

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

    LV_LOG_USER("Hello FilMachine! - This software uses LVGL V%d.%d.%d",lv_version_major(),lv_version_minor(),lv_version_patch());

    pinMode(LCD_CS, OUTPUT);
    pinMode(LCD_BLK, OUTPUT);

    digitalWrite(LCD_CS, LOW);
    digitalWrite(LCD_BLK, HIGH);

    lcd.init();
    lcd.setRotation(1);

    SPI.begin(SD_SCLK, SD_MISO, SD_MOSI);

    LV_LOG_USER("Install LVGL tick timer");
    // Tick interface for LVGL (using esp_timer to generate LVGL_TICK_PERIOD_MS periodic event)
    const esp_timer_create_args_t lvgl_tick_timer_args = {
        .callback = increase_lvgl_tick,
        .name = "lvgl_tick"
    };
    esp_timer_handle_t lvgl_tick_timer = NULL;
    esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer);
    esp_timer_start_periodic(lvgl_tick_timer, LVGL_TICK_PERIOD_MS * 1000);

    lv_init();

    lvDisplay = lv_display_create(TFT_HOR_RES, TFT_VER_RES);
    lv_display_set_color_format(lvDisplay, LV_COLOR_FORMAT_RGB565);
    lv_display_set_flush_cb(lvDisplay, my_disp_flush);

    lv_display_set_buffers(lvDisplay, lvBuffer1, lvBuffer2, lvBufferSize, LV_DISPLAY_RENDER_MODE_PARTIAL);

    lvInput = lv_indev_create();
    lv_indev_set_type(lvInput, LV_INDEV_TYPE_POINTER);
    lv_indev_set_read_cb(lvInput, my_touchpad_read);

    initGlobals();

    initPinouts();
    homePage();

    /* Create System message queue */
    gui.sysActionQ = xQueueCreate( 16, sizeof( uint16_t ) );
    /* Create task to process external functions which will slow the GUI response */                            
    xTaskCreatePinnedToCore( sysMan, "sysMan", 4096, NULL, 8,  NULL, 0 ); 

    readConfigFile(SD, FILENAME_SAVE, false);
    readMachineStats(&gui.page.tools.machineStats);

    //this with lv_task_handler in loop, cause bootloop
    connectOtaAP(); 
}

void loop()
{
    lv_task_handler(); /* let the GUI do its work */
    ElegantOTA.loop();
    delay(5);
}
mathieucarbou commented 4 months ago

I don't see anything wrong in your code. onOTAStart should only be called once when OTA starts.