ESP8266 alternatív OTA

Fórumok

A partíció 1MB FW 3MB SPIFFS. A Firmware ~900kB
Ehhez szeretnék valami WiFi-s frissítést.
A normál OTA esélytelen. Arra gondoltam ha a rendes FW által AsyncWebServer segítségével fogadott fájl pl "update.bin" akkor lezárja a fájlrendszert és kiírja 0x100000-tól a bin-t a flesbe. Fájlokat buktam, leszarom, az új FW formatálja. Ez egy AsyncElegantOTA firmware lenne. Az eredeti FW setup első sora megnézi az eepromot amiből kiderül hogy frissíteni kell-e, ha igen akkor 0x100000 címen levő kódot futtatja. Lehet egy .ino-t fordítani úgy hogy a 0x100000 címen kezdődjön és setupból indítható legyen?

Hozzászólások

van amikor nem forraszthato ki az esp chip a cuccbol.

Nem az ESP chip-ben van az EEPROM, az egy másik chip.

persze en is tudok olyan esp-t keresni mivel fel sem merul a problema.

Három út van: vagy nagyobb EEPROM vagy kisebb igények vagy másik architektúra. Az ESP8266 esetén a chip működése adott, az OTA működést a chip firmware adja, az kezeli az EEPROM újraírását, ezt nem tudod cserélni.

Esküszöm nem értelek. Ezt én írtam, törli a flasht, az eeprom első hat bájtját megőrzi.

@echo off
COLOR A
echo Full Format ^!^!^!^!^!^!^!^!^!^!^!
setlocal EnableDelayedExpansion
SET /P M=COM port: 
echo -------------------------
echo BACKUP REGISTRATION CODE
esptool.exe --port COM%M% read_flash 0x3FB000 6 code.txt
echo SUCCESS!
echo -------------------------
echo ERASE FLASH
esptool.exe --port COM%M% erase_flash
echo SUCCESS!
echo -------------------------

set "INPUT=code.txt"
set "OUTPUT=eeprom_full.bin"
set "PADBIN=pad_ff.bin"
set "HEX=pad_ff.hex"
set /a TOTAL=4096

for %%I in (%INPUT%) do set /a SIZE=%%~zI

if !SIZE! GEQ !TOTAL! (
    echo %INPUT% is already >= 4096 bytes
    copy /Y %INPUT% %OUTPUT% >nul
    goto :done
)

set /a NEED=!TOTAL! - !SIZE!

set /a LINES=!NEED! / 16
set /a REM=!NEED! %% 16

> %HEX% (
    for /L %%i in (1,1,!LINES!) do echo FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
    if !REM! NEQ 0 (
        set "LINE="
        for /L %%j in (1,1,!REM!) do set "LINE=!LINE!FF "
        echo !LINE!
    )
)

certutil -f -decodehex %HEX% %PADBIN% >nul 2>&1
if errorlevel 1 (
    echo HIBA: certutil nem tudta létrehozni a bináris fájlt.
    goto :error
)

copy /b %INPUT% + %PADBIN% %OUTPUT% >nul

del %PADBIN%
del %HEX%

echo WRITE REGISTRATION CODE
esptool.exe --port COM%M% write_flash 0x3FB000 eeprom_full.bin
del %OUTPUT%
echo SUCCESS!
goto :done

:error
echo ERROR.
exit /b 1

:done
endlocal
echo -------------------------

Na hol az eeprom?

Esküszöm nem értelek.

A fő probléma az, hogy az ESP8266 működését, felépítését és architektúráját se érted.

Na hol az eeprom?

Mindegy, hogy minek hívod a fájlt, a neve nem kerül kiírásra, az csak a gépeden jelent bármit is, viszont a Flash-ban van ugyanúgy és ahogy írtam, a Flash az egy EEPROM altípus... amit te `eeprom_full.bin` néven nevezel és kiírsz a 0x3FB000 területre - az a memory map specifikáció szerint "Unused" régió, oda azt írsz, amit akarsz, és bárhogy nevezheted a fájlt a gépeden, semmit nem jelent.

