Ir al contenido principal

Arduino - MEF - Sistema detector Rayos UV

 

Propuesta de mejora de código sin MEF proyecto Sensor UV

Código para el Arduino

La siguiente tabla es el código de Arduino que propongo, sin Máquina de Estados Finitos, explicado paso a paso. En principio podrían copiar cada parte en orden y probarlo en el Arduino.

Primero propongo unos truquitos del lenguaje C++

// Trucos de C++

#define ENTER "\n"

 

template<class CualquierTipo> inline Print

&operator << (Print &izq, CualquierTipo der )

{

  izq.print( der ) ;

  return izq ;

}

 

A continuación, incluir las librerías necesarias. Hasta el momento, incluyeron ustedes el de DHT (Detector de Humedad y Temperatura Relativa DHT22) y la pantallita LCD 16x2 mediante Protocolo I2C.

Para “conversar” en dicho protocolo se necesita la librería Wire.h nativa de Arduino C++.

 

// Incluir librerías

  #include <DHT.h>

  #include <Wire.h>

  #include <LiquidCrystal_I2C.h>

 

Seguido, definimos los pines de los componentes que agregamos al Arduino.

 

// Definición de pines

  int

    _pinDHT = 3

    , _verd1 = 4

    , _verd2 = 5

    , _amar1 = 6

    , _amar2 = 7

    , _rojo1 = 8

    , _rojo2 = 9

    , _pinUV = A0

  ;

 

Es momento de instanciar los componentes no sencillos: la pantalla LCD (salida, actuador) y el sensor DHT (entrada, sensor).

 

// Creación de variables

  LiquidCrystal_I2C

    _pantallita(

      0x27 // Dirección estándar del dispositivo I2C para el LCD

      , 16 // Letras de ancho del LCD

      , 2 // Renglones del LCD

    )

    ;

 

  DHT

    _sensorHT( _pinDHT, DHT22 )

    ;

 

Ahora crearemos las variables de las mediciones, más una que nos  va a ayudar con el tiempo:

 

  float

    _medidaSensorUV = 0.0f

    , _medidaHumedad

    , _medidaTemperatura

    , _medidaIndiceDeCalor

    , _tiempo = 0

    ;

 

Inicializamos todo dentro del setup():

 

void setup() {

 

  Serial.begin(9600);

  Serial << "arduino:conectado" << ENTER ;

 

  pinMode( _verd1, OUTPUT ) ;

  pinMode( _verd2, OUTPUT ) ;

  pinMode( _amar1, OUTPUT ) ;

  pinMode( _amar2, OUTPUT ) ;

  pinMode( _rojo1, OUTPUT ) ;

  pinMode( _rojo2, OUTPUT ) ;

 

  _pantallita.init() ;

  _pantallita.backlight();

 

  _sensorHT.begin();

 

}

 

Programamos el loop()

 

