Ruschino WiFi

Da raspibo.
Jump to navigation Jump to search

Software

Il software è ancora in fase di completamento, ma si basa su due elementi intelligenti: ESP8266 che viene utilizzato come access point, e server web.

Arduino che interpreta semplici comandi inviati da ESP8266 e pilota il ponte H.

ESP8266 Web server

Sul modulo wifi ho aggiornato il firmware installando Nodemcu.

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 .... e proposto in un video.

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.

Dopo aver creato

 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.

 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.

<source lang=lua>
 local httpRequest={}
 httpRequest["/"]="index.htm";
 httpRequest["/index.htm"]="index.htm";
 httpRequest["/style.css"]="style.css";

Per ogni file dichiaro una intestazione da fornire ai client e descriverne il contenuto.

 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 restittuisco 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)

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>

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)

Codice arduino

 String comando;
 int en12=9;
 int en34=10;
 int dir12=4;
 int dir34=5;
 int board=1;
 int out_bridge_n=0;
 int incomingByte = 0;
 int s_speed_sx=0;
 int s_speed_dx=0;
 boolean direction_sx=0;
 boolean direction_dx=0;
 boolean enable_sx=0; //non utilizzato
 boolean enable_dx=0; //non utilizzato
 int command_name;
 int position;
 char cmd;
 // Calculate based on max input size expected for one command
 #define INPUT_SIZE 30
 void motor_drive(int out_bridge, boolean dir, int speed) {
  if (out_bridge == 34) {
    digitalWrite(dir34, dir);
    analogWrite(en34, speed);  
  } 
  else if (out_bridge == 12) {
    digitalWrite(dir12, dir);
    analogWrite(en12, speed);
  }  
 }  
 void setup() {
  Serial.begin(115200); 
  pinMode(en12, OUTPUT);
  pinMode(dir12, OUTPUT);
  pinMode(en34, OUTPUT);
  pinMode(dir34, OUTPUT); 
  Serial.println("Start");
 }
 void loop() {
  if (Serial.available() > 0) {
    char input[INPUT_SIZE + 1];
    byte size = Serial.readBytes(input, INPUT_SIZE);
    input[size] = 0;
    char* command = strtok(input, "&");
    while (command != 0)
    {
      char* command_value = strchr(command, '=');
      if (command_value != 0)
      {
        *command_value = 0;
        command_name = command[0];
        ++command_value;
        position = atoi(command_value);
      }
      command = strtok(0, "&");
      switch(command_name) {
      case 'l':
        s_speed_sx=position;
        break;
      case 'R':
        s_speed_dx=position;
        break;  
      case 'D':
        direction_dx=position;
        break;
      case 'd':
        direction_sx=position;
        break;  
      case 'E':
        enable_dx=position;
        break;
      case 'e':
        enable_sx=position;
        break;  
      }  
    }
   motor_drive(34,direction_sx,s_speed_sx);
   motor_drive(12,direction_dx,s_speed_dx); 
  }
 }