Arduino Mega

Come utilizzare una webcam motorizzata per scattare foto da posizioni diverse e postarle online come se provenissero da webcam differenti? Con Arduino puoi farlo!

In questo articolo spiego come ho realizzato un sistema di controllo webcam uilizzando il famoso microcontrollore Arduino con modulo ethernet una webcam motorizzata Apexis APM-J902-Z-WS.

Ingredienti:

  • una posizione panoramica
  • il microcontrollore Arduino personamente io consiglio Arduino Mega
  • il modulo ethernet di Arduino Ethernet Shield
  • una micro SD
  • una webcam motorizzata
  • un pc con il tool di sviluppo per Arduino Arduino IDE
  • un cavo usb per comunicare con la scheda arduino
  • una connessione internet

 

L’obiettivo: webcam multiple in una sola

Disponendo di un terrazzo posto in posizione panoramica molto estesa, inquadrare un solo punto era davvero uno spreco. Mi sono detto, perchè non sfruttare la possibilità di movimento di una webcam motorizzata per scattare foto da angolazioni diverse, e poi postare ogni foto sui siti di webcam, come se si trattasse di webcam fisicamente messe in posti diversi ?

Per fare questa cosa, il software della webcam non era sufficiente. L’idea di utilizzare Arduino mi sembrava la cosa più semplice…più economica e anche divertente !

Ora vi spiego come ho fatto.

Analisi dello status di fatto: cosa può fare la webcam da sola ?

webcam Apexis APM-J902-Z-WS

Questa è la webcam Apexis APM-J902-Z-WS. Partiamo dallo stato di fatto di cosa può fare.

L’apexis APM-J902 innazitutto è una webcam di rete wireless dunque può essere comandata da remoto via rete (cavo o wireless). La prima cosa da fare è dunque è assegnarli un indirizzo IP statico e configurarla allo switch o router di casa. Via rete è possibile configurarla mediante l’apposito pannello di controllo.

Pannello di controllo della webcam apexis APM-J902-Z-WS

Vediamo dunque ciò che di utile al notro progetto può fare la cam.La cam può ricevere ed eseguire comandi via http per muovere l’obiettivo a destra, sinistra, in basso e in alto

  1. Ricevere ed eseguire comandi via http per restituire l’immagine puntata dall’obiettivo in quell’istante
  2. Ricevere ed eseguire comandi via http per memorizzare fino a 8 posizioni dell’obiettivo
  3. Ricevere ed eseguire comandi via http per spostare l’obiettvo in una delle 8 posizioni memorizzate

Sfruttado i punti di cui sopra è facile capire come si possa realizzare il tutto. Basta crerare un programma che per ognuna delle posizioni da fotografare esegua in sequenza:

  1. Posizionamento obiettivo su posizione 1
  2. Scaricamenti immagine su posizione 2
  3. Posizionamento obiettivo su posizione 2
  4. Scaricamenti immagine su posizione 2
  5. Posizionamento obiettivo su posizione 2
  6. Scaricamenti immagine su posizione 3
  7. Caricare le immagini su server FTP
  8. Attendere 10 minuti (o quanto vogliamo) e poi ripetere l’intero cilo

Ora basta fare un programma che realizzi tutto questo. Il problema è che bisognerebbe lasciare acceso un pc tutto il giorno e tutta la notte solo per fare questo!! Ecco perchè mi è venuta l’idea di utilizzare Arduino, che invece di qualche decina di Watts, consuma circa 4-6 Watt.

Supponendo che l’indirizzo IP della cam sia 10.0.0.100 i comandi da inviare alla cam sono semplici url, e sono i seguenti.

Comando per ricevere l’immagine “snapshot”:

http://10.0.0.100:81/snapshot.cgi?user=NOMEUTENTE&pwd=PASSWORD

Comando per richiamare lo spostamento dell’obiettivo su una determinata posizione:

http://10.0.0.100:81/decoder_control.cgi?command=31&user=NOMEUTENTE&pwd=PASSWORD

il command=31 corrisponde alla posizione preset numero 1. Per la posizione numero 2 utilizzare command=33, mentre per la posizione 2 il commad=35. La lista completa dei comandi potete scaricarla qui: PTZ Command List

Soluzione: utilizzare Arduino + Ethernet Shield