Szóval újra: a Flash egy egy EEPROM altípus. Az ESP8266 külső EEPROM nélkül nem működik. Az OTA boot folyamatot az ESP8266 belső ROM firmware kezeli, az meg csak úgy tud működni, hogy van egy firmware partíció és ott van két slot. Ha nincs két slot, akkor nincs OTA. Fájlrendszerből nem fog működni a boot, fájlrendszerből úgy tud működni, hogy ugyanúgy van két slot a firmware partícióban és a fájlrendszerről átmásolja valamelyikbe a firmware-t. Olyan nem lesz, amit szeretnél, de ez amúgy le van írva a specifikációkban is.

Nekem az az ic FLASH. EEPROM az a 24cXX

Neked lehet, a 24Cxx az egy konkrét gyártó konkrét egy EEPROM IC típuscsaládja. Ezen kívül van millió fajta EEPROM. Minden olyan IC EEPROM, ami elektromosan törölhető és programozható, ez egy naaaaagy gyűjtőnév. Olvasnivaló a témában: https://en.wikipedia.org/wiki/EEPROM

Az a kérdés tudok-e egy ElegantOTA fw-t így fordítani.

Az ElegantOTA egy pár soros wrapper az ESP8266 saját OTA firmware kódja felett, azzal pedig nem tudsz olyan OTA-t csinálni, hogy "1MB FW 3MB SPIFFS" használatával ~900kB firmware OTA frissíthető legyen. Ez csak úgy megy, ha a firmware partíció 2MB méretű. Maximum olyat tudsz, hogy az firmware partíció aszimmetrikus, és van egy kisebb firmware, ami letölti a nagyobbat, de a legminimálisabb Arduino firmware kb. 300 kB lesz, ESP-IDF esetén ~130 kB, ezzel se fog 900 kB OTA felférni.

Futás közben nem lehet átkonfigolni 2MB/1MB beállításra?

Nem.

Ez így szopás. Ötlet?

https://hup.hu/comment/3225522#comment-3225522

Amúgy, figyelj, szerintem ülj le egy nyugodt sarokba és olvasd el az ESP8266 specifikációkat, meglepően jól leírnak mindent.

Ez a feltöltött cuccot 0x000000 címtól beírja. Ha ez lenne 0x100000 címtől, működhetne?

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ESP.h>

// WiFi adatok — állítsd be a saját hálózatodra
const char* ssid = "asajah";
const char* pass = "xxxxxxxx";

ESP8266WebServer server(80);

// Cél flash kezdőcím (byte-ban)
const uint32_t FLASH_WRITE_ADDR = 0x200000;
// Flash szektor méret (ESP8266: 4 KB)
const uint32_t SECTOR_SIZE = 4096;

uint32_t currentWriteAddr = FLASH_WRITE_ADDR;
bool *erasedSectors = nullptr;
uint32_t firstSector = 0;
uint32_t erasedSectorCount = 0;

// Ideiglenes puffer a 4-bájtos igazításhoz
#define TMPBUF_SIZE 4096
uint8_t tmpBuf[TMPBUF_SIZE];
uint32_t tmpBufLen = 0;

void ensureSectorErased(uint32_t addr) {
  uint32_t sector = addr / SECTOR_SIZE;
  if (sector < firstSector || sector >= firstSector + erasedSectorCount) return;
  uint32_t idx = sector - firstSector;
  if (!erasedSectors[idx]) {
    Serial.printf("Erase sector %u\n", sector);
    ESP.flashEraseSector((uint16_t)sector);
    erasedSectors[idx] = true;
  }
}

