EspOS ESP8266 után ESP32 firmware

Egy egyszerű, HTML JS alapismeretekkel használható IoT firmware készítésén dolgozok esp8266 mikrokontrollerre. Aki kipróbálta, használja, ide várom a visszajelzéseket.

2026.01.12. Vége. Jön az ESP32

https://www.esp8266.org

Hozzászólások

Update v522

www.esp8266.org

Már nincs kedvem hozzá, de megcsináltam.

 

Microsoft Windows [Version 10.0.22631.5335]
(c) Microsoft Corporation. Minden jog fenntartva.

f:\EspOS>cloc ./
    525 text files.
    486 unique files.
    311 files ignored.

github.com/AlDanial/cloc v 2.04  T=17.98 s (27.0 files/s, 6255.5 lines/s)
-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
C/C++ Header                   285           2314            529          56598
HTML                           125           2687             41          29517
Arduino Sketch                  32           1100            198           9880
JavaScript                      13            988            323           7006
DOS Batch                        5            161             10            481
Text                            16             37              0            320
Markdown                         1             39              0            110
JSON                             3              0              0             49
PHP                              3              2              0             39
Clojure                          3              2              0             25
-------------------------------------------------------------------------------
SUM:                           486           7330           1101         104025
-------------------------------------------------------------------------------

f:\EspOS>

σ→0, SNR<1 (A semleges részecskékkel nincs mérhető kölcsönhatás)

https://www.esp8266.org

ESP8266 lezárva.

Kezembe került egy ESP32C3 devboard.

Ezt is mint a WinFos telepítés után egy fájlkezelővel kezdem. Total Commander logikát követtem, még a duplaklikk is működik.

Két nap alatt idáig jutottam:

 

FileManager.ino

#include <Arduino.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <LittleFS.h>
#include <vector>
#include <algorithm>

const char* ssid="asajah";
const char* pass="xxxxxxxx";

bool systemFolder = true;
bool coreFolder   = true;

AsyncWebServer server(80);

/* ================= REKURZÍV TÖRLÉS ================= */
bool rm_rf(const String &path){
File f=LittleFS.open(path);
if(!f) return false;

if(!f.isDirectory()){
 f.close();
 return LittleFS.remove(path);
}

File c=f.openNextFile();
while(c){
 String p=String(c.name());
 if(c.isDirectory()) rm_rf(p);
 else LittleFS.remove(p);
 c=f.openNextFile();
}
f.close();
return LittleFS.rmdir(path);
}