DUE PAROLE SU ARDUINO:
Per chi non lo conoscesse, Arduino è un microcontrollore programmabile, un progetto Open Source tutto italiano. In soldoni è una scheda elettronica dotata di un microprocessore, una memoria per contenere i programmi, diversi ingressi e uscite digitali e analogiche, una interfaccia di programmazione C/C++ e una miriadi di componenti interfacciabili. Arduino è una piattaforma a basso costo ed è correntemente utilizzata sia dagli studenti per progetti sperimentali, sia dagli hobbisti appassionati di elettronica, sia per utilizzi più professionali.

Esistono diverse versioni di Arduino. Nel mio progetto ho utilizzato Arduino Mega 2560 R3:

Aarduino Mega 2560-r3

Puoi acquistarlo direttamente qui:

Arduino ha la possibilità di connettersi alla rete, e quindi anche a internet mediante una scheda aggiuntiva chiamata Ethernet Shield:

Arduino Ethernet Shield - Arduino Modulo Ethernet

Acquistabile qui:

 

Le due schede si utilizzano sovrapponendo il modulo Ethernet Shield alla scheda Arduino in modo che i pettini di collegamento sulla scheda Ethernet si infilino nel fori del connettore sulla scheda Arduino.

Del modulo Ethernet Shield sfrutteremo il lettore di memorie Micro SD integrato per memorizzare le immagini catturate prima di inviarle sul server FTP.

Utilizzando l’IDE di sviluppo apposito e le opportune librerie è possibile programmare Arduino. In questo articolo non spiegherò come installare e configurare il tool di sviluppo sul proprio pc dato che è tutto ben spiegato qui.

Il risultato Finale:

Prima di tutto vediamo il risultato finale

Questo il video che illustra il funzionamento:

E queste le tre immagini catturate:

Le tre immagini in realtà sono pulite senza la scritta dell’orario e il logo, cosa che viene poi messo da un mio codice PHP il cui funzionamento non viene trattato in questa sede. Ma il risultato finale è comunque questo, ovvero tre immagini distinte comem se provenissero da tre webcam fisse diverse.

Il programma

Essendo il codice un po lungo non spiegherò tutto passo passo ma solo alcuni dettagli importanti lasciando ai commenti nel codice la spiegazione del resto.

Le librerie che ci serviranno sono le seguenti:

 
#include <SPI.h>; // per la comunicazine seriale

Passiamo alla dichiarazione delle variabili principali. In particolare va indicato il MAC ADDRESS della scheda Ethernet Shield. Questo numero lo ricavate da una etichetta sulla scheda stessa. E’ fondamentale per ottenere l’IP dal DHCP Client.

const int LED_PIN=13;                   // pin a cui collego un led opzionale per la segnalazione di programmain ciclo
const char kHostname[] = "10.0.0.100";  // INDIRIZZO IP DELLA WEBCAM
char kPath[] = "/snapshot.cgi?user=NOMEUTENTE&pwd=PASSWORD";  // url della webcam che restituisce l'immagine
IPAddress server (62, 149, 141, 10);  // IP DEL SERVER FTP
byte mac[] = { 0x90, 0xA2, 0xDA, 0x02, 0x00, 0xFF  };  // MAC ADDRESS DELLA SCHEDA ETHERNET SHIELD
const int kNetworkTimeout = 30*1000; // Numero di millisecondi da attendere renza ricevere nulla prima di rinunciare
const int kNetworkDelay = 1000;      // Numero di millisecondi da attendere prima di riprovare se non ricevo dati
File file;
char outBuf[128];
char outCount;
EthernetClient client;
EthernetClient dclient;
HttpClient http(client);
int err =0;
byte clientBuf[2048];

La funzione setup() si avvia quando alimentate la scheda e ha il compito di:

  1. Inizializzare la micro sim SD
  2. Inizializzare la comunicazione seriale con il pc per la stampa dei logs
