Cómo hice una estación meteorológica Arduino completamente funcional

Publicado: 2022-03-11

ACTUALIZACIÓN: El trabajo en nuestra estación meteorológica Arduino continuó después de la publicación de este artículo, y culminó con el lanzamiento de Open Weather Station (OWS). Compruébelo para obtener actualizaciones adicionales, recursos, junto con código y nuevos tutoriales.

¿Qué es todo esto?

El kitesurf es uno de los deportes más adictivos del mundo. Todo lo que requiere es una tabla de kitesurf, un cuerpo de agua y algunos accesorios. Es una excelente manera de ponerse en contacto con la naturaleza, liberar la mente y hacer ejercicio. Además, realmente puedes volverte loco con él.

¿Entonces, cuál es el problema?

Ah, se me olvidaba un requisito imprescindible: viento. Y ahí es donde tenemos nuestro problema: nunca sabes si habrá o no viento a menos que vivas justo al lado de tu lugar favorito para practicar kitesurf.

Vivo en Córdoba, Argentina, aproximadamente a 130 kilómetros (~80 millas) del lago donde practico kitesurf. Eso es aproximadamente un viaje de dos horas, con lo que puedo lidiar. Pero no puedo lidiar con el hecho de que los pronósticos del tiempo son inexactos. Y donde vivo, las buenas condiciones de viento duran solo un par de horas. Lo último que quieres hacer es aclarar tu agenda del lunes para practicar kitesurf y encontrarte maldiciendo a los dioses en un lago sin viento después de dos horas de conducción.

Necesitaba conocer las condiciones del viento de mi lugar favorito para practicar kitesurf, en tiempo real. Así que decidí construir mi propia estación meteorológica.

Medición del tiempo en tiempo real, en un entorno hostil

El objetivo era entregar datos meteorológicos en tiempo real al navegador en casa:

Proceso general para la estación meteorológica arduino

Antes de entrar en detalles, tomemos un momento para considerar las preguntas clave y las advertencias involucradas en un proyecto como este:

  • ¿Cómo puedo crear una estación meteorológica que no sea valiosa ni atractiva para un ladrón?
  • ¿Cómo puedo mantener al mínimo los costos de hardware y el tiempo de desarrollo?
  • ¿Cómo puedo medir y acceder a los datos meteorológicos en tiempo real y mostrarlos de forma útil?
    • Medidas requeridas: viento y ráfagas de viento, dirección del viento, lluvia, presión atmosférica, temperatura, humedad
    • Conectar la estación a Internet
    • Almacene y recupere datos meteorológicos locales
    • Comunicación entre la estación meteorológica y el servidor
  • ¿Cómo puedo reducir el mantenimiento a (casi) cero?
    • Administrar la suspensión del software
    • Gestionar la pérdida de conectividad
    • Gestionar la pérdida de suministro de energía

¡Saluda a mi amiguito!

expediente

Puede pensar que el guante está ahí para hacer que la estación parezca más amigable; pero en realidad se usa para probar el sensor barométrico (la presión del guante aumenta dentro del guante inflado). A la derecha, puede ver la estación en su ubicación final, en lo alto de una torre cercana.

También diseñé y programé un sitio web sobre kitesurf, que incluye un gráfico en tiempo real de las medidas de la estación para ayudar a la comunidad de kitesurf. Finalmente, creé un grupo de kitesurf en Facebook.

expediente

¡Eso es genial! Entonces, ¿cómo lo hiciste?

Bueno, abordaré cada punto por turno:

“¿Cómo puedo crear una estación meteorológica que no sea valiosa ni atractiva para un ladrón?”

Este fue un factor crítico y, en muchos sentidos, impulsó el resto del proceso de diseño. La mayoría de las estaciones prefabricadas por debajo de la línea de $ 2000 requerían una conexión USB a una computadora. Si un ladrón reconociera que la estación tenía una PC al lado, ese sería el final de las cosas, ya que el costo de reemplazar la computadora y la estación estaría por encima de mi presupuesto personal. Por lo tanto, decidí probar varias plataformas de hardware para implementar la estación desde cero, a un menor costo.

“¿Cómo puedo mantener los costos de hardware y el tiempo de desarrollo al mínimo?”

Solo yo financiaba los costos de este proyecto paralelo y hacía todo el trabajo en mi tiempo libre, por lo que, por supuesto, esto era una gran preocupación. Comencé con el popular PIC32 y algunos módulos Ethernet de microchip preensamblados, pero los costos no eran tan bajos como esperaba y había demasiados gastos generales involucrados en el ensamblaje y la extensión del hardware. Luego, comencé a buscar Arduino: un hardware y software de código abierto para la creación de prototipos electrónicos utilizando el lenguaje C. Esto era exactamente lo que quería y pude comprar módulos en DealeXtreme. Pude empezar a jugar con solo $15 de gastos y dos días de mi tiempo.

Por supuesto, el Arduino también tiene sus limitaciones: solo 2KBytes de RAM y 32Kbytes para mi software compilado, eso no deja mucho espacio para cadenas sofisticadas o variables inútiles 1 .

“¿Cómo puedo medir y acceder a los datos meteorológicos en tiempo real y mostrarlos de forma útil?”

Actualmente, mi estación puede medir: velocidad del viento, ráfagas de viento, dirección del viento, temperatura, humedad, lluvia y presión atmosférica. La temperatura, la humedad y la presión son manejadas por un par de bibliotecas, lo que hizo la vida mucho más fácil.

Medir la velocidad del viento y la lluvia fue un poco complicado. Los sensores funcionan abriendo y cerrando un interruptor (interruptor de láminas). Por lo tanto, necesitaba implementar interrupciones de hardware para capturar el sensor tan pronto como activa la entrada. Es decir, necesitaba llamar a algún método:

 attachInterrupt(RAINGAUGE_PIN, countRainCycles, FALLING);