/* ================= HTML ================= */
const char INDEX_HTML[] PROGMEM = R"rawliteral(
<!DOCTYPE html><html><head>
<meta charset="utf-8">
<title>ESP32 File Manager</title>
<style>
body{font-family:sans-serif}
.box{border:1px solid #aaa;padding:10px;margin:10px;width:520px}
li{cursor:pointer;padding:2px;white-space:nowrap;}
li.sel{background:#def}
li.up{font-weight:bold}
.name{display:inline-block;min-width:18em}
.size-num{display:inline-block;width:8ch;text-align:right;font-family:monospace;}
.size-unit{display:inline-block;margin-left:0.5ch;font-family:monospace;color:#555;}
#drop{border:2px dashed #888;padding:20px;text-align:center}
#flash-status{height:20px;width:100%;border:1px solid #888;position:relative;background:#ccc;margin-bottom:5px}
#flash-bar{height:100%;width:0%;color:white;padding-right:4px;box-sizing:border-box;text-align:right}
button{margin:2px}
</style>
</head><body>

<div class="box">
<h3>Flash files</h3>
<div>Path: <span id="path">/</span></div>
<button onclick="mkdir()">➕ Create DIR</button>
<ul id="files"></ul>
<button id="open" disabled onclick="openFile()">Open</button>
<button id="dl" disabled onclick="download()">Download</button>
<button id="rm" disabled onclick="del()">Delete</button>
<button id="rn" disabled onclick="ren()">Rename</button>
<button id="edit" disabled onclick="editFile()">Edit</button>
</div>

<div class="box">
<h3>Upload</h3>
<div id="flash-status"><div id="flash-bar">0 kB / 0 kB</div></div>
<div id="drop">Drag & Drop files here</div><br>
<input type="file" id="f" multiple>
<button onclick="uploadBtn()">Upload</button>
</div>

<script>
let cwd="/", sel=null, selIsDir=false, selIsSystem=false;
let flashTimer=null;

function loadFlashStatus(){
fetch('/flashfree').then(r=>r.json()).then(j=>{
 let bar=document.getElementById('flash-bar');
 let percent=(j.used/j.total)*100;
 bar.style.background=percent<70?'green':percent<90?'orange':'red';
 bar.style.width=percent+'%';
 bar.textContent=j.used+' kB / '+j.total+' kB';
});
}

function btn(e,dir){
document.getElementById('dl').disabled = !e || dir;
document.getElementById('rm').disabled = !e || selIsSystem;
document.getElementById('rn').disabled = !e || selIsSystem;
document.getElementById('open').disabled = !e;
document.getElementById('edit').disabled = !e || dir;
}

function renderEntry(f,type){
let ul=document.getElementById('files');
let li=document.createElement('li');

let icon = f.dir ? '📁 ' : '📄 ';
if(type==="system") icon='⚙️ ';
if(type==="core")   icon='🔺 ';

let name=document.createElement('span');
name.className='name';
name.textContent=icon+f.name;
li.appendChild(name);

if(!f.dir){
 li.innerHTML+=
  '<span class="size-num">'+f.size+'</span>'+
  '<span class="size-unit">B</span>';
}

li.onclick=()=>{
 [...ul.children].forEach(x=>x.classList.remove('sel'));
 li.classList.add('sel');
 sel=cwd+(cwd.endsWith('/')?'':'/')+f.name;
 selIsDir=f.dir;
 selIsSystem=(cwd==="/" && f.dir && (f.name==="system"||f.name==="core"));
 btn(true,f.dir);
};

li.ondblclick=e=>{
 e.preventDefault();
 if(f.dir){ cwd=sel; load(); }
 else window.open(sel,'_blank');
};

ul.appendChild(li);
}

function load(){
document.getElementById('path').textContent=cwd;
loadFlashStatus();

fetch('/list',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},
body:'path='+encodeURIComponent(cwd)})
.then(r=>r.json()).then(j=>{
 let ul=document.getElementById('files');
 ul.innerHTML=''; sel=null; selIsSystem=false; btn(false,false);

 if(cwd!="/"){
  let li=document.createElement('li');
  li.textContent='📁 ..'; li.className='up';
  li.onclick=()=>{ cwd=cwd.substring(0,cwd.lastIndexOf('/'))||"/"; load(); };
  ul.appendChild(li);
 }

 let core=null, system=null, others=[];
 j.forEach(f=>{
  if(cwd==="/" && f.dir && f.name==="core") core=f;
  else if(cwd==="/" && f.dir && f.name==="system") system=f;
  else others.push(f);
 });

 if(core)   renderEntry(core,"core");
 if(system) renderEntry(system,"system");
 others.forEach(f=>renderEntry(f,"normal"));
});
}

function uploadFiles(files){
let i=0;
flashTimer=setInterval(loadFlashStatus,300);
(function next(){
 if(i>=files.length){
  clearInterval(flashTimer);
  loadFlashStatus();
  load();
  return;
 }
 upload(files[i++],next);
})();
}

function upload(file,done){
let x=new XMLHttpRequest();
x.onload=()=>{ if(done)done(); };
x.open('POST','/upload?path='+encodeURIComponent(cwd)+'&name='+encodeURIComponent(file.name));
x.send(file);
}

function uploadBtn(){ uploadFiles(document.getElementById('f').files); }

drop.ondragover=e=>e.preventDefault();
drop.ondrop=e=>{ e.preventDefault(); uploadFiles(e.dataTransfer.files); };

function mkdir(){
let n=prompt("Dir name:"); if(!n)return;
fetch('/mkdir',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},
body:'path='+encodeURIComponent(cwd+"/"+n)}).then(load);
}
function del(){
if(selIsSystem){ alert("protected"); return; }
if(!sel||!confirm("Delete?")) return;
fetch('/delete',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},
body:'path='+encodeURIComponent(sel)}).then(load);
}
function ren(){
if(!sel) return;
let base=sel.substring(0,sel.lastIndexOf('/'));
let n=prompt("New name:",sel.split('/').pop());
if(!n) return;
fetch('/rename',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},
body:'old='+encodeURIComponent(sel)+'&new='+encodeURIComponent(base+'/'+n)}).then(load);
}
function download(){ if(!sel||selIsDir) return; let a=document.createElement('a'); a.href=sel; a.download=''; a.click(); }
function openFile(){ if(!sel) return; if(selIsDir){ cwd=sel; load(); } else window.open(sel); }

function editFile(){
if(!sel||selIsDir) return;
let f=document.createElement('form');
f.method='POST'; f.action='/system/editor.html';
let i=document.createElement('input');
i.name='path'; i.value=sel;
f.appendChild(i); document.body.appendChild(f);
f.submit(); document.body.removeChild(f);
}

load();
</script>
</body></html>
)rawliteral";

/* ================= SETUP ================= */
void setup(){
Serial.begin(115200);
LittleFS.begin(true);

WiFi.mode(WIFI_STA);
WiFi.config(IPAddress(192,168,0,88),IPAddress(192,168,0,1),IPAddress(255,255,255,0));
WiFi.begin(ssid,pass);
while(WiFi.status()!=WL_CONNECTED){ delay(200); yield(); }

server.on("/",HTTP_GET,[](AsyncWebServerRequest*r){ r->send(200,"text/html",INDEX_HTML); });
server.serveStatic("/",LittleFS,"/");

struct Entry{String name;bool dir;size_t size;};

server.on("/list",HTTP_POST,[](AsyncWebServerRequest*r){
 String path="/";
 if(r->hasParam("path",true)) path=r->getParam("path",true)->value();
 File dir=LittleFS.open(path);
 if(!dir||!dir.isDirectory()){ r->send(200,"application/json","[]"); return; }

 std::vector<Entry> v;
 File x=dir.openNextFile();
 while(x){
  String n=String(x.name()).substring(String(x.name()).lastIndexOf('/')+1);
  v.push_back({n,x.isDirectory(),x.isDirectory()?0:x.size()});
  x=dir.openNextFile();
 }

 auto cmp=[](const Entry&a,const Entry&b){ String A=a.name,B=b.name; A.toLowerCase();B.toLowerCase(); return A<B; };
 std::sort(v.begin(),v.end(),cmp);

 String j="[";
 for(size_t i=0;i<v.size();i++){
  if(i)j+=",";
  j+="{\"name\":\""+v[i].name+"\",\"dir\":"+(v[i].dir?"true":"false")+",\"size\":"+String(v[i].size)+"}";
 }
 j+="]";
 r->send(200,"application/json",j);
});

server.on("/flashfree",HTTP_GET,[](AsyncWebServerRequest*r){
 r->send(200,"application/json",
  "{\"total\":"+String(LittleFS.totalBytes()/1024)+",\"used\":"+String(LittleFS.usedBytes()/1024)+"}");
});

server.on("/upload",HTTP_POST,
[](AsyncWebServerRequest*r){ r->send(200); },
NULL,
[](AsyncWebServerRequest*r,uint8_t*d,size_t l,size_t i,size_t t){
 static File file;
 if(i==0){
  if(file)file.close();
  if(!r->hasParam("path")||!r->hasParam("name")) return;
  String p=r->getParam("path")->value();
  if(!p.endsWith("/"))p+="/";
  file=LittleFS.open(p+r->getParam("name")->value(),"w");
 }
 if(file) file.write(d,l);
 if(i+l==t && file) file.close();
});

server.on("/mkdir",HTTP_POST,[](AsyncWebServerRequest*r){ r->send(LittleFS.mkdir(r->getParam("path",true)->value())?200:500); });
server.on("/delete",HTTP_POST,[](AsyncWebServerRequest*r){ r->send(rm_rf(r->getParam("path",true)->value())?200:500); });
server.on("/rename",HTTP_POST,[](AsyncWebServerRequest*r){ r->send(LittleFS.rename(r->getParam("old",true)->value(),r->getParam("new",true)->value())?200:500); });

server.on("/system/editor.html",HTTP_POST,[](AsyncWebServerRequest*r){
 String path="";
 if(r->hasParam("path",true)) path=r->getParam("path",true)->value();
 File f=LittleFS.open("/system/editor.html","r");
 if(!f){ r->send(404); return; }
 String h=f.readString(); f.close();
 h.replace("<!--INSERT_PATH_HERE-->",path);
 r->send(200,"text/html",h);
});

server.begin();
}