void writeFlashAligned(const uint8_t* data, size_t len) {
  size_t offset = 0;

  // Ha van a tmpBufban adat, előbb azt töltjük fel a teljes 4-bájtos blokkhoz
  if (tmpBufLen > 0) {
    size_t need = 4 - (tmpBufLen % 4);
    if (need > len) need = len;
    memcpy(tmpBuf + tmpBufLen, data, need);
    tmpBufLen += need;
    offset += need;

    if ((tmpBufLen % 4) == 0) {
      // írjuk ki tmpBuf-ot
      ensureSectorErased(currentWriteAddr);
      // ESP.flashWrite vár uint32_t* és méret szavakban (4 bájt)
      ESP.flashWrite(currentWriteAddr, (uint32_t*)tmpBuf, tmpBufLen / 4);
      currentWriteAddr += tmpBufLen;
      tmpBufLen = 0;
    }
  }

  // Most a maradék adatot írjuk szóközönként (4 bájt)
  size_t remain = len - offset;
  size_t fullWords = (remain / 4);
  if (fullWords > 0) {
    size_t byteCount = fullWords * 4;
    // Írás előtt gondoskodunk a szükséges szektor(oka)tól
    uint32_t writeAddr = currentWriteAddr;
    uint32_t endAddr = writeAddr + byteCount;
    // erase szektorok szükség szerint
    uint32_t s = writeAddr / SECTOR_SIZE;
    uint32_t e = (endAddr - 1) / SECTOR_SIZE;
    for (uint32_t sec = s; sec <= e; ++sec) {
      ensureSectorErased(sec * SECTOR_SIZE);
    }
    // végleges írás
    ESP.flashWrite(currentWriteAddr, (uint32_t*)(data + offset), fullWords);
    currentWriteAddr += byteCount;
    offset += byteCount;
  }

  // Végül a maradék (<4 bájt) adatot tmpBufba tesszük
  size_t left = len - offset;
  if (left > 0) {
    memcpy(tmpBuf + tmpBufLen, data + offset, left);
    tmpBufLen += left;
  }
}

void handleUpload() {
  HTTPUpload& upload = server.upload();

  if (upload.status == UPLOAD_FILE_START) {
    Serial.printf("Upload start: %s, size: %u\n", upload.filename.c_str(), (uint)upload.totalSize);

    // Ellenőrizzük a flash méretét
    uint32_t flashSize = ESP.getFlashChipRealSize();
    if (FLASH_WRITE_ADDR >= flashSize) {
      Serial.println("ERROR: write address outside flash!");
      // megszakítás (válasz)
      server.send(500, "text/plain", "Write address outside flash!");
      return;
    }

    // lefoglaljuk a szektor erase állapot tömböt (feltételezzük az upload.totalSize elérhető)
    uint32_t needed = upload.totalSize ? upload.totalSize : (flashSize - FLASH_WRITE_ADDR);
    uint32_t startSec = FLASH_WRITE_ADDR / SECTOR_SIZE;
    uint32_t sectorCount = (needed + SECTOR_SIZE - 1) / SECTOR_SIZE;
    // cap ellenőrzés
    if (FLASH_WRITE_ADDR + needed > flashSize) {
      Serial.println("ERROR: Not enough flash space for file");
      server.send(500, "text/plain", "Not enough flash space for file");
      return;
    }
    firstSector = startSec;
    erasedSectorCount = sectorCount;
    // felszabadítás előző esetben
    if (erasedSectors) free(erasedSectors);
    erasedSectors = (bool*)malloc(sectorCount);
    memset(erasedSectors, 0, sectorCount);

    currentWriteAddr = FLASH_WRITE_ADDR;
    tmpBufLen = 0;

    Serial.printf("Will write to 0x%06X, sectors %u..%u\n", FLASH_WRITE_ADDR, (unsigned)startSec, (unsigned)(startSec + sectorCount - 1));
  } 
  else if (upload.status == UPLOAD_FILE_WRITE) {
    // upload.buf és upload.currentSize áll rendelkezésre
    writeFlashAligned((const uint8_t*)upload.buf, upload.currentSize);
    Serial.printf("Wrote chunk %u bytes, total written so far ~%u\n", (unsigned)upload.currentSize, (unsigned)(currentWriteAddr - FLASH_WRITE_ADDR));
  } 
  else if (upload.status == UPLOAD_FILE_END) {
    // maradék padding: padoljunk 0xFF-el a 4-byte határig (flash erased állapot 0xFF)
    if (tmpBufLen > 0) {
      // pad 0xFF
      while ((tmpBufLen % 4) != 0 && tmpBufLen < TMPBUF_SIZE) {
        tmpBuf[tmpBufLen++] = 0xFF;
      }
      if (tmpBufLen % 4 == 0) {
        ensureSectorErased(currentWriteAddr);
        ESP.flashWrite(currentWriteAddr, (uint32_t*)tmpBuf, tmpBufLen / 4);
        currentWriteAddr += tmpBufLen;
      }
      tmpBufLen = 0;
    }

    Serial.printf("Upload END: %s, total written: %u bytes\n", upload.filename.c_str(), (unsigned)(currentWriteAddr - FLASH_WRITE_ADDR));
    server.send(200, "text/plain", "Upload successful");
    // felszabadítjuk az erase tömböt
    if (erasedSectors) { free(erasedSectors); erasedSectors = nullptr; }
  }
}