void setup()
{
    pinMode(LED_PIN, OUTPUT); // Led che indica se è in ciclo o in attesa 

   // initialize serial communications at 9600 bps:
  Serial.begin(9600);
  Serial.println("(c) Copyright 2013 Alessandro Scola");
  Serial.println("http://www.alessandroscola.it");
  Serial.println("Released under Apache License, version 2.0");
  Serial.println("Software da utilizzare con Arduino Mega 2560 + Ethernet Shield");
  Serial.println("Il software controlla una IP cam Apexis, muovendola in 3 posizioni \"preset\" differenti");
  Serial.println("salvando le 3 immagini su SD card,");
  Serial.println("e successivamente facendo l'upload delle immagini su Server remoto via FTP.");
  Serial.println("Il ciclo si ripete ogni 10 minuti. Ogni 10 minuti vengono acquisite le 3 immagini");
  Serial.println("********************************************");
  Serial.print("Controllo SD Card...");

  //OBBLIGATORIO per l'uso della SD. Dato che arduino comunica alla scheda SD ed alla scheda ethernet con gli stessi pin,
  //solo una delle due può essere utilizzata alla volta. Tramite il pin 53 impostato ad HIGH si disattiva la scheda ethernet
  //in modo da poter usare la scheda SD e viceversa. impostando il pin 53 a OUTPUT si fa in modo che la libreria SD abbia la capacità
  //di gestire in automatico l'attivazione e la disattivazione di questa (in modo da non doverlo fare noi ogni volta) ma questa
  //gestione automatica del pin 53 da parte della libreria SD inizia solo dopo aver inizializzato la libreria con il comando SD.begin(4)

   digitalWrite(53,HIGH);  // il pin 53 è solo per la scheda Arudino Mega. per le altre schede usare il pin 10
   pinMode(53,OUTPUT);     // il pin 53 è solo per la scheda Arudino Mega. per le altre schede usare il pin 10 

   // set up SD
   if(SD.begin(4) == 0)
      Serial.println("errore, non riesco ad accedere a SD");
   else
   {
     Serial.println("SD Card OK !");
   }
}

Fate attenzione al Pin 53 (per la Scheda Arduino Mega, 10 per le altre schede), deve essere impostato subito come uscita e subito a valore HIGH. Questo è spiegato nei commenti e serve a disabilitare la shceda ethernet per poter inizializzare la comunicazione con la microsim SD, dato che l’accesso alla SD è gestita dalla stessa scheda che gestisce la scheda di rete.

Il resto del codice C++ comprende la funzione loop() che rappresenta l’intero ciclco del programma e alcune funzioni per la gestione della connessione FTP.