void loop(){}

 

/system/editor.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>File Editor</title>
<style>
body{font-family:sans-serif;margin:10px}
textarea{width:100%;height:70vh;font-family:monospace;font-size:14px;box-sizing:border-box;}
button{margin-top:5px;padding:5px 10px;font-size:14px;}
#status{margin-top:5px;color:green;}
</style>
</head>
<body>

<h3>File Editor</h3>
<div>Path: <span id="filepath"></span></div>
<textarea id="editor"></textarea><br>
<button id="saveBtn" disabled>Save</button>
<div id="status"></div>

<script>
// POST esetén a szerver ideágyazza a path-ot
let path = "<!--INSERT_PATH_HERE-->";

// Ha GET, akkor query stringből vesszük
if(!path){
   const params = new URLSearchParams(window.location.search);
   path = params.get('path') || '';
}

const editor = document.getElementById('editor');
const saveBtn = document.getElementById('saveBtn');
const status = document.getElementById('status');
let original = '';

if(!path){
   editor.value = 'No file specified';
   saveBtn.disabled = true;
}else{
   document.getElementById('filepath').textContent = path;
   fetch(path).then(r=>r.text()).then(t=>{
       editor.value = t;
       original = t;
       saveBtn.disabled = false;
   }).catch(e=>{
       editor.value='Failed to load file';
       saveBtn.disabled = true;
   });
}

saveBtn.onclick = ()=>{
   if(editor.value === original){
       status.textContent = 'No changes to save.';
       return;
   }

   fetch('/upload?path=' + encodeURIComponent(path.substring(0,path.lastIndexOf('/')+1)) +
         '&name=' + encodeURIComponent(path.split('/').pop()), {
       method:'POST',
       body: new Blob([editor.value])
   }).then(r=>{
       if(r.ok){
           status.textContent = 'File saved successfully.';
           original = editor.value;
       }else{
           status.textContent = 'Failed to save file.';
           status.style.color='red';
       }
   }).catch(e=>{
       status.textContent='Error saving file';
       status.style.color='red';
   });
};
</script>

</body>
</html>

 

EspOS hibáiból tanulva innen folyt köv. Először egy HTTP/HTTPS webszerver lesz, aztán meg majd valami.

Akinek van kedve meg valami ESP32 kütyüje próbálja ki, várom a visszajelzéseket.

AsyncWebServer új verzió kell hozzá,

σ→0, SNR<1 (A semleges részecskékkel nincs mérhető kölcsönhatás)

https://www.esp8266.org