void setup() {
  Serial.begin(57600);
  delay(100);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, pass);
  Serial.printf("Connecting to WiFi '%s' ...\n", ssid);
  while (WiFi.status() != WL_CONNECTED) {
    delay(200);
    Serial.print('.');
  }
  Serial.println();
  Serial.print("IP: ");
  Serial.println(WiFi.localIP());

  server.on("/", HTTP_GET, [](){
    String html = "<html><body><form method='POST' action='/upload' enctype='multipart/form-data'>"
                  "<input type='file' name='file'>"
                  "<input type='submit' value='Upload'>"
                  "</form></body></html>";
    server.send(200, "text/html", html);
  });

  // feltöltés végpont: két callback verziója (on() harmadik paraméterként a befejező callback)
  server.on("/upload", HTTP_POST, [](){
    // normál befejező válasz csak itt, de az upload callback küldi vissza a végső státuszt.
    // Üres. A valós választ az upload státuszban küldjük.
  }, handleUpload);

  server.begin();
  Serial.println("HTTP server started");
}

void loop() {
  server.handleClient();
}

Már értem. Kevered a FLASH-t meg az EEPROM-ot.

Nem, nem keverem, ahogy idéztem is a specifikációból: "Flash memory is a block-erase EEPROM optimized for high density and speed." 

Te kevered:

Nincs is eeprom. Az a flash végén egy kis rész. Nem értem ez hogy jön ide.

Az a "flash végén egy kis rész", az nem EEPROM, mert a Flash egésze egy EEPROM, a "flash végén egy kis rész" az egy szimpla "Unused" memory block az EEPROM-ban, egy 4096 bájt hosszú blokk, amit te valamiért a saját gépeden úgy hívsz, hogy `eeprom_full.bin`. De hívhatod azt bárhogy, lehetne a neve `patyomkin.bin` is, ugyanúgy működne a programod, az ESP8266 ugyanis nem tudja, hogy mi a fájl neve, amiből felmásolsz 4096 bájtot.

a: 24C16

Ilyen nincs az ESP architektúrában használva.

b: EEPROM.begin(512); 

Ez meg az Arduino AVR platformon lévő 4kB méretű EEPROM szoftveres emulációja ESP Arduino környezetben, ezt, ha akarod (EEPROM_start), akkor bárhova be tudod címezni az ESP külső EEPROM memóriában... ez nem egy fizikai külön EEPROM, hanem egy szoftveresen emulált EEPROM, ami pont ugyanúgy viselkedik, mint egy AVR alapú Arduino esetén a fizikai EEPROM.

Hagyjuk, értelek.

Kételkedek benne kissé.

28C64 megvan? Az volt az igazi EEPROM. Gyerek voltam, abból csináltam kamu matávos telefonlártyát. Az eredeti rajzban 27xx eprom volt. Faszom törölgesse uvlámpával, beficcent helyette. Loptam a rajzot, valami villamos volt, egy egyetemi lap. Emlékszik még rá valaki?

Én nem szenvednék teljes firmware OTA frissítésésel, inkább olyat használnék, ahol tudod változtatni a filerendszeren a kódot. Pl. NodeMCU.

A tudomány és a hit vitája akkor eldőlt, amikor villámhárítót szereltek a templomokra.

Sokkal kevesebbet (és lassabban) tudsz futtatni, ha interpretált kóddal futsz neki, lényegesen jobb lehetőségeid vannak Arduino alapokon és ESP-IDF esetén meg teljesen kinyílik a világ. De ez az OTA lehetőséget nem érinti, azt a chip saját firmware kódja végzi és kötött a működése.

Tisztában vagyok vele, hogy egy interpreter nyelv lassabb, mint a natív fordított. De rengeteg olyan alkalmazás van, ahol ez az interpreter sebesség is bőven elég, nekem pl. eddig megfelelt mindenre: Villanyóra P1 port, szenzorok olvasása, ventilátorok vezérlése, mindezek ESP-01-el, ami a leggyengébb ESP8266 modul.

A tudomány és a hit vitája akkor eldőlt, amikor villámhárítót szereltek a templomokra.

Én kukaznam az esp8266 vonalat egy az egyben. Csak szenvedés.

Saying a programming language is good because it works on all platforms is like saying anal sex is good because it works on all genders....

Azért ehhez vedd figyelembe, hogy mennyi az órajel... Lehet, hogy ARM-on akár 16 órajel ez, de maga az MCU jelentősen nagyobb frekvencián működik. Plusz C programozásra van tervezve, szóval nem kell assembly-ben szenvedni. Ha ilyen gyors reagálás kell akkor lehet érdemes elgondolkodni egy FPGA-n.

Ha ilyen gyors reagálás kell akkor lehet érdemes elgondolkodni egy FPGA-n.

Bizonyos esetekben kénytelen vagyok ezt csinálni, jellemzően az Infineon PSOC család segítségével.

Nekem sem szívem csücske a PIC, de mikor átálltam ARM-re (kb. 10 éve) ez okozta a legnagyobb gondot.

„Az összeomlás elkerülhetetlen, a katasztrófa valószínű, a kihalás lehetséges.” (Jem Bendell)

Hja, van belőle olyan board, aminél a Wifi/BL/BLE chip (többnyire Infineon CYW43439) rá van drótozva pár GPIO-ra, de annyira nem szopó használni. Az ESP32 esetén viszont sokkal jobb az integráció, mert nincs külön chip erre, benne van minden az ESP-ben.

ami azt is jelenti (hogy kulon chip), hogy nehezebben fagysz ki.

En ezt elonynek latom.

 

(bonusz: mind az ethernet, mind a wifi egy liga. Nem pedig ilyen nyakatekert, mint esp-nel, ha ethernetezni szeretnél).

Saying a programming language is good because it works on all platforms is like saying anal sex is good because it works on all genders....

ami azt is jelenti (hogy kulon chip), hogy nehezebben fagysz ki.

Miért lenne bármilyen fagyás? Egy dolog okoz ilyet, ha a táp nem képes annyi delejt adni, amennyi kell a Wifi kommunikációhoz, akkor könnyen brownout állapot jön, de ugyanez a helyzet bármilyen Wifi board esetén.

(bonusz: mind az ethernet, mind a wifi egy liga. Nem pedig ilyen nyakatekert, mint esp-nel, ha ethernetezni szeretnél).

Alapból az se tud Ethernet, amit írtál, ahhoz is külön hardver kell, az meg megy ESP-vel is, nem tudom mi a nyakatekert benne...

és y2k38/y21k06 safe esp8266 sdk?  :)

Igen. Leginkább azért, mert egyáltalán nincs benne dátumkezelés, külön library kell hozzá, hogy kezelhess dátumot, anélkül csak az utolsó boot óta eltelt időt tudod lekérdezni.

