Ethernet, TextFinder e stream: pagine incomplete (risolto!)

Salve a tutti,

sto provando ad implementare un codice che estragga le informazioni meteo da una pagina HTML del servizio ilmeteo.it
Per far questo, richiedo la pagina HTML con i dati e faccio la ricerca delle parti utili utilizzando la libreria TextFinder
http://playground.arduino.cc/Code/TextFinder
Il codice è derivato da un'analogo presente in rete che prende i dati da Yahoo.
http://forum.arduino.cc/index.php?topic=121992.msg920175#msg920175
Quello che succede è che la ricezione della pagina si interrompe, quasi sempre dopo lo stesso numero di caratteri. Per accorgermene ho abilitato il debug attivando la linea #define DEBUG_OUTPUT nel file TextFinder.cpp
Questo problema sembra esserci anche nello sketch originale solo che siccome i dati si trovano giusto nella prima parte ricevuta, non si evidenzia l'errore.

Il codice è questo.

/*
 * IlMeteo01
 * derived from Yahoo01
 * http://forum.arduino.cc/index.php?topic=121992.msg920175#msg920175
 *
 * Original code by Webmeister
 * http://forum.arduino.cc/index.php?action=profile;u=15387
 * Read Yahoo Weather API XML
 * 03.09.2012
 * http://arduino-praxis.ch
 *
 * Maybe this is the original one
 * SimpleClientGoogleWeatherDHCP.pde
 *
 * Modifications by zoomx
 * 2015-10-14
 * Added DHCP
 * Added Current Condition
 * http://forum.arduino.cc/index.php?topic=121992.msg933688#msg933688
 *
 * 2015-10-22
 * Changed to IlMeteo.it
 *
 */

//#define DEBUG_OUTPUT  // in TextFinder.cpp messo qui non funziona!

#include <SPI.h>
#include <Ethernet.h>
#include "TextFinder.h"   //http://playground.arduino.cc/Code/TextFinder

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xAD };
byte ip[] = { 10, 0, 1, 101 };
byte gateway[] = { 10, 0, 1, 1 };
byte subnet[] = { 255, 255, 255, 0 };

IPAddress server(94, 32, 108, 10);   //IlMeteo 94.32.108.10

EthernetClient client;
TextFinder  finder( client );

char place[20];
char hum[5];
char temp[5];
char currentcond[20];

void setup()
{

  // Start Serial Port
  Serial.begin(115200);
  Serial.println(F("IlMeteo01"));
  Serial.println(F("Setup..."));
  pinMode(4, OUTPUT);
  digitalWrite(4, HIGH);
  // Start Ehternet
  //Ethernet.begin(mac, ip);
  if (Ethernet.begin(mac) == 0) {
    Serial.println(F("Failed to configure Ethernet using DHCP"));
    // no point in carrying on, so do nothing forevermore:
    for (;;)
      ;
  }
  // print your local IP address:
  Serial.print(F("My IP address: "));
  for (byte thisByte = 0; thisByte < 4; thisByte++) {
    // print the value of each byte of the IP address:
    Serial.print(Ethernet.localIP()[thisByte], DEC);
    Serial.print(".");
  }
  Serial.println();
}


void loop()
{
  if (client.connect(server, 80))
  {
    Serial.println(F("Connecting to IlMeteo..."));
    client.println("GET /box/previsioni.php?citta=5913&type=real1 HTTP/1.0");
    client.println("HOST:www.ilmeteo.it\n\n");
    client.println("Connection: close");
    client.println();
    Serial.println(F("Connected..."));
  }
  else
  {
    Serial.println(F(" connection failed"));
  }


  if (client.connected())
  {

    // Place/City
    if ( (finder.getString("<div class=\"titolo\">", "</div>", place, 20) != 0) )
    {
      //Serial.print(F("Citta':  "));
      //Serial.println(place);
    }

    // Current Condition
    //if ( (finder.getString("_blank\" title=\"Meteo %%CITY%%\">", "</a></b>
", currentcond, 20) != 0) )
    if ( (finder.getString("%%\">", "</a></b>
", currentcond, 20) != 0) )
    {
      //Serial.print(F("Tempo attuale:  "));
      //Serial.println(currentcond);
    }

    // Temperature
    if ( (finder.getString("color:red\">", "&deg;C", temp, 4) != 0) )
    {
      //Serial.print(F("Temperatura C:  "));
      //Serial.println(temp);
    }
    else
    {
      //Serial.println(F("Nessun dato di temperatura"));
    }


    if ( (finder.getString("Umidit&agrave;: ", "
", hum, 4) != 0) )
    {
      //Serial.print(F("Umidita':  "));
      //Serial.println(hum);
    }
    else
    {
      //Serial.println(F("Nessun dato di umidita'"));
    }

    // END XML
    Serial.println();
    Serial.print(F("Citta':  "));
    Serial.println(place);
    Serial.print(F("Tempo attuale:  "));
    Serial.println(currentcond);
    Serial.print(F("Temperatura C:  "));
    Serial.println(temp);
    Serial.print(F("Umidita':  "));
    Serial.println(hum);

    Serial.println(F("FINE"));
  }
  else
  {
    Serial.println(F("Disconnected"));
  }

  client.stop();
  client.flush();
  delay(60000);
}

Se lo provate, mostrerà la parte scaricata che, appunto, si interrompe. Quindi stampa i dati trovati, in questo caso la sola città. Lo sketch non si pianta ne va in reset, dopo 60 secondi viene rieffettuata la richiesta e così all'infinito.
Ho provato anche ad usare la classe stream ma succede esattamente lo stesso, stampa solo la città. Non so se in stream c'è un debug per vedere cosa effettivamente viene trattato.
Se provo uno degli esempi della libreria Ethernet, quello che scarica la pagina iniziale di Google, va tutto bene e viene stampato l'intero codice ricevuto, ben più lungo della pagina de il meteo.it
Ho provato a cambiare shield ma il risultato è identico.
Ho anche provato su due reti diverse al lavoro e a casa ma il risultato è sempre identico.
Una cosa strana è che a volte, prima che abilitassi il debug, il codice ha funzionato la prima volta ma poi non ha più funzionato alle richieste successive. Da quando ho abilitato il debug però non succede mai.
Avete qualche idea?
In allegato ho messo lo sketch e la libreria già modificata per il debug. Vanno nella stessa cartella.

Edit: ho cambiato ed accorciato il titolo perché non avevo più caratteri a disposizione.
Edit: finalmente risolto! Buffer overrun!!! Vedi post #24.

IlMeteo01.zip (5.64 KB)

Lo provo anche se lo avrei fatto in modo diverso.

e questo?

pinMode(4, OUTPUT);
digitalWrite(4, HIGH);

dovrebbe essere un 10 non un 4

Era un'aggiunta che avevo fatto cercando in giro ma non ha portato ad alcuna differenza nei risultati.

Accetto anche il suggerimento del modo diverso: come?
Io avevo pensato a riprendere l'esempio della pagina di google e farmi un parser da me ma mi sembra di reinventare l'acqua calda.
Questa è l'altra implementazione che mi è venuta in mente ma il risultato è identico

/*
 * IlMeteo02
 * derived from Yahoo01
 * http://forum.arduino.cc/index.php?topic=121992.msg920175#msg920175
 *
 * Original code by Webmeister
 * http://forum.arduino.cc/index.php?action=profile;u=15387
 * Read Yahoo Weather API XML
 * 03.09.2012
 * http://arduino-praxis.ch
 *
 * Maybe this is the original one
 * SimpleClientGoogleWeatherDHCP.pde
 *
 * Modifications by zoomx
 * 2015-10-14
 * Added DHCP
 * Added Current Condition
 * http://forum.arduino.cc/index.php?topic=121992.msg933688#msg933688
 *
 * 2015-10-22
 * Changed to IlMeteo.it
 *
 */

//#define DEBUG_OUTPUT  //messo qui non funziona!
// Max string length may have to be adjusted depending on data to be extracted
#define MAX_STRING_LEN  20


#include <SPI.h>
#include <Ethernet.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xAD };
byte ip[] = { 10, 0, 1, 101 };
byte gateway[] = { 10, 0, 1, 1 };
byte subnet[] = { 255, 255, 255, 0 };

IPAddress server(94, 32, 108, 10);   //IlMeteo 94.32.108.10

EthernetClient client;

char place[20];
char hum[5];
char temp[5];
char currentcond[20];

// Setup vars
char tagStr[MAX_STRING_LEN] = "";
char dataStr[MAX_STRING_LEN] = "";
char tmpStr[MAX_STRING_LEN] = "";
char endTag[3] = {'<', '/', '\0'};
int len;

// Flags to differentiate XML tags from document elements (ie. data)
boolean tagFlag = false;
boolean dataFlag = false;


void setup()
{

  // Start Serial Port
  Serial.begin(115200);
  Serial.println(F("IlMeteo02"));
  Serial.println(F("Setup..."));
  // Start Ehternet
  //Ethernet.begin(mac, ip);
  if (Ethernet.begin(mac) == 0) {
    Serial.println(F("Failed to configure Ethernet using DHCP"));
    // no point in carrying on, so do nothing forevermore:
    for (;;)
      ;
  }
  // print your local IP address:
  Serial.print(F("My IP address: "));
  for (byte thisByte = 0; thisByte < 4; thisByte++) {
    // print the value of each byte of the IP address:
    Serial.print(Ethernet.localIP()[thisByte], DEC);
    Serial.print(".");
  }
  Serial.println();
}


void loop()
{
  if (client.connect(server, 80))
  {
    Serial.println(F("Connecting to IlMeteo..."));
    client.println("GET /box/previsioni.php?citta=5913&type=real1 HTTP/1.0");
    client.println("HOST:www.ilmeteo.it\n\n");
    client.println("Connection: close");
    client.println();
    Serial.println(F("Connected..."));
  }
  else
  {
    Serial.println(F(" connection failed"));
  }


  if (client.connected())
  {

    // Place/City
    if ( client.find("<div class=\"titolo\">"))
    {
      tagFlag = false;
      do
      {
        char inChar = client.read();
        if (inChar == '<')
        {
          tagFlag = true;
        }
        else
        {
          Serial.print(inChar);
          addChar(inChar, tmpStr);
        }
      } while (tagFlag == false);
    }

    Serial.println();
    Serial.println(tmpStr);

    clearStr(tmpStr);


    // Current Condition
    if ( client.find("\">"))
    {
      tagFlag = false;
      do
      {
        char inChar = client.read();
        if (inChar == '<')
        {
          tagFlag = true;
        }
        else
        {
          Serial.print(inChar);
          addChar(inChar, tmpStr);
        }
      } while (tagFlag == false);
    }
    Serial.println(tmpStr);
    Serial.println(F("FINE"));
  }
  else
  {
    Serial.println(F("Disconnected"));
  }

  client.stop();
  client.flush();
  Serial.println("Fine ciclo");
  delay(60000);

}


//Function to add a char to a string and check its length
void addChar (char ch, char* str) {
  char *tagMsg  = "<TRUNCATED_TAG>";
  char *dataMsg = "-TRUNCATED_DATA-";

  // Check the max size of the string to make sure it doesn't grow too
  // big.  If string is beyond MAX_STRING_LEN assume it is unimportant
  // and replace it with a warning message.
  if (strlen(str) > MAX_STRING_LEN - 2) {
    if (tagFlag) {
      clearStr(tagStr);
      strcpy(tagStr, tagMsg);
    }
    if (dataFlag) {
      clearStr(dataStr);
      strcpy(dataStr, dataMsg);
    }

    // Clear the temp buffer and flags to stop current processing
    clearStr(tmpStr);
    tagFlag = false;
    dataFlag = false;

  } else {
    // Add char to string
    str[strlen(str)] = ch;
  }
}


// Function to clear a string
void clearStr (char* str) {
  int len = strlen(str);
  for (int c = 0; c < len; c++) {
    str[c] = 0;
  }
}

Sto cercando di capire quali sono i dati che restituisce il file
http://www.ilmeteo.it/box/previsioni.php

ad esempio roma è 5913
http://www.ilmeteo.it/box/previsioni.php?citta=5913

catania è 1832
http://www.ilmeteo.it/box/previsioni.php?citta=1832

e di stamparli sul serial

analizzando il sorgente della pagina HTML col tasto destro o dal menu si tratta poi alla fine di prendere solo

Martedì 10 Sereno 15 25 NE 4 km/h
 5%

Quella pagina alla fine è tutto un contorno per visualizzarla sul pc, ma in realtà sarebbe sufficiente interrogare il database facendosi restituire una stringa con 4-5 dati separati da una virgola. Questo era il metodo alternativo, ora cerco qual'è la domanda giusta da fare .. io lo facevo col jquery

tipo

GM_xmlhttpRequest({
            method: "GET",          
            url:url_data, 
            onload: function(response) {

ora vedo se esiste un metodo semplice da tradurre in client.print
e capire quei numeri da dove li prende, perchè da qualche parte il javascript li pesca

dati tMin" 15 (temperat minima)
dati tMax" 25 (temperat massima)
dati">NE (direzione vento)
dati" 4 km/h (vento)
...
...

Il meteo.it fornisce anche le tabelle xml ma a pagamento, i dati sono descritti qui

La tabella che chiedo io nello sketch dovrebbe essere la più piccola che sono riuscito ad ottenere, le altre sono più grandi come dimensione della risposta.

Ci sono anche dei siti stranieri ma la loro affidabilità è minore.

Ma il punto è perché la ricezione si interrompe se cerco nello stream ricevuto mentre non si interrompe se leggo lo stream un carattere alla volta. Ho sospettato problemi di ram ma mi sarei aspettato un reset dell'Arduino che invece continua imperterrito.

Se leggi un char alla volta e lo stampi va bene, se concateni questi char dentro una stringa senza poterne definire un limite mandi in pappa il micro che ha una ram molto piccola.

Anche io pensavo questo ma il risultato dovrebbe essere un reset del micro. Inoltre non dovrebbe succedermi mai che una volta funziona e le altre no come mi è appena capitato.

Ho usato l'IDE 1.6.6 in versione portable in cui credevo di aver aggiornato tutte le librerie (mi chiede di aggiornare daccapo la Ethernet, ma non dovrebbe avere già l'ultima?), al primo giro mi ha stampato l'intera pagine html fino al tag finale, in seguito si interrompe sempre poco dopo la città. Ho quindi premuto il reset per vedere se il comportamento si ripeteva ma non si è ripetuto più.

Edit:
Ho un'altra ipotesi: la pagina è sicuramente richiesta in più pacchetti. Quello che succede è che una volta ricevuto il primo pacchetto gli altri vengono ignorati o addirittura neanche chiesti.
Mi servirebbe un hub ethernet per spiare le comunicazioni ma ormai sono introvabili.

Edit2:
Con il debug attivato dovresti vedere che la ricezione della pagina si ferma poco dopo la riga dove c'è la città.
Se c'è un errore nel parsing ricevi tutta la pagina ma poi non vengono stampati i dati ma questo è facilmente risolvibile.

Se hai un portatile puoi connettere l'Arduino alla Ethernet e usare il Wifi per la connessione a internet.
Con WireShark puoi controllare il traffico tra le due interfacce.

Buona idea! Grazie!
L'altra idea che ho avuto è di provare con un ESP8266. Però così non risolvo il mistero.

Ma perchè ti serve sniffare il traffico ? forse non ho capito cosa vuoi fare allora... non volevi ricavare temperature e dati vari da una pag web ?

Ho il sospetto che la scheda ethernet prenda solo il primo pacchetto e poi chiuda la connessione. Se sniffando il traffico mi ritrovo con tutta la pagina ho l'indizio che il problema è nel mio sketch o in qualche libreria. Se invece mi accorgo che viene scaricato solo il primo pacchetto allora il problema potrebbe stare nell'interazione tra le librerie e la scheda ethernet.

Il problema c'era anche con lo sketch di origine che usava i dati di Yahoo solo che li i dati che servivano erano tutti nel pezzo che viene scaricato comunque, quando ho provato ad accedere anche agli altri dati ho avuto lo stesso comportamento, ogni tanto funziona ma spessissimo no.

Mi dai quello che usi per Yahoo

Certo!

/*
 * Yahoo01
 * http://forum.arduino.cc/index.php?topic=121992.msg920175#msg920175
 * To get the city ID
 * Goto https://weather.yahoo.com and enter the city in the search bar
 * You get someting like this
 * https://weather.yahoo.com/italy/sicily/catania-713571/
 * The ID is 713571
 *
 * Original code by Webmeister
 * http://forum.arduino.cc/index.php?action=profile;u=15387
 * Read Yahoo Weather API XML
 * 03.09.2012
 * http://arduino-praxis.ch
 *
 * Modifications
 * 2015-10-14
 * Added DHCP by zoomx
 * Added Current Condition
 * http://forum.arduino.cc/index.php?topic=121992.msg933688#msg933688
 *
 */


#include <SPI.h>
#include <Ethernet.h>
#include "TextFinder.h"

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xAD };
byte ip[] = { 10, 0, 1, 101 };
byte gateway[] = { 10, 0, 1, 1 };
byte subnet[] = { 255, 255, 255, 0 };

// Server Yahoo
//IPAddress server(87, 248, 122, 181);
IPAddress server(217, 12, 1, 156);

EthernetClient client;
TextFinder  finder( client );

char place[50];
char hum[30];
char currentcond[50];

void setup()
{

  // Start Serial Port
  Serial.begin(9600);
  Serial.println("Yahoo01");
  Serial.println("Setup...");
  // Start Ehternet
  //Ethernet.begin(mac, ip);
  if (Ethernet.begin(mac) == 0) {
    Serial.println("Failed to configure Ethernet using DHCP");
    // no point in carrying on, so do nothing forevermore:
    for (;;)
      ;
  }
  // print your local IP address:
  Serial.print("My IP address: ");
  for (byte thisByte = 0; thisByte < 4; thisByte++) {
    // print the value of each byte of the IP address:
    Serial.print(Ethernet.localIP()[thisByte], DEC);
    Serial.print(".");
  }
  Serial.println();
}


void loop()
{
  if (client.connect(server, 80))
  {
    // Call Wetter-API
    // w: ID from your City
    // http://weather.yahooapis.com/forecastrss?w=713571&u=c
    ///
    Serial.println("Connect to Yahoo Weather...");
    client.println("GET /forecastrss?w=713571&u=c HTTP/1.0");
    client.println("HOST:weather.yahooapis.com\n\n");
    client.println();
    Serial.println("Connected...");
  }
  else
  {
    Serial.println(" connection failed");
  }


  if (client.connected())
  {

    // Humidity
    if ( (finder.getString("<yweather:atmosphere humidity=\"", "\"", hum, 4) != 0) )
    {
      Serial.print("Humidity:  ");
      Serial.println(hum);
    }
    else
    {
      Serial.print("No Humidity Data");
    }


    // Place/City
    if ( (finder.getString("<title>Conditions for ", " ", place, 50) != 0) )
    {
      Serial.print("City:  ");
      Serial.println(place);
    }

    // Current Condition
    //if ( (finder.getString("<b>Current Conditions:</b>
 ", "C
 ", currentcond, 50) != 0) )
    if ( (finder.getString("text=\"", "\"", currentcond, 50) != 0) )
      //                      <b>Current Conditions:</b>
    

      //    CHECK     THIS                                         ^^^^
    {
      Serial.print("Current Cond:  ");
      Serial.println(currentcond);
    }

    // Temperature
    if (finder.find("temp=") )
    {
      int temperature = finder.getValue();
      Serial.print("Temp C:  ");
      Serial.println(temperature);
    }
    else
    {
      Serial.print("No Temperature Data");
    }

    // Forecast doesn't work
    if ( (finder.getString("
<b>Forecast:</b>
","
", currentcond, 50) != 0) )
    {
      Serial.print("Forecast:  ");
      Serial.println(currentcond);
    }
    // END XML
  }
  else
  {
    Serial.println("Disconnected");
  }

  client.stop();
  client.flush();
  delay(60000);
}

Fa uso della stessa libreria TextFinder.
Ho messo l'IP di Yahoo ma credo che se uso yahoo.it dovrebbe funzionare lo stesso.
Anche qui non arriva alle Current Condition mentre stampa ciò che c'è prima.

a me non sembra si blocchi

Yahoo01
Setup...
My IP address: 192.168.2.107.

Connect to Yahoo Weather...
Connected...
Humidity: 88
City: Catania,
Current Cond: Clear
No Temperature Data

Connect to Yahoo Weather...
Connected...
Humidity: 88
City: Catania,
Current Cond: Clear
No Temperature Data

Connect to Yahoo Weather...
Connected...
Humidity: 88
City: Catania,
No Temperature Data

Ogni tanto manca il Current Cond mentre la temperatura manca sistematicamente. Però in quel periodo non avevo ancora scoperto la linea per abilitare il debug della libreria.
Se abiliti il debug ( porti il serial print a 115200, 9600 è troppo lento) puoi notare che anche qui il caricamento si interrompe nelle righe che riguardano il forecast poco dopo il current Cond. Non vedi mai il tag che segnala il fine pagina.

Ho giorni pienissimi e poco tempo, non ho tralasciato l'argomento, a giorni penso di riuscire a riprendere gli sketch

ciao

Non ti preoccupare anche io sono in condizioni simili altrimenti avrei già provato con il portatile.
Il mio sospetto si concentra sulla libreria stream.
GRAZIE! Del pensiero, almeno.

Secondo me è textfinder che incasina tutto quando si trova in una linea troppo lunga.

prova questo se crasha

#include <SPI.h>
#include <Ethernet.h>

byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(0,0,0,0);

//IPAddress server(173,194,40,24); //google
//IPAddress server(54,225,234,35); //arduino cc
//IPAddress server(104,16,14,15);  //jquery
IPAddress server(94, 32, 108, 10);   //IlMeteo 94.32.108.10
EthernetClient client;

void setup() {
  
  delay(1000);
  pinMode(4, OUTPUT);
  digitalWrite(4, 1);
  pinMode(10, OUTPUT);
  digitalWrite(10, 1);
  Serial.begin(115200);
  if (Ethernet.begin(mac) == 0){
    Serial.println("Failed to configure Ethernet using DHCP");
   Ethernet.begin(mac,ip);
}
 Serial.print(Ethernet.localIP());
   delay(100);
   
  if (client.connect(server, 80))   {
    Serial.println(" Connected!");
    client.println("GET http://www.ilmeteo.it/box/previsioni.php?citta=5913");      
    client.println("Connection: close");
    client.println();
  
  } 
    else  Serial.println(" Connection failed!");
  int n_righe=0;

  while (client.connected()) {
        if (client.available()) {
        char c = client.read();
        //Serial.write(c);// debug char di risposta
        
         //if (c == '\n') {
          //n_righe++;
          //Serial.print(n_righe); // degug per trovare le linee che servono
        //}
        
            if (n_righe > 68 &&  n_righe < 75) Serial.write(c);// debug char riga di risposta   
       }  
  } 
  
  client.stop();
    delay(1);
}

void loop(){
}

da come uscita su seriale questo

9 17 W 2 km/h
32% 

ora devi solo spulciare le linee

No, non credo sia textfinder. La seconda versione che ho postato usa direttamente stream come fa textfinder e succede esattamente la stessa cosa. Però non c'è il debug. Puoi anche provare a cercare altro nello stream ma semplicemente non lo trovi.
Se noti, dopo la stampa del pezzo html ricevuto e prima della stampa dei dati estratti c'è una pausa. Textstream aspetta dati e poi va in timeout, se allunghi il timeout si allunga la pausa. Ho provato ad aggiungere pause, pensavo che ci fossero ritardi nella ricezione dei dati ma non è cambiato nulla. Magari devo allungare di molto il timeout....

Però il tuo esempio mi ha dato un'indizio. Magari con un contatore posso saltare gran parte della pagina e processare il resto. Resta sempre una pezza ma forse è meglio di niente.