void loop() {

 

Y verificamos cada 2 segundos el sensor de humedad DHT, que es solicita este sensor para no tomar mediciones erróneas. En todo caso, podríamos realizarlo también cada 3, 4 ó 5 segundos.

 

  if( _tiempo >= 2000 ) {

 

Intentamos extraer del sensor las medidas de humedad y temperatura, y corroboramos que fue posible y que no generó un valor no-numérico o NaN (Not-A-Number) con la función isnan() de Arduino.

 

    _medidaHumedad = _sensorHT.readHumidity() ;

    _medidaTemperatura = _sensorHT.readTemperature() ;

 

    if(

      isnan( _medidaHumedad )

      || isnan( _medidaTemperatura )

    ) {

      Serial << "error:" << "Error obteniendo los datos del sensor DHT11" << ENTER ;

      _tiempo = 0 ;

      return;

    }

 

Si lo hemos logrado, podemos quizás computar el Índice de calor, que depende de la temperatura y la humedad, y la librería de DHT tiene una función que lo calcula. Esto es opcional.

 

    _medidaIndiceDeCalor = _sensorHT.computeHeatIndex(

        _medidaTemperatura

        , _medidaHumedad

    ) ;

 

Es momento de probar el sensor de Rayos UltraVioleta. Medimos directamente por la función analogRead()

 

    _medidaSensorUV = analogRead( _pinUV ) ;

    _medidaSensorUV = map(

      _medidaSensorUV

      , 0, 1023

      , 0, 100

    );

    Serial << "Sensor UV:" << _medidaSensorUV << ENTER ;

 

Aquí me pareció óptimo crear una función. Habían muchos renglones parecidos, para activar las luces correspondientes. Así que pensé en crear una función y directamente pasarle 1 (=HIGH) y 0 (=LOW) a cada luz que ustedes plantearon.

 

La función la llamé fnLuces() y pide para cada luz (2xVerdes, 2xAmarillas, 2xRojos) un HIGH o LOW. Con una toma de decisiones según los números que plantearon, y con ayuda del else, las condiciones se pudieron cambiar de la siguiente manera:

 

    if     ( _medidaSensorUV < 10 ) fnLuces( 1,0, 0,0, 0,0 ) ;

    else if( _medidaSensorUV < 25 ) fnLuces( 1,1, 0,0, 0,0 ) ;

    else if( _medidaSensorUV < 40 ) fnLuces( 1,1, 1,0, 0,0 ) ;

    else if( _medidaSensorUV < 55 ) fnLuces( 1,1, 1,1, 0,0 ) ;

    else if( _medidaSensorUV < 70 ) fnLuces( 1,1, 1,1, 1,0 ) ;

    else fnLuces( 1,1, 1,1, 1,1 ) ;

 

Después de analizar los Serial.print() (Serial <<) que servirían para la AppWeb que necesito para DAII, me acordé que necesitarían grabar esto en una base de datos, así que junté todos los valores en un solo print:

 

    Serial

      << "humedad:" << _medidaHumedad << "%" << ENTER

      << "temperatura:" << _medidaTemperatura << "ºC" << ENTER

      << "indice de calor:" << _medidaIndiceDeCalor << "ºC" << ENTER

      << "grabar:" << _medidaHumedad

      << "\t" << _medidaTemperatura

      << "\t" << _medidaSensorUV << ENTER

    ;

 

Finalizamos el if() de cada 2 segundos, reseteando el tiempo para volver a medir los próximos 2 segundos.

 

    _tiempo = 0 ;

    return ;

  } // Fin if() de cada 2 segundos

 

Mostramos en la pantalla LCD los valores más importantes, y aumentamos el tiempo transcurrido en 200 milisegundos; la pantalla se actualiza 5 veces por segundo (1000/200).

 

  _pantallita.clear() ;

 

  _pantallita.print( "Temp: " ) ;

  _pantallita.print( (int)_medidaTemperatura ) ;

  _pantallita.print( "C" ) ;

 

  _pantallita.print( "Humedad: " ) ;

  _pantallita.print( (int)_medidaHumedad ) ;

  _pantallita.print( "%" ) ;

 

  delay( 200 ) ;

  _tiempo = _tiempo + 200 ;

} // Fin loop()

 

Finalmente creó la función para encender las luces…

 

void fnLuces( int v1, int v2, int a1, int a2, int r1, int r2 ) {

  digitalWrite( _verd1, v1 ) ;

  digitalWrite( _verd2, v1 ) ;

 

  digitalWrite( _amar1, v1 ) ;

  digitalWrite( _amar2, v1 ) ;

 

  digitalWrite( _rojo1, v1 ) ;

  digitalWrite( _rojo2, v1 ) ;

}

 

 

Análisis del resultado del Monitor Serial

Qué cosas se puede mostrar en el Monitor Serial si probáramos el Arduino. Ubico primero todos los Serial <<, y agendo posibilidades. De todos ellos, la App Web necesitará aquellos que comienzan con una o varias palabras, haya en el medio un signo dos-puntos (:) y termine con un ENTER (Serial.println).

  "humedad:50.04%"

  "temperatura:20.50ºC"

  "indice de calor:5.33ºC"

  "Sensor UV:45.00"

  "grabar:50.04\t20.50\t45.00"

  "error:Error obteniendo los datos del sensor DHT11"

 

Detalles para la App Web

1.      Instalar la librería de WebComponents en Castellano, en el HEAD

 

<script src="https://gorosito.red/componentes" ></script>

 

2.      En el BODY insertar un componente Arduino-USB

 

<arduino-usb id="idArduino" eventos="_listaEventosArduino" >

</arduino-usb>

 

3.      En el BODY, luego, crear un SCRIPT y codificar lo siguiente:

 

    var _listaEventosArduino = {

      humedad: fnHumedad

      , temperatura: fnTemperatura

      , "indice de calor": fnIndiceCalor

      , "Sensor UV": fnSensorUV

      , grabar: fnGrabarEnBD

      , error: fnErrorAlMedir

    };

 

