Pseudocódigo para Datalogger y envio de datos

Buenas tardes, estoy pensando en cual sería la forma más eficiente de realizar la tarea que tantas veces se trata en este foro sobre la recolección de datos de sensores, su almacenado en un fichero dentro de una SD y su posterior envio a través de algún canal de comunicación (BT, BLE, WIFI, GSM/GPRS, XBEE...etc).

Bien, la idea es bastante simple y además hay mucho código publicado. El protocolo a seguir sería algo así:

1.- Leer los sensores y obtener los datos.
2.- Abrir el fichero si existe o crearlo en caso contrario y escribir los datos en el.
3.- Cada cierto tiempo establecer una conexión, abrir el fichero, leer los datos disponibles desde el ultimo envio y enviarlos.

Lo que me gustaría debatir es cual sería la mejor manera de realizar el punto 3, teniendo presente que entre envio y envio se producen múltiples escrituras en el fichero (varias lecturas de sensores) y por supuesto solo debemos enviar los datos escritos en el fichero desde el ultimo envio.

He buscado información y he visto que mucha gente opta por crear un nuevo fichero en la SD con otro nombre, usando por ejemplo la fecha, aunque podría hacerse de muchas formas. Almacena el nombre del ultimo fichero creado y en el próximo envio solo enviara los datos de este ultimo fichero, volviendo a crear otro nuevo tras el envio y guardando los siguientes datos en el. Esta parece una buena solución, por ponerle un pero sería que tendriamos un fichero por cada envio que hagamos, que pueden ser varios al día.

Otro posible método que se me ocurre sería, como todos los datos almacenados en el fichero llevan su timeStamp, podemos almacenar el timestamp de la ultima fila enviada y comenzar en el proximo envio a partir de ella. No me parece mala idea crear otro fichero "envios.txt" en el que creamos una fila por cada envio, indicando datos como fecha del envio, si fue correcto y el timestamp de la ultima fila enviada. En el siguiente envio, recuperamos ese dato y enviamos lo que tengamos a partir de el.

A parte de estos dos "métodos" a ver si alguién quiere aportar más ideas, espero haberme explicado bien.

Saludos y gracias.

Puedes separarlos en varios archivos si quieres, pero permíteme decirte que eso es posible aún en un solo archivo. Almacenando el último "puntero" donde acabó la última transmisión; así, si el tamaño del archivo es mayor a la posición de ese puntero, es porque hay datos nuevos.

Haces la lectura, pero antes de cerrar el archivo has lo siguiente:

ultimoPuntero = archivo.position(); // ultimoPuntero es de tipo unsigned long

Ahora, para saber si hay datos nuevos:

if (ultimoPuntero < archivo.size())

Lucario448:
Puedes separarlos en varios archivos si quieres, pero permíteme decirte que eso es posible aún en un solo archivo. Almacenando el último "puntero" donde acabó la última transmisión; así, si el tamaño del archivo es mayor a la posición de ese puntero, es porque hay datos nuevos.

Haces la lectura, pero antes de cerrar el archivo has lo siguiente:

ultimoPuntero = archivo.position(); // ultimoPuntero es de tipo unsigned long

Ahora, para saber si hay datos nuevos:

if (ultimoPuntero < archivo.size())

Me parece una opción muy elegante la que propones, es similar a la que propongo de almacenar el ultimo timestamp del ultimo registro enviado y la próxima vez comenzar a enviar a partir de éste, pero usando un puntero a la ultima posición del fichero. En el siguiente envio (se debe cumplir la condición de que ultimo puntero sea inferior al tamaño del fichero, pues se realizan escrituras a menudo) y se empieza a enviar a partir de la posición almacenada en ultimoPuntero.

Alguna propuesta más...

alfredomrh:
Alguna propuesta más...

En realidad no diré nada novedoso, sino detallar una de tus ideas.

Cuando un archivo se transmite, se activa una bandera booleana para que el próximo muestreo se vaya a un archivo nuevo en vez de seguir con el mismo. Para automatizar el nombramiento, se puede seguir la siguente rutina:

char nombre[13]; //Global
unsigned int nArchivo = 0; // Otra global

void nuevoNombre() {
  sprintf(nombre, "LOG%04d.TXT", nArchivo++);
  if (SD.exists(nombre)) SD.remove(nombre);
  if (nArchivo == 10000) nArchivo = 0;
}

Gracias por tus aportes...podrías detallar como empezaría a enviar los datos dentro de este if.

