ESP8266 Web server touch
Introduzione
Questa pagina descrive il un generico server web su ESP8266 che può gestire eventi touch provenienti da untelefono o un tablet.
E' stata appositamente creata per il robot Ruschino WiFi, ma ha una più ampia possibilità di utilizzo.
Questo progetto ha subito una importante evoluzione con la possibilità di ricevere dati di feedback da arduino sul display di comando per i dettagli fare riferimento a ESP8266_Web_server_con_telemetria
ESP8266 Web server con gestione degli eventi touch
Sul modulo wifi ho aggiornato il firmware installando Nodemcu (come riportato in ESP8266#Upload_di_un_nuovo_firmware).
Questo firmware permette la pogrammazione dei moduli in linguaggio lua e ha già molte librerie a disposzione.
Questo è il codice del file web_serv.lua che crea e gestire le richieste http.
Seguono il codice della pagina index.htm e init.lua che lancia il server poco dopo il boot del modulo.
Dopo aver iniziato a scrivere index.htm mi sono accorto che una pagina più lunga di 512 byte non viene inviata al client a causa di una limitazione del firmware.
Per ovviare al problema ho adottato il codice proposto da Lefteris Stamataros e proposto in un video (vedi riferimenti).
Il problema si è risolto in modo efficace, ho solo riadattato il tutto per i miei scopi.
Si inizia configurando il modulo come access point, gli ultimi caratteri sono ricavati dal macaddress questo ci predispone per costruire più robot e riuscire a distinguerne il segnale radio.
webserv.lua
In questa prima parte configuro il modulo come access point:
local str=wifi.ap.getmac();
local ssidTemp=string.format("%s%s%s",string.sub(str,10,11),string.sub(str,13,14),string.sub(str,16,17));
cfg={};
cfg.ssid="192.168.4.1_Ruschino-"..ssidTemp;
wifi.ap.config(cfg);
cfg.ip="192.168.4.1";
cfg.netmask="255.255.255.0";
cfg.gateway="192.168.4.1";
wifi.ap.setip(cfg);
wifi.setmode(wifi.SOFTAP)
str=nil;
ssidTemp=nil;
Invio ad arduino una stringa per essere sicuro che le ruote siano ferme. Stampo la stringa sulla porta seriale a cui è collegato Arduino:
collectgarbage();
print("R=0&l=0&D=0&d=0&E=0&e=0")
<source>
Preparo una lista delle pagine disponibili sul server web, per aggiunngere una nuova pagina oltre a creare un file va popolata anche la variabile. Da notare che index.htm può essere richiamato sia con http://192.168.4.1/ che come http://192.168.4.1/index.htm perchè entrambe le variabili httpRequest["/"] e httpRequest["/index.htm"] puntano allo stesso file.
<source lang=javascript>
local httpRequest={}
httpRequest["/"]="index.htm";
httpRequest["/index.htm"]="index.htm";
httpRequest["/style.css"]="style.css";
Per ogni file dichiaro una intestazione il mimetype da fornire ai browser dei client per indicarne il contenuto, sarà utile soprattutto se si voglio usare immagini.
local getContentType={};
getContentType["/"]="text/html";
getContentType["/index.htm"]="text/html";
getContentType["/style.css"]="text/css";
Creo un server in ascolto sulla porta 80 ed attendo le richieste. Se la richiesta corrisponde al nome di un file lo vado a leggere a blocchi di 512 byte e lo invio al client altrimenti restituisco un messaggio di errore.
local filePos=0;
if srv then srv:close() srv=nil end
srv=net.createServer(net.TCP)
srv:listen(80,function(conn)
conn:on("receive", function(conn,request)
--print("[New Request]");
local _, _, method, path, vars = string.find(request, "([A-Z]+) (.+)?(.+) HTTP");
if(method == nil)then
_, _, method, path = string.find(request, "([A-Z]+) (.+) HTTP");
end
local formDATA = {}
Se la pagina viene richiamata passando variabili querystring decodifico l'url e invio le variabili sulla porta seriale ad arduino.
Per inviare i comandi il codice javascript passa una richiesta XMLHttpRequest richiedendo una pagina inesistente ma passando alcune variabili.
Con questo metodo non è necessario ricaricare la pagina e il traffico di rete risulta minimo.
if (vars ~= nil)then
--for k, v in string.gmatch(vars, "(%w+)=(%w+)&*") do
-- print("["..k.."="..v.."]");
-- formDATA[k] = v
--end
print(vars)
end
if getContentType[path] then
requestFile=httpRequest[path];
-- print("[Sending file "..requestFile.."]");
filePos=0;
conn:send("HTTP/1.1 200 OK\r\nContent-Type: "..getContentType[path].."\r\n\r\n");
else
-- print("[File "..path.." not found]");
conn:send("HTTP/1.1 404 Not Found\r\n\r\n")
conn:close();
collectgarbage();
end
end)
In questa sezione viene gestito l'invio della pagina web a blocchi di 512 byte fino al termine del file.
conn:on("sent",function(conn)
if requestFile then
if file.open(requestFile,r) then
file.seek("set",filePos);
local partial_data=file.read(512);
file.close();
if partial_data then
filePos=filePos+#partial_data;
--print("["..filePos.." bytes sent]");
conn:send(partial_data);
if (string.len(partial_data)==512) then
return;
end
end
--else
-- print("[Error opening file"..requestFile.."]");
end
end
--print("[Connection closed]");
conn:close();
collectgarbage();
end)
end)
Index.htm
Codice del file index.htm
Questa pagina è stata creata per dispositivi mobili, legge gli eventi su uno schemro touch, al momento utilizzarla da pc non è possibile, anche se penso basti poco per riadattarla.
L'aspetto è minimale, ci sono due quadrati il cui trascinamento sullo schermo regola la potenza erogata verso i motori.
Motori girano in entrambe le direzioni e riportandoli in posizione centrale le route si fermano.
Innanzitutto definiamo lo stile della pagina per garantirne la compatibilità con i vari dispositivi.
<html>
<head>
<meta name="viewport" content="initial-scale = 1.0,maximum-scale = 1.0"/>
<style>
body {padding: 0; margin: 0;}
#heade{ border: 3px solid #ff0000; height: 100px; position: fixed; top: 0px; right: 100px; left: 100px}
#l{ border: 3px solid #ff0000; margin: auto 200px auto 0px; width: 100px; height: 100px; position: fixed; top: 200px; left: 0px}
#r{ border: 3px solid #0000ff; margin: 0px auto auto 255px; width: 100px; height: 100px; position: fixed; top: 200px; right: 0px}
.c{ border: -1px solid #000000; width: 100%; position: fixed}
</style>
Dopo aver azzerato variabili passiamo a gestire gli eventi di tocco sullo schermo.
Vengono letti i dati anche durante lo scorrimento (è una predisposzione per ampliamenti di codice futuri) e quando solleviamo il dito dallo schermo parte la richiesta in background della pagina test a cui vengono accodati tutti i parametri da passare ad arduino.
Inoltre vengono spostati i quadrati sulla pagina per dare un feedback visivo al pilota.
<script>
var L=200;
var R=200;
var ML=0;
var MR=0;
var DL=0;
var DR=0;
var EL=0;
var ER=0;
addEventListener('touchmove', function(e) {
e.preventDefault();
var touch = e.touches[0];
var posY = touch.pageY - 50;
var posX = touch.pageX - 50;
if (posY>=0 && posY<=400) {
X=posX;
Y=posY;
if(e.touches.length == 1 && posX < 100) {
document.getElementById("l").style.top = posY+'px';
L=Math.round(posY);
}
if(e.touches.length == 1 && posX > 250) {
document.getElementById("r").style.top = posY+'px';
R=Math.round(posY);
}
if(e.touches.length == 2) { // If two fingers are touching
document.getElementById("l").style.top = posY+'px';
L=Math.round(posY);
document.getElementById("r").style.top = posY+'px';
R=Math.round(posY);
}
}
}, false);
addEventListener('touchend', function(e) {
e.preventDefault();
var touch = e.touches[0];
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
}
if (L>180 && L<220) {ML=0;EL=0;} //P. centrale 10px toll.
if (R>180 && R<220) {MR=0;ER=0;}
if (L>220) {EL=1;DL=1;}
if (R>220) {ER=1;DR=1;}
if (L<180) {EL=1;DL=0;}
if (R<180) {ER=1;DR=0;}
if (L<180) {ML=Math.round(255-(L*1.30))}
if (R<180) {MR=Math.round(255-(R*1.30))}
if (L>220) {ML=Math.round(255-((400-L)*1.30))}
if (R>220) {MR=Math.round(255-((400-R)*1.30))}
//document.getElementById("header").innerHTML= ML+'-'+MR+'_'+L+'-'+R;
xhttp.open("GET", "test?R="+MR+"&l="+ML+"&D="+DR+"&d="+DL+"&E="+ER+"&e="+EL, true);
xhttp.send();
}, false);
</script>
</head>
<body>
<div id=header></div>
<div id=l></div>
<div id=r></div>
<hr class=c style="top: 190px">
<hr class=c style="top: 300px">
</body>
</html>
Questa è l'interfaccia di controllo vista dal mio telefono. Al momento sono supportati solo gli eventi provenienti da device con touch screen, per il comando da pc l'interfaccia andrà adattata per la gestione eventi del mouse.
Init.lua
Ed infine init.lua
Questo file attende tre secondi dopo il boot per lanciare il server web, si tratta di una soluzione pratica per prevenire lo start di uno script che va in errore e ci blocca il modulo wifi.
Inoltre viene settata la porta seriale per comunicare con arduino.
tmr.alarm(0, 3000, 0, function() dofile('web_serv.lua') end )
uart.setup(0,115200,8,0,1,0)
Varie
Inizialmente non ci avevo pensato, ma avendo configurato il modulo ESP8266 come access point è possibile pilotare il robottino da più device contemporaneamente, nel senso che i comandi vengono inviati in modo asincrono e la loro elaborazione dura pochissimo.
Al momento questo non risulta particolarmente interessante, ma lo potrebbe diventare in futuro, qualora si aggiungessero accessori, inoltre si potrebbe utilizzare questa caratteristica per far pilotare il robot da un bambino con un genitore che in caso il robot sia in pericolo può intervenire prima che ruschino possa ad esempio cadere per le scale.