La variable _listaEventosArduino es una lista nombrada (nombre técnico en JavaScript: objeto, en Python: diccionario, en PHP: array asociativo, en Pascal: record, en C/C++: Struct).

 

Noten que tiene que ver con lo del Serial.println(), el signo dos puntos.

 

Como norma general, en JavaScript, lo que va a la izquierda de cada 2 puntos, puede tener comillas o no; pero si es más de una palabra, las tiene que tener sí o sí.

 

Ahora creamos las funciones mencionadas en esa lista:

<script>

    function fnHumedad( _generadaHumedad ) {

      idHumedad.innerHTML = _generadaHumedad ; // "50.04%"

      idInputHumedad.value = + _generadaHumedad ; // 50.04

      console.log( "Humedad leída:", _generadaHumedad ) ;

    }

    function fnTemperatura( _generadaTemperatura ) {

      idTemperatura.innerHTML = _generadaTemperatura ; // "20.50ºC"

      idInputTemp.value = + _generadaTemperatura ; // 20.50

      console.log( "Temperatura leída:", _generadaTemperatura ) ;

    }

    function fnIndiceCalor( _generadoIndice ) {

      // ...

    }

    function fnSensorUV( _generadoUV ) {

      idUV.innerHTML = _generadoUV + "%" ; // "45%"

      idInputUV.value = +_generadoUV ; // 45

      idProgresoUV.value = +_generadoUV ;

      console.log( "Factor UV medido:", _generadoUV ) ;

    }

    function fnErrorAlMedir( _generadoTexto ) {

      console.error( _generadoTexto ) ;

    }

 

Ahora, la función más importante, la que grabará en una base de datos. Eso lo haremos mediante la función fetch() de JavaScript y sus métodos .then() y .catch().

 

La función fetch() pedirá a un recurso de un servidor web llamado “grabar.php” y le pasará datos por método GET (acuérdense esto de 4to año, directo en la URL).

 

Los métodos .then() y .catch() sirven para esperar una respuesta por parte del servidor. Según esta respuesta, ejecutaremos una función u otra.

 

Veamos cómo sigue:

 

    function fnGrabarEnBD( _textoParaGrabar ) {

      var _split = _textoParaGrabar.split( "\t" ) ;

      var _hum = _split[0] ;

      var _tem = _split[1] ;

      var _uv  = _split[2] ;

      fetch( "/grabar.php?nmHum="+_hum+"&nmTem="+_tem+"&nmUV="+_uv )

        .then( x => x.text() )

        .then( fnGrabadoOk )

        .catch( fnEnviarError )

      ;

    }

 

    function fnGrabadoOk( _textoGenerado ) {

      if( _textoGenerado == "1" ) {

        console.log( "Grabado OK" ) ;

      }

      else {

        console.log( "Error al grabar", _textoGenerado ) ;

      }

    }

    function fnEnviarError( _textoGenerado ) {

      console.log( "Error al enviar", _textoGenerado ) ;

    }

 

Acá ya podemos cerrar el SCRIPT

</script>

 

Endpoint o recurso del servidor

Recordemos que un Servidor PHP funciona ofreciendo como recursos archivos guardados en una carpeta pública. Y no sólo eso, si ese archivo que se pide es un archivo de extensión .php, antes de enviar información a quien lo pide (por ejemplo, un navegador web, o una función fetch()), procesará información e imprimirá lo que corresponda (por esto se dice que PHP permite generar páginas web dinámicamente).

Una vez recordado esto, en un proyecto de Glitch, basado en basededatos2024, remixarlo con el nombre pedido en el Classroom, y en la carpeta “publica/” crear el archivo “grabar.php” (publica/grabar.php).

Código del archivo publica/grabar.php

En primer lugar, para simplificar el trabajo con una base de datos, existe un archivo llamado útiles.php que se encuentra en la carpeta base. Lo incluimos:

<?php

  include "../utiles.php" ;

 

Ahora crearemos una variable llamada $base que se conectará a un archivo de base de datos tipo SQLite. (Ver más adelante en este documento cómo crear la base de datos).

 

  $base = basededatos( "proyectoarduino.db" ) ;

 

Ahora consideramos los datos que enviaría el Fetch. Suponiendo lo siguiente:

fetch( “/grabar.php?nmHum=50.04&nmTem=20.50&nmUV=85” )

Habremos enviado por método GET los datos en los name de tres imaginarios INPUT que son nmHum, nmTem y nmUV…

 

  $humedad = $get->nmHum ;

  $temperatura = $get->nmTem ;

  $uv = $get->nmUV ;

  $ahora = date( "Y-m-d h:i:s" ) ;

 

A continuación creamos la regla SQL de tipo INSERT INTO para agregar esos datos a la base de datos:

 

  $reglaSQL = "

    INSERT INTO TablaMediciones

    ( _id, _hora, _humedad, _temperatura, _uv )

    VALUES

    ( NULL, '$ahora', '$humedad', '$temperatura', '$uv' )

    ;

  ";

  $accion = $base->insertar( $reglaSQL ) ;

 

Finalmente verificamos si hubo algún tipo de error y mostramos un cero y el texto del error; pero si está todo OK, imprimiremos un “1”, el que adoptamos en la función fnGrabadoOk() de la página web…

 

  if( $accion->error ) {

    print "0-" . $accion->error ;

  }

  else {

    print "1" ;

  }

?>

Crear la base de datos

1.      Hacerle vista previa al proyecto.

2.      Ubicar el botón Gestor, que nos llevará al archivo gestor.php

3.      Te va a pedir una contraseña. Es la escrita en el archivo “clave.php”. Ahí dice que la contraseña es “admin” (sin comillas). Ustedes luego la cambiarán.

4.      Ahora te pedirá el nombre de un archivo. Te va a listar todos los archivos de base de datos existentes, pero no estará el de “proyectoarduino.db” que citamos más arriba. Escribilo (obviamente sin comillas) y al presionar el botón, el sistema lo creará.

5.      Vamos a crear la tabla. Escribí el siguiente comando CREATE TABLE…

CREATE TABLE TablaMediciones (

    _id INTEGER PRIMARY KEY AUTOINCREMENT

    , _hora DATETIME

    , _humedad REAL

    , _tempertura REAL

    , _uv REAL

  )

6.      Ponele un punto y coma. Pero no dejés espacios luego porque generará un error. Quizás lo mejor para hacer esto es ubicar el botón “Poner punto y coma”; automáticamente pondrá el Punto y Coma donde corresponda.

7.      Para asegurarte que la tabla se creó escribí “tablas” + Botón Proceder (sin comillas), esto mostrará todas las tablas creadas en la base de datos.

8.      Otra cosa que podés hacer es describir la tabla creada. Con “describir TablaMediciones”, te hará un informe sobre esa tabla, qué columnas tiene y de qué tipo es cada una.

 

n   

Comentarios

Entradas populares de este blog

Probando ChatGPT - Mapa conceptual con Mermaid

 Había conversado con ChatGPT acerca de cuidado de las plantas e hidroponía. Luego de unos días me di cuenta que quizás también podría saber acerca de crear Mapas conceptuales. Salió lo siguiente; le falta un concepto para crear correctamente mapas conceptuales (la relación entre los conceptos), pero podría ser algo para ayudarnos a corregir mejor. El texto original era el que cito a continuación, pero le agregué explícitamente que me armara un "mapa conceptual" utilizando el lenguaje Mermaid, un lenguaje descriptivo que permite construir gráficos diversos, entre ellos Mapas conceptuales, generando un archivo SVG. ``` Hacé un mapa conceptual en lenguaje Mermaid con el siguiente texto, redactado anteriormente: "La hidroponía es un método de cultivo que se utiliza para cultivar plantas sin suelo utilizando una solución nutriente y agua. Algunas plantas pueden cultivarse con éxito mediante hidroponía, mientras que otras necesitan el suelo para crecer y prosperar. De las pl...

Conectar a Servidor Node.JS desde VB6 o VBA

El presente código sirve tanto para Visual Basic 6.0 (sí, todavía sirve, todavía sirve!) como para Visual Basic para Aplicaciones (Excel o VBA). ' Si estamos en Visual Basic para Aplicaciones agregar la referencia ' WinHTTP, en el menú Herramientas, Referencias, Microsoft WinHTTP Services Function Solicitar( _ ByVal URLBase As String _ , Optional ByVal Puerto As Integer = 80 _ , Optional ByVal Seccion As String = "" _ , Optional ByVal Metodo As String = "GET" _ ) As String On Error GoTo solucion #If VBA6 Then Dim objXML As New WinHttpRequest #ElseIf Win32 Then Dim objXML As Object Set objXML = CreateObject("MSXML2.ServerXMLHTTP") #End If objXML.Open Metodo, URLBase & ":" & Puerto & "/" & Seccion, False objXML.send If (objXML.Status = 404) Then Solicitar = "404 Error" Else Solicitar = objXML.responseText End If Se...