Ahora, para saber si hay datos nuevos:

if (ultimoPuntero < archivo.size())

[/quote]

Supongo que sería al así...

 if (archivo) {

          while (archivo.available()) {

                  if(archivo.position() > ultimoPuntero) Serial.write(dataFile.read()); //solo leería los nuevos 
                                                                                                                   //registros

          }

          ultimoPuntero = archivo.position(); //volvemos a almacenar la ultima posicion
          dataFile.close();

         }

Saludos,

Para ir a transmitir información nueva, más sencillo sería:

// Se abre el archivo para sólo lectura
if (archivo) {

  if (ultimoPuntero < archivo.size()) {

    archivo.seek(ultimoPuntero); // Volver a colcocar el puntero en la última posición leída
    while(archivo.available()) Serial.write(archivo.read());
    ultimoPuntero = archivo.position();
  } else {

    // Qué hacer en caso de que no haya información nueva
  }
  archivo.close(); // Importante nunca olvidar esto
} else {

  // Qué hacer en caso de fallar la apertura.
}

Lucario448:
Para ir a transmitir información nueva, más sencillo sería:

// Se abre el archivo para sólo lectura

if (archivo) {

if (ultimoPuntero < archivo.size()) {

archivo.seek(ultimoPuntero); // Volver a colcocar el puntero en la última posición leída
    while(archivo.available()) Serial.write(archivo.read());
    ultimoPuntero = archivo.position();
  } else {

// Qué hacer en caso de que no haya información nueva
  }
  archivo.close(); // Importante nunca olvidar esto
} else {

// Qué hacer en caso de fallar la apertura.
}

Perfecto, con archivo.seek() situas el puntero de archivo en la ultima posicion enviada y si hay información disponible, la lees.

Para rizar un poco más el rizo y quedar este post lo más didáctico posible, he pensado que podría haber problemas si el arduino se queda bloqueado en algún momento, ya que esto en un sistema que va a estar durante mucho tiempo trabajando puede pasar, se soluciona añadiendo un watchdog al código, para que si se queda congelado se resete.

En dicho caso perderiamos el contenido de la variable ultimoPuntero, con lo que tendríamos un problema, para enviar la nueva información obtenida. Esto lo podríamos solventar guardando su contenido en una memoria no volátil lo que es posible con nuestro arduino. Pero yo me inclino por otra solución ya que tenemos un datalogger y sería crear otro fichero por ejemplo "envios.log" en el cual vamos almacenando un registro por cada envio, con su timestamp, resultado, y tambien el valor de ultimoPuntero. Cuando vayamos a realizar el siguiente envio, abrimos primero este fichero y obtenemos el valor de ultimoPuntero del ultimoregistro guardado y así no tendremos problemas en caso de reset, además de llevar un registro de envios que nos puede venir bien para otros menesteres.

A ver a Lucario448 que le parece esto que propongo...

Y para resumir el post, podemos decir que solo hay dos propuestas para realizar el envio de información no redundante:

1.- Generar un nuevo fichero tras cada envio, en el que almacenamos la información nueva obtenida.
2.- Almacenar la información en un único fichero, pero ir almacenando la posición del último registro enviado, para en el siguiente envio, continuar enviado la información que haya a partir de la posición almacenada en este puntero.

Para más información aqui está la información de referencia de la librería SD con las funciones utilizadas.

Si alguién quiere proponer un nuevo método, estaría muy bien discutirlo y añadirlo a este post.

Saludos,

alfredomrh:
Pero yo me inclino por otra solución ya que tenemos un datalogger y sería crear otro fichero por ejemplo "envios.log" en el cual vamos almacenando un registro por cada envio, con su timestamp, resultado, y tambien el valor de ultimoPuntero. Cuando vayamos a realizar el siguiente envio, abrimos primero este fichero y obtenemos el valor de ultimoPuntero del ultimoregistro guardado y así no tendremos problemas en caso de reset, además de llevar un registro de envios que nos puede venir bien para otros menesteres.

Si quieres agregar información extra allá tú, pero déjame decirte que lo mínimo que se necesita para almacenar el último puntero son nada más y nada menos que 4 bytes/caracteres.

El agregar información adicional no está mal, el problema es después buscar una rutina eficiente que encuentre dónde exactamente se encuentra el dato del último puntero. Claro, esto no sería problema si los registros fueran todos de longitud (tamaño) constante.

Recuerda que la librería en realidad maneja archivos en forma binaria; que tenga la capacidad de imprimir y recuperar texto es otra historia. En efecto, la librería visualiza un archivo como un vector ("array" en inglés) de bytes con posibilidad de crecer; ¿si no de dónde crees que salió el concepto de "puntero del archivo"?.
Al verlo de esta manera, es lo que lo hace para nada flexible con los registros de longitud variable (algo que muy frecuentemente ocurre al grabar registros textualizados).

Entiendo lo que dices, en principio a mi me parece buena idea guardar un fichero aparte del de los datos obtenidos de los sensores con información sobre los envios, simplemente para llevar un registro de estos (aunque esto seguramente también lo podemos obtener del lado del servidor), ya que actualmente nos suele sobrar un 99% de la capacidad de las tarjetas SD que usamos en los datalogger y siempre que el tiempo para grabar estos datos no sea un problema en nuestro programa y pienso que en un datalogger no suele serlo.

Por otra parte lo que remarcas sobre la dificultad de recuperar el ultimoPuntero almacenado en ese fichero, ya que file.Read() fileRead(buf, len) solo trabajan con bytes, es muy importante tenerlo en cuenta. Entonces hay que optar por que los registros en ese fichero de envios.log sean de longitud fija, para que la rutina de recuperación sea simple y eficiente. De todas formas hay que tener en cuenta que esa rutina solo debe ejecutarse tras un reset del sistema.

Yo pienso que el código de un datalogger no está completo si no implementa un watchdog por si el sistema se bloquea en algún momento, de ahi mi preocupación por almacenar el puntero a partir del cual está la nueva información en algún lugar no volátil.

Entonces estos no lleva a optar por otra solución que en principio debería ser más simple y es almacenar los 4 bytes (unsigned long) de ultimoPuntero en la eeprom de nuestro arduino, tenemos hasta un 1Kb disponible en esta memoria no volátil.

Para ello...

#include <EEPROM.h>

//esto guardará la variable en las posiciones 0,1,2 y 3 de nuestra Eeprom 
for(i=0; i < 4; i++)  EEPROM.put( i, ultimoPuntero );  //Grabamos el valor

Aqui hay algún ejemplo de como realizar esto o aqui.

alfredomrh:
y siempre que el tiempo para grabar estos datos no sea un problema en nuestro programa y pienso que en un datalogger no suele serlo.

No debería siempre y cuando no se tenga que hacer en una fracción de segundo.

alfredomrh:
estos no lleva a optar por otra solución que en principio debería ser más simple y es almacenar los 4 bytes (unsigned long) de ultimoPuntero en la eeprom de nuestro arduino, tenemos hasta un 1Kb disponible en esta memoria no volátil.

Solo por decirlo: puedes replicar ese comportamiento en un archivo aparte también, solo que colócale otra extensión que de por sí los datos binarios no son directamente legibles para un ser humano

alfredomrh:

#include <EEPROM.h>

//esto guardará la variable en las posiciones 0,1,2 y 3 de nuestra Eeprom
for(i=0; i < 4; i++)  EEPROM.put( i, ultimoPuntero );  //Grabamos el valor

Corrección: basta con

EEPROM.put( i, ultimoPuntero );  //Grabamos el valor

Porque put y get automáticamente reconocen la cantidad de bytes necesaria para "serializar" o reconstruir una variable u objeto de cualquier tipo.

Entonces, para ponerlo en claro, haríamos algo así:

1.- Cargaría sobre mi placa es siguiente clear eeprom para limpiar toda la eeprom, realmente poner todos los bytes a 0.

2.- Después ya cargaría el sketch con el código de mi datalogger.

En el setup de mi sketch:

 int eeAddress = 0; //direccion donde siempre almaceno el puntero

void setup() {

  float ultimoPunteroEeprom = 0.00f;   //Variable  donde almaceno el puntero

  EEPROM.get(eeAddress, ultimoPunteroEeprom); //consigo el valor almacenado en la eeprom

  //Si el valor leido de la eeprom no es cero, es por que no es la primera vez que se ejecuta el programa
  //y estamos aqui fruto de un reset, por tanto vuelvo a asignar a mi puntero el valor que tenia 

  if(!ultimoPunteroEeprom == 0.00) ultimoPuntero = ultimoPunteroEeprom;

}

void loop(){

       //despues de cada envio y obtención de la nueva posicion con ultimoPuntero = file.position();

         EEPROM.update(eeaddress, ultimoPuntero); //actualizo su valor en la eeprom

}

Habría que tener la precaución si queremos inciar el programa otra vez desde el principio, volver a ejecutar el sketch que limpia la eeprom.

Lucario448 gracias por tus aportes, a ver si esto es correcto y si no hay ninguna aportación nueva, vamos cerrando el hilo. Pienso que ha quedado bastante claro y didáctico.

Saludos,

void setup() {

  float ultimoPunteroEeprom = 0.00f;   //Variable  donde almaceno el puntero

  EEPROM.get(eeAddress, ultimoPunteroEeprom); //consigo el valor almacenado en la eeprom

  //Si el valor leido de la eeprom no es cero, es por que no es la primera vez que se ejecuta el programa
  //y estamos aqui fruto de un reset, por tanto vuelvo a asignar a mi puntero el valor que tenia 

  if(!ultimoPunteroEeprom == 0.00) ultimoPuntero = ultimoPunteroEeprom;

}

Este paso es redundante; si de la EEPROM se recupera cero, es porque nunca hubieron transmisiones.

EEPROM.update(eeaddress, ultimoPuntero); //actualizo su valor en la eeprom

Otro error, update sigue siendo para un único byte. Para estructuras de múltiples bytes se utiliza put.

Lucario448:

void setup() {

float ultimoPunteroEeprom = 0.00f;  //Variable  donde almaceno el puntero

EEPROM.get(eeAddress, ultimoPunteroEeprom); //consigo el valor almacenado en la eeprom

//Si el valor leido de la eeprom no es cero, es por que no es la primera vez que se ejecuta el programa
  //y estamos aqui fruto de un reset, por tanto vuelvo a asignar a mi puntero el valor que tenia

if(!ultimoPunteroEeprom == 0.00) ultimoPuntero = ultimoPunteroEeprom;

}



Este paso es redundante; si de la EEPROM se recupera cero, es porque nunca hubieron transmisiones.

No se a que te refieres, entiendo que debe leer la eeprom para poder comprobar si es cero o no.

Y en relación al update, tomo nota...hay que meterlo en un bucle para comprobar los 4 bytes.

alfredomrh:
No se a que te refieres, entiendo que debe leer la eeprom para poder comprobar si es cero o no.

Primero: es redundante utilizar float para eso. Si es cero, es cero en cualquier tipo de dato numérico del mismo tamaño (entero o no).

Segundo: luego verificas si el valor recuperado es cero o no. ¿Para qué? Si recupera cero es por ser la primera vez o borrarse la EEPROM, ¿con qué necesidad verificarlo?

Al punto que quiero llegar es: no tienes que verificar si el valor recuperado es cero o no, solo lo recuperas y punto (pasa directo al get sin rodeos); luego la lógica del programa por sí sola manejará la situación.

alfredomrh:
Y en relación al update, tomo nota...hay que meterlo en un bucle para comprobar los 4 bytes.

No es necesario, put hace update a cuanto byte haya que colocar.

Lucario448:
Primero: es redundante utilizar float para eso. Si es cero, es cero en cualquier tipo de dato numérico del mismo tamaño (entero o no).

Segundo: luego verificas si el valor recuperado es cero o no. ¿Para qué? Si recupera cero es por ser la primera vez o borrarse la EEPROM, ¿con qué necesidad verificarlo?

Tienes razón, yo estaba pensado en utilizar dos variables ultimoPunteroEeprom y ultimoPuntero, de ahí el if, para hacer la asignación o no hacerla, pero como bien dices si utilizo un get en el setup unicamente con ultimoPuntero que es la variable que utilizo en el resto del programa, me ahorro esa comparación.

Lucario448:
Al punto que quiero llegar es: no tienes que verificar si el valor recuperado es cero o no, solo lo recuperas y punto (pasa directo al get sin rodeos); luego la lógica del programa por sí sola manejará la situación.

No es necesario, put hace update a cuanto byte haya que colocar.

Utilizar el update en lugar del get, es simplemente pues la librería lo recomienda para aumentar la vida de la eeprom.

Saludos,

alfredomrh:
Utilizar el update en lugar del get, es simplemente pues la librería lo recomienda para aumentar la vida de la eeprom.

A ver cómo te explico:

Note

This function uses EEPROM.update() to perform the write, so does not rewrites the value if it didn't change.

Lucario448:
A ver cómo te explico:

Ok, Put ya usa update...estaba pensando en Write...sorry.