Tutoriales

Clima actual con Raspberry Pi y LCD Nokia 5110

En este tutorial podrás usar tu Raspberry Pi para leer el clima actual de tu localidad a través de una conexión a internet y lo podrás mostrar en tu pantalla LCD Nokia 5110 (PCD8544). Se dará una breve explicación de cómo echar a andar un thread (hilo) y también cómo usar mutexes para evitar que ambos hilos del programa estén haciendo uso de una variable (compartir memoria). También se dará una explicación sobre cómo dejar corriendo una aplicación en tu Raspberry Pi sin tener que estar conectado a ella a través de SSH con el programa 'screen'. Espero te sea de utilidad.

 


Items necesarios

 

+ Raspberry Pi correctamente funcional con Raspbian (checa un tutorial anterior)
+ Pantalla LCD Nokia 5110 (click para link de venta)
+ Conexión a internet (Wifi o por Ethernet)

 

Software necesario

+ Cross compiler ARM sobre Linux para PC (link con tutorial en Hertville)
+ Paquete con código fuente Weather LCD (link de descarga)

 

Obteniendo datos del clima por internet

El paquete weather-util en Linux te permite descargar las condiciones actuales y futuras del clima en tu localidad a través de tu conexión a internet. Primero necesitas instalarlo en tu Raspberry Pi. Ejecuta los siguientes comandos en tu Pi...

$ sudo apt-get update

$ sudo apt-get install weather-util

Al instalarlo se descargarán varios scripts en Python que harán una conexión al servidor http://weather.noaa.gov/ y bajarán la información actual de tu localidad. Para probar que funciona deberás entrar a la página web indicada y obtener el ID de tu localidad. La ID la obtendrás eligiendo tu país y luego una ciudad. En mi caso, siendo México en la ciudad de Querétaro obtuve la ID MMQT, indicada en el recuadro rojo.

weather Queretaro

Una vez conociendo tu ID, introduce el comando siguiente en la consola. La aplicación te devolverá varias líneas de información. En vez de MMQT coloca tu propia ID.

pi@pisanlink:~$ weather --id=MMQT
WARNING: the --id option is deprecated and will eventually be removed
Searching via station...
[caching result Queretaro, Qro., Mexico]
Current conditions at Queretaro, Qro.
Last updated Sep 22, 2013 - 12:45 PM EDT / 2013.09.22 1645 UTC
   Temperature: 68 F (20 C)
   Relative Humidity: 77%
   Wind: from the ENE (070 degrees) at 6 MPH (5 KT)
   Sky conditions: mostly cloudy

 



Creando la aplicación en la Pi

Necesitaremos una aplicación que invoque el script de weather y que procese el texto que devuelve para mostrarlo en la LCD. El contenido de la función principal es el siguiente.

00000023 int main(int argc, char **argv)

00000024 {

00000025    char string[100];

00000026    Weather_Data_T weather;

00000027    Weather_Data_T weather_to_print;

00000028

00000029    pthread_t weather_thread;

00000030

00000031    printf("Weather Stats - Raspberry Pi\n");

00000032

00000033    if(VALID_ID != Weather_Validate_ID(argv[1]))

00000034    {

00000035       printf("Visit http://weather.noaa.gov/ and get a valid location ID\n");

00000036       exit(-1);

00000037    }

00000038    strcpy(Weather_ID, argv[1]);

00000039

00000040    pthread_mutex_lock(&Weather_Mutex);

00000041    sprintf(weather.location, "n/a");

00000042    sprintf(weather.temperature, "n/a");

00000043    sprintf(weather.humidity, "n/a");

00000044    sprintf(weather.sky, "n/a");

00000045    sprintf(weather.weather, "n/a");

00000046    sprintf(weather.wind, "n/a");

00000047    pthread_mutex_unlock(&Weather_Mutex);

00000048

00000049    Nokia_5110_Init();

00000050

00000051    /* Start the weather thread, pass the weather ID as well */

00000052    pthread_create(&weather_thread, NULL, Weather_Thread, (void*)&weather);

00000053 

00000054    while(1)

00000055    {

00000056       /* Copy the weather data into the printable copy */

00000057       pthread_mutex_lock(&Weather_Mutex);

00000058       weather_to_print = weather;

00000059       pthread_mutex_unlock(&Weather_Mutex);

00000060

00000061       Nokia_5110_Set_Cursor(0, 0);

00000062       Nokia_5110_Putstring(weather_to_print.location);

00000063

00000064       Nokia_5110_Set_Cursor(0, 1);

00000065       sprintf(string, "%s %s Humid", weather_to_print.temperature, weather_to_print.humidity);

00000066       Nokia_5110_Putstring(string);

00000067

00000068       Nokia_5110_Set_Cursor(0, 2);

00000069       sprintf(string, "Sky %s", weather_to_print.sky);

00000070       Nokia_5110_Putstring(string);

00000071

00000072       Nokia_5110_Set_Cursor(0, 3);

00000073       Nokia_5110_Putstring(weather_to_print.weather);

00000074

00000075       Nokia_5110_Set_Cursor(0, 4);

00000076       Nokia_5110_Putstring(weather_to_print.wind);

00000077

00000078       time_t t = time((void*)0);

00000079       struct tm tm = *localtime(&t);

00000080

00000081       Nokia_5110_Set_Cursor(0, 5);

00000082       sprintf(string, "%02d:%02d:%02d %02d%s",

00000083       tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_mday, &Month_Text[tm.tm_mon][0]);

00000084       Nokia_5110_Putstring(string);

00000085

00000086       bcm2835_delay(500);

00000087    }

00000088

00000089    Nokia_5110_Stop_SPI();

00000090

00000091    return 0;

00000092 }

 

 

Inicialmente la aplicación tendrá que recibir el ID para después pasarlo al script weather. Llegará a través del arreglo **argv en la posición 1. Se hace una evaluación sencilla para ver que el ID tenga 4 dígitos.

Weather_Validate_ID(argv[1])

Una vez validado, empezaremos a usar la variable Weather_Data_T weather. En esta variable se estarán depositando los textos que el script weather devuelva y la funcion main los tomará y mostrará en la LCD. Los datos en Weather_Data_T weather serán escritos cada cierto tiempo a través de un hilo que estará corriendo una y otra vez el script weather. Los datos cambiarán en cualquier momento y la aplicación main tiene que ser cuidadosa y no leer datos mientras el script está escribiendo la variable.

Para solucionar esto, se agregó un mutex, que permite que "algo" se modifique o lea por un solo hilo o función. Si el script está modificando los datos, sólo él podrá hacerlo y los demás no podrán leer o modificar los datos mientras el mutex esté activo. Esperarán hasta que el dueño actual libere el recurso.

Para poder usarlo, simplemente hay que usar la librería #include <pthread.h>. Crear una variable mutex pthread_mutex_t Weather_Mutex = PTHREAD_MUTEX_INITIALIZER y después asegurarse de que nadie esté usando nuestro recurso:

00000040 pthread_mutex_lock(&Weather_Mutex);

Si está en uso, la función se quedará esperando hasta que sea liberado. Si no está en uso, la función podrá modificar el recurso inmediatamente.

Una vez que se dejó de usar el recurso, se debe liberar.

00000047 pthread_mutex_unlock(&Weather_Mutex);



Echando a andar el hilo del script weather

Una vez inicializada la variable podremos arrancar un segundo hilo en nuestra aplicación. Un hilo es otra secuencia de ejecución de nuestro programa que corre por su cuenta. El hilo que crearemos se encargará de correr el script weather constantemente para tener los datos del clima actualizados cada cierto tiempo. La función main (hilo 1) se encargará de refrescar datos en la LCD cada que el hilo del script (hilo 2) obtenga datos del clima.

Para arrancar el segundo hilo es necesario contar con una función con un prototipo algo raro... una función que devuelve un apuntador tipo void* pero... que no lo devuelve al terminar.

void* Weather_Thread(void* data)

{

   while(1)

   {

      Weather_Get_Stats((Weather_Data_T*)data, Weather_ID);

      usleep(15000000);

   }

}

 

Esa función será con la que arrancará nuestro hilo 2. Ejecutará Weather_Get_Stats() y después dormirá 15 segundos, haciendo esto una y otra vez. Weather_Get_Stats() se encargará de actualizar los datos del clima.

 

Obteniendo datos del clima

Ya habíamos visto que el script weather devuelve varias líneas de texto con datos del clima. Nuestra aplicación se encargará de ejecutar el script y capturar esas líneas de texto, extrayendo los datos que nos interesan. Para poder capturar las líneas se usará la función popen() que puede ejecutar un programa y toda salida de texto que éste arroje podrá ser redirigida hacia nosotros.

   fp = popen(command, "r");

   if (fp == NULL)

   {

      printf("Failed to run \"%s\"\nTry with \"%s\"\n",

            command, WEATHER_INSTALL_COMMAND);

      return;

   }

 

El script lo llamaremos a través de la cadena command, la cual se armará con los siguientes defines:

#define WEATHER_COMMAND_P1 "weather --id="

#define WEATHER_COMMAND_P2 " -m --no-cache" 

Intercalando el ID que se haya proporcionado al ejecutar nuestra aplicación, que en mi caso es MMQT. El string resultante sería:

"weather --id=MMQT -m --no-cache"

La -m simplemente pide que los datos devueltos del clima vengan dados en unidades métricas.

Una vez que el script está corriendo, podremos atrapar el texto con la siguiente función...

fgets(text, sizeof(text)-1, fp) != NULL

Mientras fgets obtenga cadenas con datos, el script weather estará devolviendo líneas de texto. Cuando termine el script, fgets devolverá un NULL.

Ya teniendo las líneas, bastará con buscar patrones de texto con los que sabremos que el script nos está reportando la temperatura, velocidad del viento, estado del cielo, humedad relativa, y la ubicación que pedimos.

Simplemente ejecutamos strstr() que se encarga de buscar un string dentro de otro string. Si lo encuentra nos devuelve el apuntador, si no, nos da un NULL.

Veamos el caso de la búsqueda de la localidad, buscaremos la siguiente info en cada línea:

#define WEATHER_CITY_TEXT "Current conditions at "

      /* Get the city */

      found_str = strstr(text, WEATHER_CITY_TEXT);

      if(NULL != found_str)

      {

         pthread_mutex_lock(&Weather_Mutex);

         strncpy(weather_data->location,

               &text[strlen(WEATHER_CITY_TEXT)], WEATHER_TEXT_LENGTH);

         Weather_Clean_Newline(weather_data->location);

         pthread_mutex_unlock(&Weather_Mutex);

      }

 

Si la encuentra, entonces levantamos un mutex para apropiarnos de la variable weather para que el hilo 1 del main no la use mientras la modificamos. Se deposita la localidad en el string y bajamos el mutex.

Se usa Weather_Clean_Newline() ya que el texto trae enter y regreso de carro, que no nos sirven.

De esta forma, la variable weather siempre tendrá información actualizada gracias al script que está corriendo constantemente.

 



Mostrando el clima en la pantalla LCD Nokia 5110

El hilo 1 que ejecuta el main entrará en un ciclo while(1)que estará mostrando cada medio segundo la info en pantalla, incluyendo la hora actual y el día. Se estará leyendo la variable weather y para evitar leer datos mientras el script weather los actualiza, se usó el mutex como en el caso anterior.

00000056       /* Copy the weather data into the printable copy */

00000057       pthread_mutex_lock(&Weather_Mutex);

00000058       weather_to_print = weather;

00000059       pthread_mutex_unlock(&Weather_Mutex);

 

Y listo, con eso tendremos nuestro monitor de clima actual en la Raspberry Pi.

Conexiones de la LCD

Tal y como la conectamos en el anterior tutorial, las conexiones son las siguientes...

 

He notado que el script de weather consume muchos recursos de la Pi, desconozco la razón. Si tienes pensado ejecutar otras aplicaciones en paralelo (en mi caso VLC con Internet Radio), considera que puede que les baje su rendimiento de vez en cuando.

 

 

Ejecución constante del programa con screen

Talvez te preguntes cómo puedes dejar corriendo la aplicación del clima aun cuando ya te hayas desconectado de la Pi a través de SSH. Yo uso el programa screen. Te permite crear sesiones SSH y las mantiene vivas aun cuando ya te hayas desconectado. Para instalar el software basta con que corras...

$ sudo apt-get install screen

Una vez instalado córrelo con permisos de root

$ sudo screen

Y luego ejecuta la aplicación del clima...

$ sudo ./weather_lcd MMQT

La aplicación correrá normalmente. Presiona Ctrl + Shift + D y el software screen se desconectará de la sesión, pero seguirá corriendo la aplicación del clima. Podrás irte y tu aplicación seguirá corriendo aunque no haya ningún usuario conectado a la Pi.

Espero que este tutorial te sirva. Envíame tus comentarios a través de la sección de contacto.