Ellenben az Arduino SDK kapcsán van benne egy ún. 49 napos bug, mert a millis() függvény a boot óta eltelt ezredmásodperceket méri, annak 49,71 nap után van egy túlcsordulása és nulláról indul, tehát érdemes erre felkészülni, ha ezt akarod használni valami fontos ütemezési dologra... :)

Eddig jutottam:

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ESP.h>
#include <user_interface.h>

// Fix: Konfliktus elkerülésére manuálisan definiáljuk a sikeres visszatérési értéket
#define SPI_FLASH_RESULT_OK 0

// --- KÖZÖS DEFINÍCIÓK ÉS BEÁLLÍTÁSOK ---
#define STAGER_SRAM_BUF   0x3ffe8000 // SRAM puffer

// WiFi adatok — ÁLLÍTSA BE A SAJÁT ADATAIRA! 🔑
const char* ssid = "asajah";
const char* pass = "xxxxxxxx";

// A feltöltött bináris adatai
const uint32_t FLASH_WRITE_ADDR = 0x200000;
const uint32_t SECTOR_SIZE = 4096;
const uint32_t COPY_SIZE = 865488U; // EspOS_v508_4MB_Lite.bin mérete

// Globális változók a feltöltéshez
ESP8266WebServer server(80);
uint32_t currentWriteAddr = FLASH_WRITE_ADDR;
bool *erasedSectors = nullptr;
uint32_t firstSector = 0;
uint32_t erasedSectorCount = 0;
#define TMPBUF_SIZE 4096
uint8_t tmpBuf[TMPBUF_SIZE];
uint32_t tmpBufLen = 0;

// --- FÜGGVÉNY PROTOTÍPUSOK ---
void handleUpload();
void uploadBootloaderFromArray();
void ensureSectorErased(uint32_t addr);
void writeFlashAligned(const uint8_t* data, size_t len);

// --- ROM/ALACSONY SZINTŰ DEKLARÁCIÓK ---
extern "C" {
  // Megszakításkezelés
  extern void ets_intr_lock(void);
  extern void ets_intr_unlock(void);
  
  // Flash I/O rutinok (int visszatérési típussal, ahogy az IRAM másolónknak kell)
  extern int spi_flash_read(uint32_t src_addr, void *dest, uint32_t size);
  extern int spi_flash_write(uint32_t dest_addr, const void *src, uint32_t size);
  extern int spi_flash_erase_sector(uint32_t sec);
}


// =================================================================
// KRITIKUS IRAM FUNKCIÓ: A TISZTA ROM MÁSOLÓ
// =================================================================
// Ez a függvény az IRAM-ban fut, és a ROM I/O hívásokkal másolja a firmware-t.
void __attribute__((section(".iram.text"))) copyAndHalt() __attribute__((noreturn));

void __attribute__((section(".iram.text"))) copyAndHalt() {
    
    const uint32_t COPY_SRC = FLASH_WRITE_ADDR; 
    const uint32_t COPY_DST = 0x000000U;        
    
    uint8_t *buf = (uint8_t*)STAGER_SRAM_BUF;
    
    uint32_t remaining = COPY_SIZE;
    uint32_t src = COPY_SRC;
    uint32_t dst = COPY_DST;
    
    ets_intr_lock();
    
    while (remaining > 0) {
        uint32_t this_chunk = (remaining >= SECTOR_SIZE) ? SECTOR_SIZE : remaining;
        uint32_t sector_to_erase = (dst) / SECTOR_SIZE;
        
        if (spi_flash_read(src, buf, this_chunk) != SPI_FLASH_RESULT_OK) {
            ets_intr_unlock();
            while (1); 
        }
        
        if (spi_flash_erase_sector(sector_to_erase) != SPI_FLASH_RESULT_OK) {
            ets_intr_unlock();
            while (1); 
        }
        
        if (spi_flash_write(dst, buf, this_chunk) != SPI_FLASH_RESULT_OK) {
            ets_intr_unlock();
            while (1); 
        }
        
        src += this_chunk;
        dst += this_chunk;
        remaining -= this_chunk;
    }
    
    ets_intr_unlock();
    
    while(1); 
}
// ------------------------------------------

