ESP8266 Web server touch

Da raspibo.
Jump to navigation Jump to search
Ruschino web0.png

ESP8266 Web server touch
Interfaccia di comando per Ruschino WiFi, ma non solo

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.

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.

Ruschino web0.png Ruschino web1.png

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.


Riferimenti