Arduino Web serveris ir File Upload per AJAX

23 09 2013

Programuodamas Arduino Mega Web serverį, kurio turinys yra SD kortelėje, susiduriame su viena nemaža problema susijusi su pastoviu HTML bei kitų failų, esančių kortelėje, atnaujinimų. Pastoviai kortelę ištraukti bei įdėti gana nepatogu, tačiau būtina atlikus kiekvieną kodo atnaujinimą. Kiek padirbėjęs su paieškos sistema radau užuominas, kad TinyWebServer biblioteka puikiai suderinti šie dalykai. Pirmiausiai tai puikiame pavyzdyje jau yra scriptas bei visa struktūra kaip viskas daroma, kuomet norime iš PC pasinaudojant CURL.EXE programa failus siųsti į SD kortelę. Failai bus sukuriami, o jei tokie jau yra perrašomi. To man ir reikia. Kaip atrodo HTML atnaujinimas galite pamatyti trumpame filmuke:

Viskas labai nesudėtinga. Curl programa nusiskaito failus iš eilės ir juos susiunčiu generuodama antraštes  su pakatalogiu “upload“ pvz: http://192.168.100.177/upload/config.html. Pakatalogis mums reikalingas ne tam kad žinoti kur saugosime failą, o tik tam kad Arduino žinotu kad failą reikia įkelti į SD kortelę, o ne atvirkščiai. 

Dabar darbus tęsiame toliau. Aš noriu šiuos failus į SD kortelę įskelti ne per PC aplikaciją, o per WEB naršyklę. Šiuo atveju WEB puslapis saugomas SD kortelėje ir jį interpretuoti gali tik du elementai: Arduino ir WEB naršyklę. Pirmu atveju reikalas sudėtingas nes visoms interpretacijoms Arduino nėra tinkamas (apie tokius dalykus kaip PHP reiktu nė nedrįsti galvoti). Antras variantas jau kiek realesnis, nes WEB naršyklė puikiai moka vykdyti Javascript užklausas. Prie Javascript dar pridėjus AJAX duomenų apsikeitimą turime kažką panašaus ko man reikia. Taigi išbandęs nemažai variantų radau tinkama būdą siųsti failus į Arduino per AJAX.

HTML failo įkėlimo elementas

Pirmas dalykas ko mums reikia tai HTML dokumente turėti mygtuką failams pasirinkti bei įkėlimo procesui pradėti. Taigi tam yra toks dalykas:

<input type=“file“ id=“file“ name=“file“ />

Dar prie jo pridėsime mygtuką kuris inicijuos duomenų siuntimą:

<input type=’button’ id=’upload’ value=“Upload“ onclick=“uploadFile()“/>

Failo įkėlimu rūpinsis Javascript funkcija uploadFile().

AJAX failo įkėlimas

Kadangi naujesnės naršyklės palaiko xhr2 tai visas įkėlimo procesas įmanomas naudojant tik AJAX. Kiek pavargus pagaminta tokia funkcija įkėlimui įvykdyti:

function uploadFile()
{
var xhr = new XMLHttpRequest();
if (file = document.getElementById(‘file’).files[0]
{
xhr = new XMLHttpRequest();
xhr.file = file; // not necessary if you create scopes like this
xhr.addEventListener(‘progress’, function(e) {
var done = e.position || e.loaded, total = e.totalSize || e.total;
console.log(‘xhr progress: ‘ + (Math.floor(done/total*1000)/10) + ‘%’);
}, false);
if ( xhr.upload ) {
xhr.upload.onprogress = function(e) {
var done = e.position || e.loaded, total = e.totalSize || e.total;
console.log(‘xhr.upload progress: ‘ + done + ‘ / ‘ + total + ‘ = ‘ + (Math.floor(done/total*1000)/10) + ‘%’);
};
}
xhr.onreadystatechange = function(e) {
if ( 4 == this.readyState ) {
console.log([‘xhr upload complete’, e]);
}
};
xhr.open(‘put’, “/upload/“ + file.name, true);
xhr.send(file);
}
}

Pati funkcija praktiškai nedaro nieko kito kaip tiesiog pasiima failo pavadinimą iš HTML elemento “file“ ir jo antraštę bei turinį siunčia per xhr užklausą. Vėliau tai atlikus funkcijos sukurti įvykiai stebi kada bus baigtas siuntimas ir eigoje į konsolės srautą siunčia progreso informaciją. Proceso stebėjimą galime nesunkiai perkelti į HTML bet mes dabar ne apie tai.  Svarbiausiai mums dabar yra tai kad AJAX siunčia failą, o Arduino tinkamai jį atpažįsta ir įrašo į SD kortelę.

Daug failų

HTML atnaujinę failo pasirinkimo komponentą galime leisti vartotojui pasirinkti kelis failus:

<input type=“file“ id=“file“ name=“file“ multiple />

Liko tik atnaujinti AJAX. Kodėl visko neatlikus pasinaudojant for ciklu:

function uploadFile()
{
var xhr = new XMLHttpRequest();
for (var i = 0; i<99; i++)
{
if (file = document.getElementById(‘file’).files[i])
{
xhr = new XMLHttpRequest();
xhr.file = file; // not necessary if you create scopes like this
xhr.addEventListener(‘progress’, function(e) {
var done = e.position || e.loaded, total = e.totalSize || e.total;
console.log(‘xhr progress: ‘ + (Math.floor(done/total*1000)/10) + ‘%’);
}, false);
if ( xhr.upload ) {
xhr.upload.onprogress = function(e) {
var done = e.position || e.loaded, total = e.totalSize || e.total;
console.log(‘xhr.upload progress: ‘ + done + ‘ / ‘ + total + ‘ = ‘ + (Math.floor(done/total*1000)/10) + ‘%’);
};
}
xhr.onreadystatechange = function(e) {
if ( 4 == this.readyState ) {
console.log([‘xhr upload complete’, e]);
}
};
xhr.open(‘put’, “/upload/“ + file.name, true);
xhr.send(file);
}
else break;
}
}

Ka mes čia pridarėme? Ogi mes tiesiog keliaujame per 99 failo pasirinkimo elemento masyvo vietas ir jei jame egzistuoja failas vykdome siuntimą. Procedūra baigiama kai failo pasirinkimo elementas nebeturi failo. Dabar žiūrime kaip tai veikia:

Blogai. Toks siuntimo būdas veikia, bet jis netobulas dėl kelių priežasčių: for ciklas susiunčia visus failus vienu metu ir toliau xhr juos fone siunčia o tai darydamas atnaujina proceso informaciją. Tai negerai nes Arduino taip greitai kelių failų gali nesugraibyti ir atsiranda poreikis siuntinėti failus po vieną. Taigi randame kita sprendimą:

function uploadFile(i)
{
var xhr = new XMLHttpRequest();
if (file = document.getElementById(‘file’).files[i])
{
xhr = new XMLHttpRequest();
xhr.file = file; // not necessary if you create scopes like this
xhr.addEventListener(‘progress’, function(e) {
var done = e.position || e.loaded, total = e.totalSize || e.total;
console.log(‘xhr progress: ‘ + (Math.floor(done/total*1000)/10) + ‘%’);
}, false);
if ( xhr.upload ) {
xhr.upload.onprogress = function(e) {
var done = e.position || e.loaded, total = e.totalSize || e.total;
console.log(‘xhr.upload progress: ‘ + done + ‘ / ‘ + total + ‘ = ‘ + (Math.floor(done/total*1000)/10) + ‘%’);
};
}
xhr.onreadystatechange = function(e) {
if ( 4 == this.readyState ) {
console.log([‘xhr upload complete’, e]);
uploadFile(i+1);
}
};
xhr.open(‘put’, “/upload/“ + file.name, true);
xhr.send(file);
}
}

Čia mes turime panaudotą rekursiją. Įkėlimo funkcija gauna failo indeksą kurį reikia įkelti ir jį siunčia. Kuomet įkėlimas baigtas iškviečiama ta pati įkėlimo funkcija tik jai duodame sekanti failo numerį. Dar nepamirštame upload mygtuke atnaujinti funkcijos iškvietimą į “onclick=“uploadFile(0)““ nes taip pirmoji funkcija turės ką veikti. Kaip tai veikia pavaizdavau trumpame filmuke:

Kaip matome konsolėje niekas niekur neskuba ir failai į Arduino keliauja po viena eilute. Ačiu Rekursija. Tiek tam kartui.

P.S. failų įkėlimo greitis tragiškas 😀


Veiksmai

Information

2 responses

24 09 2013
Darau, Blė

Nu, jeigu dar būtų galima tuos failus įkėlinėti į AVR flashą, tai būtų iš viso jėga. O tamsta nesi bandęs padaryti firmwaro atnaujinimo per Ethernet? Man toks bajeris šviečiasi…

24 09 2013
mindogas

Nebandęs bet esu domėjesis. Problema tame kad tokiu atveju reikia kad Arduino turėtu perrašyta TFTP bootloaderi, o to aš nenoriu. O viska atlikti iš įkelto į SD failo deja neimanoma (mano žiniomis).

Parašykite komentarą

Įveskite savo duomenis žemiau arba prisijunkite per socialinį tinklą:

WordPress.com Logo

Jūs komentuojate naudodamiesi savo WordPress.com paskyra. Atsijungti / Keisti )

Twitter picture

Jūs komentuojate naudodamiesi savo Twitter paskyra. Atsijungti / Keisti )

Facebook photo

Jūs komentuojate naudodamiesi savo Facebook paskyra. Atsijungti / Keisti )

Google+ photo

Jūs komentuojate naudodamiesi savo Google+ paskyra. Atsijungti / Keisti )

Connecting to %s




%d bloggers like this: