Tutoriales
Clima actual con Raspberry Pi y LCD Nokia 5110
- Detalles
- Categoría: Tutorial
- Publicado el Domingo, 22 Septiembre 2013 11:44
- Escrito por Santiago Villafuerte
- Visto: 22570
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.
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.