void loop()
{
  int err =0;
  EthernetClient c;
  HttpClient http(c);
  long bufferCount = 0;

  digitalWrite(LED_PIN,HIGH);

   Serial.println("********************************************");
   Serial.println("Sto ottendendo indirizzo IP dal router ...");
   while (Ethernet.begin(mac) != 1)
   {
    Serial.print("Errore ottendendo indirizzo IP via DHCP, provo ancora ...");
    delay(15000);
   }
   Serial.println("Ok !");

 //******************************************************************************************

 //Da il comando per la rotazione in posizione 1
 // go tp POSIZIONE 1:
 Serial.println("********************************************");
 Serial.println ("Muovo la cam in POSIZIONE 1 e attendo che si posizioni");

 char kPathPOSIZIONE1[] ="/decoder_control.cgi?command=31&user=NOMEUTENTE&pwd=PASSWORD";
 err = http.get(kHostname, 81, kPathPOSIZIONE1);
 if (err != 0)
 {
   Serial.println ("Errore muovendo a POSIZIONE 1.");
 }
 http.stop();
 delay(8000); // attendo 8 secondi che la cam si posizioni

 //******************************************************************************************

  char kPath[] ="/snapshot.cgi?user=NOMEUTENTE&pwd=PASSWORD";
  err = http.get(kHostname, 81, kPath);
  if (err == 0)
  {
    Serial.println("Connessione con la cam OK !");

    err = http.responseStatusCode();
    if (err >= 0)
    {
      Serial.print("Codice risposta http: ");
      Serial.println(err);

      // Usually you'd check that the response code is 200 or a
      // similar "success" code (200-299) before carrying on,
      // but we'll print out whatever response we get

      err = http.skipResponseHeaders();
      if (err >= 0)
      {
        int bodyLen = http.contentLength();
        Serial.print("La risposta e' lunga: ");
        Serial.print(bodyLen);
        Serial.println(" bytes");

        file = SD.open("cam1.jpg", FILE_WRITE);
        file.seek(0);
        if (file) Serial.print("Salvo l'immagine su SD...");
        else Serial.println("Impossibile aprire file!");

        // Now we've got to the body, so we can print it out
        unsigned long timeoutStart = millis();
        byte c;
        // Whilst we haven't timed out & haven't reached the end of the body

        bufferCount=0;
        while ( (http.connected() || http.available()) &&
               ((millis() - timeoutStart) < kNetworkTimeout) ) { if (http.available()) { //c = http.read(); clientBuf[bufferCount] = http.read(); bufferCount++; // Print out this character //Serial.print(c); //if (file) file.write(c); if(bufferCount > 2047)
                  {
                    file.write(clientBuf,2048);
                    bufferCount = 0;
                  }

                // We read something, reset the timeout counter
                timeoutStart = millis();
            }
            else
            {
                // We haven't got any data, so let's pause to allow some to
                // arrive
                delay(kNetworkDelay);
            }
        }// fine while
        if(bufferCount > 0) file.write(clientBuf,bufferCount);

        file.close();
        Serial.println ("File chiuso.");
      }
      else
      {
        Serial.print("Failed to skip response headers: ");
        Serial.println(err);
      }
    }
    else
    {
      Serial.print("Nessuna risposta: ");
      Serial.println(err);
    }
  }
  else
  {
    Serial.print("Connect failed: ");
    Serial.println(err);
  }
  http.stop();

 //****************************************************************************************

 //Da il comando per la rotazione in posizione 2
 // go tp POSIZIONE 2:  

 Serial.println("********************************************");
 Serial.println ("Muovo la cam in POSIZIONE 2 e attendo che si posizioni");

 char kPathPOSIZIONE2[] ="/decoder_control.cgi?command=33&user=NOMEUTENTE&pwd=PASSWORD";
 err = http.get(kHostname, 81, kPathPOSIZIONE2);
 if (err != 0)
 {
   Serial.println ("Errore muovendo a POSIZIONE 2.");
 }
 http.stop();
 delay(6000);

 //******************************************************************************************

 // SALVA IMG 2

  err = http.get(kHostname, 81, kPath);
  if (err == 0)
  {
    Serial.println("Connessione con la cam OK !");

    err = http.responseStatusCode();
    if (err >= 0)
    {
      Serial.print("Codice risposta http: ");
      Serial.println(err);

      // Usually you'd check that the response code is 200 or a
      // similar "success" code (200-299) before carrying on,
      // but we'll print out whatever response we get

      err = http.skipResponseHeaders();
      if (err >= 0)
      {
        int bodyLen = http.contentLength();
        Serial.print("La risposta e' lunga: ");
        Serial.print(bodyLen);
        Serial.println(" bytes");

        file = SD.open("cam2.jpg", FILE_WRITE);
        file.seek(0);
        if (file) Serial.print("Salvo l'immagine su SD...");
        else Serial.println("Impossibile aprire file!");

        // Now we've got to the body, so we can print it out
        unsigned long timeoutStart = millis();
        byte c;
        // Whilst we haven't timed out & haven't reached the end of the body
        bufferCount=0;
        while ( (http.connected() || http.available()) &&
               ((millis() - timeoutStart) < kNetworkTimeout) ) { if (http.available()) { //c = http.read(); clientBuf[bufferCount] = http.read(); bufferCount++; // Print out this character //Serial.print(c); //if (file) file.write(c); if(bufferCount > 2047)
                  {
                    file.write(clientBuf,2048);
                    bufferCount = 0;
                  }

                // We read something, reset the timeout counter
                timeoutStart = millis();
            }
            else
            {
                // We haven't got any data, so let's pause to allow some to
                // arrive
                delay(kNetworkDelay);
            }
        }// fine while

        if(bufferCount > 0) file.write(clientBuf,bufferCount);
        file.close();
        Serial.println ("File chiuso.");
      }
      else
      {
        Serial.print("Failed to skip response headers: ");
        Serial.println(err);
      }
    }
    else
    {
      Serial.print("Nessuna risposta: ");
      Serial.println(err);
    }
  }
  else
  {
    Serial.print("Connect failed: ");
    Serial.println(err);
  }
  http.stop();

  //****************************************************************************************

 //Da il comando per la rotazione in posizione 3
 // go tp POSIZIONE 3:  

 Serial.println("********************************************");
 Serial.println ("Muovo la cam in POSIZIONE 3 e attendo che si posizioni");

 char kPathPOSIZIONE3[] ="/decoder_control.cgi?command=35&user=NOMEUTENTE&pwd=PASSWORD";
 err = http.get(kHostname, 81, kPathPOSIZIONE3);
 if (err != 0)
 {
   Serial.println ("Errore muovendo a POSIZIONE 3.");
 }
 http.stop();
 delay(4000);

 //******************************************************************************************

 // SALVA IMG 3

  err = http.get(kHostname, 81, kPath);
  if (err == 0)
  {
    Serial.println("Connessione con la cam OK !");

    err = http.responseStatusCode();
    if (err >= 0)
    {
      Serial.print("Codice risposta http: ");
      Serial.println(err);

      // Usually you'd check that the response code is 200 or a
      // similar "success" code (200-299) before carrying on,
      // but we'll print out whatever response we get

      err = http.skipResponseHeaders();
      if (err >= 0)
      {
        int bodyLen = http.contentLength();
        Serial.print("La risposta e' lunga: ");
        Serial.print(bodyLen);
        Serial.println(" bytes");
        Serial.println("Body returned follows:");

        file = SD.open("cam3.jpg", FILE_WRITE);
        file.seek(0);
        if (file) Serial.print("Salvo l'immagine  su SD...");
        else Serial.println("Impossibile aprire file!");

        // Now we've got to the body, so we can print it out
        unsigned long timeoutStart = millis();
        byte c;
        // Whilst we haven't timed out & haven't reached the end of the body
        bufferCount=0;
        while ( (http.connected() || http.available()) &&
               ((millis() - timeoutStart) < kNetworkTimeout) ) { if (http.available()) { //c = http.read(); clientBuf[bufferCount] = http.read(); bufferCount++; // Print out this character //Serial.print(c); //if (file) file.write(c); if(bufferCount > 2047)
                  {
                    file.write(clientBuf,2048);
                    bufferCount = 0;
                  }

                // We read something, reset the timeout counter
                timeoutStart = millis();
            }
            else
            {
                // We haven't got any data, so let's pause to allow some to
                // arrive
                delay(kNetworkDelay);
            }
        }// fine while
        if(bufferCount > 0) file.write(clientBuf,bufferCount);
        file.close();
        Serial.println ("File chiuso.");
      }
      else
      {
        Serial.print("Failed to skip response headers: ");
        Serial.println(err);
      }
    }
    else
    {
      Serial.print("Nessuna risposta: ");
      Serial.println(err);
    }
  }
  else
  {
    Serial.print("Connessione fallita: ");
    Serial.println(err);
  }
  http.stop();

  //******************************************************************************************

 //Da il comando per la rotazione in posizione 1
 // go tp POSIZIONE 1:
 Serial.println("********************************************");
 Serial.println ("Riporto la cam in POSIZIONE 1 e attendo che si posizioni");

 char kPathPOSIZIONE4[] ="/decoder_control.cgi?command=31&user=NOMEUTENTE&pwd=PASSWORD";
 err = http.get(kHostname, 81, kPathPOSIZIONE1);
 if (err != 0)
 {
   Serial.println ("Errore muovendo a POSIZIONE 1.");
 }
 http.stop();
 delay(8000); // attendo 8 secondi che la cam si posizioni

 //******************************************************************************************

  //Carico le immagini sul server FTP
  Serial.println("********************************************");
  Serial.println ("");
  Serial.println ("CARICO LE 3 IMMAGINI SU SERVER FTP");
  Serial.println ("");
  uploadFTP();
  Serial.println("********************************************");

  Serial.println("Attendo 10 minuti e poi ricomincero' il ciclo...");
  digitalWrite(LED_PIN, LOW);
  delay(600000);
  //Serial.println("Attendo 15 secondi ...");
  //delay(15000);
} // fine void loop()