// =================================================================
// WEBSERVER ÉS MÁSOLÁS INDÍTÁSA
// =================================================================
void uploadBootloaderFromArray() {
    Serial.println("RAW STAGER Write OK. Starting IRAM copy of uploaded firmware...");

    // Flash cache kikapcsolása a másolás előtt
    WiFi.disconnect(true);
    WiFi.mode(WIFI_OFF);
    delay(50);
    noInterrupts();
    wdt_disable();
    
    copyAndHalt();
    
    while(1);
}

// =================================================================
// FLASH ÍRÁS SEGÉDFÜGGVÉNYEK
// =================================================================
void ensureSectorErased(uint32_t addr) {
    uint32_t sector = addr / SECTOR_SIZE;
    if (sector < firstSector || sector >= firstSector + erasedSectorCount) return;
    uint32_t idx = sector - firstSector;
    if (!erasedSectors[idx]) {
        Serial.printf("Erase sector %u (ESP.flashEraseSector)\n", sector);
        wdt_disable(); 
        ESP.flashEraseSector((uint16_t)sector); 
        wdt_enable(WDTO_8S); 
        erasedSectors[idx] = true;
    }
}

void writeFlashAligned(const uint8_t* data, size_t len) {
    size_t offset = 0;

    // 1. Puffer feltöltése / Igazítás (tmpBuf)
    if (tmpBufLen > 0) {
        size_t copyLen = min(len, (size_t)TMPBUF_SIZE - tmpBufLen);
        memcpy(tmpBuf + tmpBufLen, data + offset, copyLen);
        tmpBufLen += copyLen;
        offset += copyLen;
        
        if (tmpBufLen == TMPBUF_SIZE) { // Ha a puffer megtelt (4-gyel osztható)
            ensureSectorErased(currentWriteAddr);
            noInterrupts(); wdt_disable();
            ESP.flashWrite(currentWriteAddr, (uint32_t*)tmpBuf, TMPBUF_SIZE / 4);
            wdt_enable(WDTO_8S); interrupts();
            currentWriteAddr += TMPBUF_SIZE;
            tmpBufLen = 0;
        }
    }

    // 2. Igazított darabok írása
    size_t remain = len - offset;
    size_t fullWords = (remain / 4);
    if (fullWords > 0) {
        size_t byteCount = fullWords * 4;
        uint32_t endAddr = currentWriteAddr + byteCount;
        uint32_t s = currentWriteAddr / SECTOR_SIZE;
        uint32_t e = (endAddr - 1) / SECTOR_SIZE;
        for (uint32_t sec = s; sec <= e; ++sec) {
            ensureSectorErased(sec * SECTOR_SIZE);
        }
        noInterrupts();
        wdt_disable();
        ESP.flashWrite(currentWriteAddr, (uint32_t*)(data + offset), fullWords);
        wdt_enable(WDTO_8S);
        interrupts();
        currentWriteAddr += byteCount;
        offset += byteCount;
    }

    // 3. Maradék mentése
    size_t left = len - offset;
    if (left > 0) {
        memcpy(tmpBuf + tmpBufLen, data + offset, left);
        tmpBufLen += left;
    }
}