Esta interrupción rompería la ejecución normal del código y llamaría a la función countAnemometerCycles o countRainCycles tan pronto como el interruptor experimente un flanco descendente, producido al cerrar o abrir el circuito. Algunas variables se incrementan en cada disparador del interruptor. (Más tarde, sopesa estas variables para tener en cuenta las conversiones de unidades).

 void countRainCycles() { rainCyclesCounter++; // This is easy! And it actually works. }

¡Pero no tan rápido! Este proceso genera cientos de disparadores falsos como resultado del efecto de rebote del interruptor inherente a cualquier interruptor de hardware. Afortunadamente, existen soluciones tanto de hardware como de software para este problema.

Sobre el efecto rebote

El efecto de rebote se produce como consecuencia de que el interruptor abre o cierra físicamente sus 'contactos', que establecen contacto con el resto del circuito. Cuando los contactos comienzan a separarse (abrir el interruptor) o unirse (cerrar el interruptor), se pueden generar algunos pequeños arcos eléctricos, así como una elasticidad mecánica en el circuito que activará y apagará el circuito durante un par de milisegundos. Cuando acciona un interruptor de luz, este efecto no es aparente; pero cuando agrega una interrupción al borde descendente de una señal, este efecto de rebote desencadena una tonelada de interrupciones. Más aquí.

rebote del circuito

Implementé un circuito antirrebote de hardware y una versión similar en el software. Pero, ¿cómo se implementa exactamente un rebote de software? ¡Fácil! Después de que ocurra el primer desencadenante esperado, "espere" el tiempo suficiente para que el rebote se estabilice antes de comenzar a escuchar nuevas interrupciones. Esto se puede lograr en un par de líneas de C:

 void countRainCycles() { if (nextTimeRainIterrupt == 0 || nextTimeRainIterrupt < millis()) { rainCyclesCounter++; // The interrupts counter nextTimeRainIterrupt = millis() + 100; // Wait 100msecs before next trigger } }

La función millis() devuelve el tiempo de ejecución actual en milisegundos desde que se encendió el Arduino. También vale la pena señalar que estas variables deben definirse como volátiles para indicarle al compilador que no optimice la ejecución y, por lo tanto, evite valores inexactos durante las interrupciones de hardware.

De alguna manera, necesitaba que la estación almacenara los datos acumulados y enviara periódicamente estas medidas a una base de datos MySQL. Así que agregué un módulo Ethernet con una ranura SD para registrar los valores y recuperarlos cada vez que un usuario (el servidor) se conecta a la estación. Durante las pruebas en casa con conectividad ADSL, funcionó sorprendentemente bien, pero casi se me cae el cabello cuando probé esto "en el campo" con Internet 3G (usando un módem 3G), ya que la estación se reiniciaba aleatoriamente cuando intentaba recuperar el ¡mediciones! Después de pruebas significativas, finalmente descubrí que los ejemplos proporcionados en Internet que describen el "servicio" de datos a un cliente conectado no consideraron que la conexión podría ser tan deficiente que la conexión con el cliente podría perderse a mitad de la transmisión del paquete, causando la el búfer de salida se desbordaría. Pero, ¿por qué una conexión interrumpida causaría un desbordamiento del búfer? Bueno, supongamos que comienza la sesión de transmisión y la estación comienza a llenar el búfer de salida con datos. Idealmente, el cliente consume este búfer más rápido de lo que se llena. Sin embargo, cuando se conectaba con un módem 3G, ¡este no era el caso! La conexión con el cliente era demasiado mala, por lo que el búfer se llenaba más rápido de lo que se consumía, lo que provocaba un desbordamiento del búfer y un reinicio repentino de la estación.

Para solucionar el problema, necesitaba agregar una función a la biblioteca Ethernet provista con el Arduino que era algo como esto:

 int EthernetClient::free() { if (_sock != MAX_SOCK_NUM) return W5100.getTXFreeSize(_sock); return 0; }

Luego, pude verificar si el cliente tenía algo de espacio en el búfer antes de intentar llenarlo con más datos:

 while (file.available() > 0) { if (client.free() > 0) { // This was key to solving the issue c = file.read(); client.print((char)c); } else { // No free buffer? Ok, I'll wait a couple of millis... delay(50); } } file.close();

Por cierto, si estás interesado en programar un Arduino, aquí tienes una gran guía.

Otra tarea interesante fue la implementación de un registro LIFO. ¿Por qué era esto necesario? Bueno, por lo general, cuando guardo las mediciones en un archivo determinado, el enfoque es simple: abrir el archivo, agregar las nuevas muestras al final y cerrar el archivo. Pero supongamos que quiero obtener las últimas 1000 mediciones, ordenadas cronológicamente. Esas medidas están al final del archivo; así que debería abrir el archivo, mover el cursor hasta el final, generar las últimas mediciones, luego hacer que el cursor del archivo regrese a la medición anterior y generar eso buscando un delimitador de muestra para detectar dónde comenzar y detenerse. El Arduino no tiene suficiente RAM ni potencia de procesador para ejecutar este proceso rápidamente, por lo que necesitaba otro enfoque. En su lugar, decidí enviar el archivo en orden inverso al servidor y luego revertir los literales de cadena en el lado del servidor:

 unsigned long filePosition = file.size(); file.seek(filePosition); while (filePosition >= 0) { if (client.free() > 0){ file.seek(filePosition); c = file.peek(); if (c != -1) { client.print((char)c); } if (filePosition <= 0) { break; } filePosition--; } }

Con alguna experiencia como desarrollador de PHP, es fácil obtener las muestras más recientes con los caracteres en el orden correcto:

 // $output has the reversed string measures, each sample is delimited by ; $rows = split(";", trim($output)); array_walk_recursive($rows, 'reverseString'); if (strlen($rows[0]) == 0) { array_shift($rows); // Remove the first line if empty } function reverseString(&$row, $key) { $row = trim(strrev($row)); } // $rows is now the array of the latest samples :)

En el lado del servidor, luego configuro un proceso cron para obtener las últimas mediciones cada dos minutos e inserto los datos en un motor MySQL. Para mostrar los datos, creé www.kitesurfcordoba.com.ar y usé jQuery para actualizar automáticamente los gráficos (que se generan usando pChart v2.0, una gran biblioteca de código abierto).

Había un montón de otros trucos necesarios para que todo funcionara, relacionados con la ingeniería de software y hardware, pero me he demorado lo suficiente, así que hablemos de minimizar el mantenimiento.

“¿Cómo puedo reducir el mantenimiento a (casi) cero?”

Esta fue una gran preocupación porque ciertamente no es fácil para mí llegar a la estación; si estuviera dispuesto a conducir dos horas solo para arreglar un mal funcionamiento menor, entonces no habría tenido que obligarla a entrar en primer lugar (yo no mencioné esto antes, pero después de todo lo que hemos pasado, la estación es en realidad una "ella", y su nombre es Dorothy).

Entonces, ¿de qué tipo de errores estamos hablando aquí? Bueno, por ejemplo: el software puede colgarse, la red puede perder conectividad, el suministro de energía puede fallar (y lo hace), etc.

Esencialmente, la estación necesita realizar la mayor autorrecuperación posible. Es por eso que utilicé perros guardianes tanto blandos como duros. Para aquellos que no están familiarizados, un perro guardián es una pieza de software o hardware que verifica si un sistema está funcionando correctamente y, si no, intenta devolverlo a la vida. El Arduino tiene un perro guardián incorporado que puede usar. Lo configuré para que esperara 8 segundos: si una llamada tarda más que ese límite de tiempo, el programa de vigilancia del software reiniciará la placa.

 wdt_enable(WDTO_8S); // "wdt" stands for "watchdog timer"

Me encanta esta función. Sin embargo, hay momentos en que la placa se reinicia y el módulo Ethernet no. ¿Por qué? Bueno, esta es una placa de creación de prototipos relativamente asequible, no un dispositivo a prueba de fallas muy costoso (ciertamente no debería construir un marcapasos con él). Para superar este inconveniente, tuve que piratear el Arduino cruzando una entrada de reinicio de hardware a una salida digital en la placa. Para evitar un bucle de reinicio, también se deben agregar un par de líneas de código:

 void setup() { digitalWrite(RESET_ARDUINO_PIN, HIGH); // Set it to HIGH immediately on boot pinMode(RESET_ARDUINO_PIN, OUTPUT); // We declare it an output ONLY AFTER it's HIGH digitalWrite(RESET_ARDUINO_PIN, HIGH); // Default to HIGH, set to LOW to HARD RESET ...

Después de eso, pude restablecer el hardware del Arduino y todos los módulos encima (incluido el módulo Ethernet) simplemente llamando a digitalWrite(RESET_ARDUINO_PIN, LOW) , lo que devolvió la vida a Dorothy después de un par de segundos.

Además, la placa se reinicia automáticamente después de una pérdida de energía. Y si falla la conexión a Internet, aprovechamos las capacidades de almacenamiento de la tarjeta SD (los datos se pueden almacenar en la tarjeta durante más de una semana y el servidor puede extraer datos antiguos para recuperar las muestras faltantes). La combinación de todas estas características nos brinda una estación meteorológica altamente robusta que puede sobrevivir a las condiciones hostiles para las que fue construida. En total, esta cosa me costó alrededor de $ 300.

gráfico circular

Y en el fin

La estación ha estado funcionando desde diciembre de 2012. Hasta la fecha, no ha fallado (o si lo hizo, la estación se recuperó lo suficientemente rápido como para que la comunidad de kitesurf y yo no lo notáramos). Hay aproximadamente 500 kitesurfistas que revisan la estación meteorológica regularmente antes de viajar al lugar. Así que además de la recompensa de resolver algunos desafíos técnicos difíciles, también he tenido la oportunidad de brindarle a un grupo de personas una experiencia de kitesurf más agradable.

1 Inicialmente, estaba usando un Arduino Uno. Más tarde, cambié a un Arduino Mega debido a la necesidad de aumentar la RAM y la memoria flash.

Relacionado: Trabajar con muestreo de audio ESP32