// #################### FUNZIONI PER L'FTP: #########################
byte uploadFTP()
{
  char fileName1[13] = "CAM1.JPG";
  char fileName2[13] = "CAM2.JPG";
  char fileName3[13] = "CAM3.JPG";
  byte clientBuf[64];
  long clientCount = 0;

  HttpClient http(client);

  if (client.connect(server,21))
  {
    Serial.println(F("Connesso al server..."));
  }
  else
  {
    Serial.println(F("Connessione al server fallita."));
    return 0;
  }

   if(!eRcv()) return 0;

  client.println(F("NOMEUTENTE FTP"));

  if(!eRcv()) return 0;

  client.println(F("PASSWORD FTP"));

  if(!eRcv()) return 0;

  client.println(F("SYST"));

  if(!eRcv()) return 0;

  client.println(F("PASV"));

  if(!eRcv()) return 0;

  char *tStr = strtok(outBuf,"(,");
  int array_pasv[6];
  for ( int i = 0; i < 6; i++) {
    tStr = strtok(NULL,"(,");
    array_pasv[i] = atoi(tStr);
    if(tStr == NULL)
    {
      Serial.println(F("Bad PASV Answer"));    

    }
  }

  unsigned int hiPort,loPort;

  hiPort = array_pasv[4] << 8; loPort = array_pasv[5] & 255; Serial.print(F("Data port: ")); hiPort = hiPort | loPort; Serial.println(hiPort); if (dclient.connect(server,hiPort)) { Serial.println(F("Data connected")); } else { Serial.println(F("Data connection failed")); client.stop(); return 0; } //******************************************************** client.println(F("TYPE I")); // Importante! seleziona modo BINARIO per il trasferimento !! client.println(F("CWD /www.alessandroscola.it/webcam-belluno/CAM1")); client.print(F("STOR ")); client.println(fileName1); if(!eRcv()) { dclient.stop(); return 0; } file = SD.open(fileName1, FILE_READ); if(!file) { Serial.println("Apertura SD fallita !"); return 0; } Serial.println(F("Writing")); clientCount=0; while(file.available()) { clientBuf[clientCount] = file.read(); clientCount++; if(clientCount > 63)
    {
      dclient.write(clientBuf,64);
      clientCount = 0;
    }
  }
  if(clientCount > 0) dclient.write(clientBuf,64);
  dclient.stop();
  Serial.println(F("Data disconnected"));
  file.close();

  //*********************************************************
  if (dclient.connect(server,hiPort))
  {
    Serial.println(F("Data connected"));
  }
  else
  {
    Serial.println(F("Data connection failed"));
    client.stop();
    return 0;
  }

   client.println(F("CWD /www.alessandroscola.it/webcam-belluno/CAM2"));
  client.print(F("STOR "));
  client.println(fileName2);

   if(!eRcv())
  {
    dclient.stop();
    return 0;
  }
  file = SD.open(fileName2, FILE_READ);
  if(!file)
  {
    Serial.println("Apertura SD fallita");
    return 0;
  }
  Serial.println(F("Writing"));
  clientCount=0;
  while(file.available())
  {
    clientBuf[clientCount] = file.read();
    clientCount++;

    if(clientCount > 63)
    {
      dclient.write(clientBuf,64);
      clientCount = 0;
    }
  }
  if(clientCount > 0) dclient.write(clientBuf,clientCount);
  dclient.stop();
  Serial.println(F("Data disconnected"));
  file.close();

  //*********************************************************
  if (dclient.connect(server,hiPort))
  {
    Serial.println(F("Data connected"));
  }
  else
  {
    Serial.println(F("Data connection failed"));
    client.stop();
    return 0;
  }

  client.println(F("CWD /www.alessandroscola.it/webcam-belluno/CAM3"));
  client.print(F("STOR "));
  client.println(fileName3);

   if(!eRcv())
  {
    dclient.stop();
    return 0;
  }

  file = SD.open(fileName3, FILE_READ);
  if(!file)
  {
    Serial.println("Apertura SD fallita");
    return 0;
  }
  Serial.println(F("Writing"));
  clientCount=0;
  while(file.available())
  {
    clientBuf[clientCount] = file.read();
    clientCount++;

    if(clientCount > 63)
    {
      dclient.write(clientBuf,64);
      clientCount = 0;
    }
  }
  if(clientCount > 0) dclient.write(clientBuf,clientCount);
  dclient.stop();
  Serial.println(F("Data disconnected"));
  file.close();
  //******************************************************
  dclient.stop();
  Serial.println(F("Data disconnected"));

  if(!eRcv()) return 0;

  client.println(F("QUIT"));

  if(!eRcv()) return 0;

  client.stop();
  Serial.println(F("Command disconnected"));

  file.close();
  Serial.println(F("SD Chiusa."));
  return 1;

} // fine doFTP()

byte eRcv()
{
  byte respCode;
  byte thisByte;

  while(!client.available()) delay(1);

  respCode = client.peek();

  outCount = 0;

  while(client.available())
  {
    thisByte = client.read();
    Serial.write(thisByte);

    if(outCount < 127) { outBuf[outCount] = thisByte; outCount++; outBuf[outCount] = 0; } } if(respCode >= '4')
  {
    efail();
    return 0;
  }

  return 1;
}

void efail()
{
  byte thisByte = 0;

  client.println(F("QUIT"));

  while(!client.available()) delay(1);

  while(client.available())
  {
    thisByte = client.read();
    Serial.write(thisByte);
  }

  client.stop();
  Serial.println(F("Command disconnected"));
  file.close();
  Serial.println(F("SD Chiusa."));
}