void handleUpload() {
  HTTPUpload& upload = server.upload();

  if (upload.status == UPLOAD_FILE_START) {
    if (erasedSectors) { free(erasedSectors); erasedSectors = nullptr; }
    uint32_t sectorCount = (upload.totalSize + SECTOR_SIZE - 1) / SECTOR_SIZE;
    erasedSectors = (bool*)malloc(sectorCount);
    memset(erasedSectors, 0, sectorCount);
    firstSector = FLASH_WRITE_ADDR / SECTOR_SIZE;
    erasedSectorCount = sectorCount;
    currentWriteAddr = FLASH_WRITE_ADDR;
    tmpBufLen = 0;
    Serial.printf("Upload start: %s, size: %u\n", upload.filename.c_str(), (unsigned)upload.totalSize);
  } 
  else if (upload.status == UPLOAD_FILE_WRITE) {
    writeFlashAligned((const uint8_t*)upload.buf, upload.currentSize);
    Serial.printf("Wrote chunk %u bytes, total written so far ~%u\n", (unsigned)upload.currentSize, (unsigned)(currentWriteAddr - FLASH_WRITE_ADDR));
  } 
  else if (upload.status == UPLOAD_FILE_END) {
    if (tmpBufLen > 0) {
      // Puffer maradék kiírása, zárás 4-gyel osztható méretre
      while ((tmpBufLen % 4) != 0 && tmpBufLen < TMPBUF_SIZE) { tmpBuf[tmpBufLen++] = 0xFF; }
      if (tmpBufLen % 4 == 0) {
        ensureSectorErased(currentWriteAddr);
        noInterrupts(); wdt_disable();
        ESP.flashWrite(currentWriteAddr, (uint32_t*)tmpBuf, tmpBufLen / 4);
        wdt_enable(WDTO_8S); interrupts();
        currentWriteAddr += tmpBufLen;
      }
      tmpBufLen = 0;
    }

    Serial.printf("Upload END: %s, total written: %u bytes\n", upload.filename.c_str(), (unsigned)(currentWriteAddr - FLASH_WRITE_ADDR));
    server.send(200, "text/plain", "Upload successful (Initiating copy...)");
    if (erasedSectors) { free(erasedSectors); erasedSectors = nullptr; }

    uploadBootloaderFromArray(); // INDÍTÁS!
  }
}

// =================================================================
// SETUP ÉS LOOP
// =================================================================
void setup() {
    Serial.begin(74880);
    delay(100);
    
    // FIGYELEM: A Flash Mode (DOUT) beállítását az Arduino IDE Eszközök menüjében kell hagyni!
    // A rom_set_flash_mode hívás nélkül ez a kód már lefordul.
    
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, pass);
    Serial.printf("Connecting to WiFi '%s' ...\n", ssid);
    while (WiFi.status() != WL_CONNECTED) {
        delay(200);
        Serial.print('.');
        yield();
    }
    Serial.println();
    Serial.print("IP: ");
    Serial.println(WiFi.localIP());

    server.on("/", HTTP_GET, [](){
        String html = "<html><body><form method='POST' action='/upload' enctype='multipart/form-data'>"
                      "<input type='file' name='file'>"
                      "<input type='submit' value='Upload'>"
                      "</form></body></html>";
        server.send(200, "text/html", html);
    });

    server.on("/upload", HTTP_POST, [](){
        // Ez a függvény a feltöltés állapotának kezeléséhez kell (handleUpload hívja)
    }, handleUpload); // Most már deklarálva van!

    server.begin();
    Serial.println("HTTP server started");
}

void loop() {
    server.handleClient();
    yield();
}

Eredmény: 

11:20:05.448 -> Upload END: EspOS_v508_4MB_Lite.bin, total written: 865488 bytes
11:20:05.448 -> RAW STAGER Write OK. Starting IRAM copy of uploaded firmware...
11:20:16.790 -> "@1Q|⸮!⸮@⸮КvX%⸮~⸮h⸮sI    ⸮8t/⸮⸮⸮*1T⸮1    ⸮⸮⸮QQ@⸮y*1⸮"⸮nZAl ⸮^1⸮!⸮q⸮tt⸮⸮,⸮D     5⸮*1⸮⸮
11:20:32.746 ->  ets Jan  8 2013,rst cause:2, boot mode:(3,7)
11:20:32.746 -> 
11:20:32.746 -> load 0x4010f000, len 3424, room 16 
11:20:32.792 -> tail 0
11:20:32.792 -> chksum 0xbe
11:20:32.792 -> load 0xffffffff, len -1, room 8 

Volt egy másik ötlet is hogy írok egy saját bootloadert, ami 0x20000-0x2FFFFF tartományt átmásolja 0x000000-0x0FFFFF területre, de az se sikerült.