Hola amigos. En este post estaré explicando como crear un programa que nos servirá para graficar los datos que obtenemos de un sensor conectado a Arduino. La verdad es un programa básico que podría ser mejorado por cualquiera de ustedes que sepa de programación.
En lo personal yo soy un aficionado a la programación y apenas se construir aplicaciones a partir de secuencias lógicas y algoritmos basados en la matemática. Carezco de formación académica en el campo de la programación (como la gran mayoría de los grandes programadores) por lo que desconozco muchos conceptos que podrían facilitar la creación de este tipo de aplicaciones.
En fin, empecemos.
Creamos un proyecto en Netbeans al que llamaremos Graphics. Colocamos un JForm y agregamos un jPanel de color blanco.
El color blanco se coloca en la propiedad «Background». También sugiero cambiar el borde a «Etched Border».
A mi me gusta que la ventana quede del tamaño de la pantalla, por lo que crearemos un método llamado ScreenDImension() con el que «setearemos» nuestra ventana al tamaño de la pantalla.
Declaramos las variables globales del tipo entero ancho y alto.
La función ScreenDimension():
1 2 3 4 5 6 |
private void ScreenDimension() { Dimension dim = Toolkit.getDefaultToolkit().getScreenSize(); ancho = dim.width; alto = dim.height; this.setBounds(0, 0, ancho, alto - 35); } |
Con esto lo que hacemos es que obtenemos el tamaño de la pantalla y ajustamos la ventana a dichas medidas. Necesitamos invocar este método luego del initComponents().
Al ejecutar el programa obtenemos lo siguiente:
Ahora queremos que el panel blanco se ajuste al tamaño de la ventana.
Crearemos un método con al cual le indicaremos al Panel su posición y tamaño. Yo he decidido dejar un espacio de 200 pixels a la izquierda de la ventana para luego colocar unos controles.
1 2 3 4 |
private void setControls() { this.setLayout(null); jPanelGrafica.setBounds(200, 0, ancho - 200, alto); } |
El método setControls() coloca el panel a 200 pixeles a la derecha del borde de la ventana. Además le da el ancho y el alto. Con el comando this.setLayout(null) eliminamos el Layout que es una propiedad que tienen los controles en Java como los panel y los frame que asigna posiciones a los controles que se encuentren dentro. Esto a veces nos limita un poco por lo que le quitamos el Layout al jFrame para que nos deje hacer lo que queremos sin restricciones.
Ahora dibujaremos los ejes X y Y de nuestra gráfica.
Para ello utilizaremos la interfaz Graphics. Declaramos una variable pública:
1 |
Graphics g; |
Se importará java.awt.Graphics.
Ahora debajo del método setControls() le indicamos a la variable g que ella estará ligada al panel blanco que ya colocamos.
1 |
g = jPanelGrafica.getGraphics(); |
Dibujar con Graphics es muy fácil la verdad. Hagamos una prueba: dibujaremos un cuadrado y un círculo. El método que hará esto seria Graficar():
1 2 3 4 5 6 7 8 9 10 11 |
private void drawShapes(Graphics g) { // Set the color to red g.setColor(Color.red); // Draw a red square at (300, 300) with a width and height of 300 g.fillRect(300, 300, 300, 300); // Change the color to blue g.setColor(Color.blue); // Draw a blue oval at (100, 100) with a width and height of 200 g.fillOval(100, 100, 200, 200); } |
Colocamos un botón al que llamaremos «Graficar». En él invocaremos a Graficar().
Al ejecutar la aplicación y hacer clic en el botón se obtiene lo siguiente:
Ahora crearé el eje Y de nuestra gráfica utilizando un rectángulo de 2 de ancho por el alto de la ventana.
1 |
g.fillRect(38, 0, 2, alto); //Esto creará el eje vertical dejando un espacio para la nomenclatura. |
Ahora colocaremos las «rayitas en el eje».
Las rayas estarán de 25 en 25 pixeles, para que halla espacio entre una y otra y se entienda la gráfica.
El código es el siguiente:
1 2 3 |
for (int i=1; i<= (alto/25)-4;i++){ g.drawLine(36, i*25, 45, i*25); } |
En un ciclo for, se dibujará una línea que vaya del 36 al 45 (7 pixeles) y la coordenada y estará dada por i*25 siendo la variable i el contador del ciclo for. Cuando colocamos (alto/25)-4 le indicamos al programa que divida el alto de la ventana del panel entre 25 y que le quite 4 rayitas para que al final quede un espacio en donde se colocará el eje x en condiciones normales.
Todo esto lo colocaremos en un método al que llamaremos setGrid(). Grid significa cuadrícula, osea que este método preparará la cuadrícula para graficar.
Ahora colocamos los números. Declararé 2 variables públicas, GraphMax y GraphMin, osea, el máximo y el mínimo de la gráfica:
1 |
int GraphMax=100, GraphMin=0; |
Ahora con esto podemos implementar el siguiente código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
int number; int posX = 0; String tamaño; g.setColor(Color.black); g.fillRect(38, 0, 2, alto); for (int i = 1; i <= (alto / 25) - 4; i++) { g.drawLine(36, i * 25, 45, i * 25); number = (GraphMax) - ((i - 1) * ((GraphMax - GraphMin) / 25)); tamaño = "" + number; switch (tamaño.length()) { case 1: posX = 20; break; case 2: posX = 15; break; case 3: posX = 10; break; case 4: posX = 5; break; } g.drawString(number + "", posX, (i * 25) + 4); } |
Cuando ejecutamos el código obtenemos lo siguiente:
Si cambiamos el Máximo y el Mínimo podemos ver que nuestro algoritmo se adapta a los cambios.
Sólo nos falta agregar el eje X a nuestro método setGrid(), sin embargo primero trabajaremos en el algoritmo para graficar.
Primero buscamos la coordenada de la rayita de más abajo. Creamos una variable del tipo entero llamada cero
1 |
int cero=alto-118; |
Ahora creamos un método al que llamaremos Graph(int input) que será el encargado de graficar. En el parámetro input metemos el número a graficar.
Por el momento utilizaremos el siguiente código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
public void graph(int input) { // Set the drawing color to red g.setColor(Color.red); if (Xstep == 0) { // If it's the first data point, initialize LastX and calculate Ystep LastX = 38; Ystep = cero - (((input - GraphMin) * 25) / ((GraphMax - GraphMin) / 25)); LastY = Ystep; } else { // For subsequent data points, update LastX, LastY, and Ystep LastX = Xstep; LastY = Ystep; Ystep = cero - (((input - GraphMin) * 25) / ((GraphMax - GraphMin) / 25)); } // Calculate the new Xstep and draw a small oval at that point Xstep = (tiempo * 25) + 38; g.fillOval(Xstep - 2, Ystep - 2, 5, 5); // Draw a line connecting the current and previous data points g.drawLine(LastX, LastY, Xstep, Ystep); // Calculate the position of the zero reference line zero = cero - (((0 - GraphMin) * 25) / ((GraphMax - GraphMin) / 25)); g.setColor(Color.black); // Draw a vertical line at the Xstep position g.drawLine(Xstep, zero + 3, Xstep, zero - 5); if (tiempo > 0) { String tamaño; int number; int Xpos = 0; number = frecuencia * (tiempo); tamaño = "" + number; // Calculate the X position for drawing the frequency label switch (tamaño.length()) { case 1: Xpos = 3; break; case 2: Xpos = 6; break; case 3: Xpos = 9; break; case 4: Xpos = 12; break; } // Draw the frequency label g.drawString(number + "", Xstep - Xpos, zero + 15); } // Draw grid points along the Y-axis g.setColor(Color.gray); for (int i = 0; i <= zero - Ystep; i++) { if (i % 5 == 0) { g.fillOval(Xstep, zero - i, 1, 1); } } // Draw grid points along the X-axis for (int i = 0; i <= Xstep - 38; i++) { if (i % 5 == 0) { g.fillOval(38 + i, Ystep, 1, 1); } } // Increment the time variable and clear the graph if necessary tiempo++; if (tiempo == (ancho / 25) - 8) { g.setColor(Color.white); g.fillRect(0, 0, ancho, alto); tiempo = 0; Xstep = 0; LastX = 38; Ystep = 0; LastY = 0; } } |
Con este código, si colocamos la sentencia Graph(tiempo); y luego setGrid() en el botón Graficar, entonces obtendremos lo siguiente:
Con esto estamos listos para graficar datos provenientes de Arduino. Yo he preparado un código para graficar datos de humedad.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
#include "DHT.h" #define DHTPIN 2 #define DHTTYPE DHT11 DHT dht(DHTPIN, DHTTYPE); boolean started = false; double h; int frecuencia = 1000; // Default frequency in milliseconds char inputChar = '0'; // Default input character void setup() { pinMode(13, OUTPUT); Serial.begin(9600); // Initialize serial communication } void loop() { if (Serial.available() > 0) { inputChar = Serial.read(); started = (inputChar == 'b'); // Start reading humidity data when 'b' is received frecuencia = (inputChar >= '1' && inputChar <= '9') ? (inputChar - '0') * 1000 : frecuencia; // Set frequency based on input (1-9) frecuencia = (inputChar == 'a') ? 10000 : frecuencia; // Set frequency to 10,000 when 'a' is received } if (started) { h = dht.readHumidity(); // Read humidity data Serial.print(h); // Print humidity value to serial monitor Serial.print(','); // Separator for data } delay(frecuencia); // Delay for the specified frequency } |
Aquí solamente utilizamos un DHT11, igual al que utilizamos en el post DHT11: Sensor de Humedad/Temperatura para Arduino.
Una variable del tipo boolean (started) le indicará a Arduino si envía datos o no. Nuestro programa en Java le indicará la frecuencia a la que tiene que enviar datos.
Cargamos el código a nuestro microcontrolador. Ahora necesitamos agregar ArduinoConnection(), EnviarDatos(), RecibirDatos() y el SerialEventListener.
Aquí están los imports (recuerden agregar la librería RXTX)
1 2 3 4 5 6 7 8 9 10 11 12 |
import gnu.io.CommPortIdentifier; import gnu.io.SerialPort; import gnu.io.SerialPortEvent; import gnu.io.SerialPortEventListener; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Toolkit; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Enumeration; |
Ahora las variables:
1 2 3 4 5 6 |
private OutputStream Output = null; private InputStream Input = null; SerialPort serialPort; private final String PORT_NAME = "COM3"; private static final int TIME_OUT = 2000; private static final int DATA_RATE = 9600; |
Los métodos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
import gnu.io.*; import java.io.*; public class ArduinoConnection implements SerialPortEventListener { // Constants for port configuration private static final String PORT_NAME = "COM1"; // Change to your Arduino's port name private static final int DATA_RATE = 9600; // Baud rate private static final int TIME_OUT = 2000; // Time-out for opening port private static final int ERROR = 1; // Error code for failures // Variables for communication private SerialPort serialPort; private OutputStream Output; private InputStream Input; // Constructor public void ArduinoConnection() { CommPortIdentifier portId = null; Enumeration<?> portEnum = CommPortIdentifier.getPortIdentifiers(); // Find the Arduino port while (portEnum.hasMoreElements()) { CommPortIdentifier currPortId = (CommPortIdentifier) portEnum.nextElement(); if (PORT_NAME.equals(currPortId.getName())) { portId = currPortId; break; } } // Exit if the port is not found if (portId == null) { System.exit(ERROR); return; } try { // Open the port and set communication parameters serialPort = (SerialPort) portId.open(this.getClass().getName(), TIME_OUT); serialPort.setSerialPortParams(DATA_RATE, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE); // Initialize input and output streams Output = serialPort.getOutputStream(); Input = serialPort.getInputStream(); // Register this class as an event listener for data availability serialPort.addEventListener(this); serialPort.notifyOnDataAvailable(true); } catch (Exception e) { // Exit on any exception System.exit(ERROR); } } // Event handler for receiving data @Override public void serialEvent(SerialPortEvent event) { if (event.getEventType() == SerialPortEvent.DATA_AVAILABLE) { try { // Call a method to process the received data int receivedData = RecibirDatos(); // Handle the received data as needed } catch (IOException e) { // Handle the exception, e.g., log or display an error message } } } // Method to receive and process data private int RecibirDatos() throws IOException { int receivedData = 0; receivedData = Input.read(); return receivedData; } // Method to send data to Arduino private void EnviarDatos(String data) { try { // Send the data as bytes Output.write(data.getBytes()); } catch (IOException e) { // Handle the exception, e.g., log or display an error message } } } |
Por último, el EventListener. Desde aquí invocamos el método Graph().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
public synchronized void serialEvent(SerialPortEvent oEvent) { // Check if there is serial data available while (oEvent.getEventType() == SerialPortEvent.DATA_AVAILABLE) { try { int datos; datos = RecibirDatos(); // Read incoming data from the serial port if (datos > 0) { // Append the received character to the message Mensaje = Mensaje + (char) datos; // Check if the message ends with a comma if (Mensaje.charAt(Mensaje.length() - 1) == ',') { for (int i = 0; i <= Mensaje.length() - 1; i++) { // Search for a period (.) in the message if (Mensaje.charAt(i) == '.') { // Extract and parse the numeric data before the period DatoEntrada = Integer.parseInt(Mensaje.substring(0, i)); System.out.println(DatoEntrada); // Process and display the received data (e.g., call Graph) Graph(DatoEntrada); // Clear the message for the next data Mensaje = ""; } } } } } catch (Exception e) { // Handle exceptions and print error messages System.err.println(e.toString()); } } } |
Con esto ya podemos iniciar nuestro programa y empezar a recibir datos desde Arduino.
Ahora solo tenemos que agregar unos pequeños detalles para que nuestra graficadora se vea un poco más completa.
Agregaré un panel con los siguientes controles:
La programación de estos controles está disponible aquí:
https://docs.google.com/folder/d/0B0hsUkhqWH97elQtOU42aTlHTGc/edit?usp=sharing
Lo que hice fue que agregué los títlos de los ejes, el máximo y el mínimo de la gráfica, la frecuencia con la que Arduino enviará datos. Se podrá pausar el recibimiento de datos y reanudarlo. También agregué una tabla en la cual se irán registrando los datos que se irán recibiendo.
En el archivo que subí está disponible todo el código de estos controles con su respectiva explicación. Ahora es hora de probar nuestra aplicación.
[youtube https://www.youtube.com/watch?v=tMEekJcYY-U]
Lamento no poder incluir el audio en este video por el blower, pero se logra apreciar el funcionamiento del programa. Con el desodorante aumento un poco la humedad, luego con el blower la bajo. Cambio los datos de humedad a temperatura y la bajo para luego subirla nuevamente con el blower.
Espero sus comentarios.
Saludos.
amigo como seria para conseguir el codigo fuente???
con que otro sensor puedo remplazar el q usas?
Aquí puedes encontrar el proyecto de Netbeans:
http://docs.google.com/folder/d/0B0hsUkhqWH97elQtOU42aTlHTGc/edit?usp=sharing
Esto se puede usar con cualquier sensor. Yo lo he usado en este blog con DHT11 y HC-SR04. Pero funciona con cualquier sensor
Puede funcionar en vez de el sensor que usar un potenciometro? como por ejemplo para calcular la posicion del potenciometro?
Sí es posible, sin embargo tendrías que utilizar un principio un poco diferente, la lectura analógica de datos en donde le aplicas un voltaje al potenciómetro y con las caídas determinas la resistencia en el mismo
excelente publicacion estoy apunto de entregar un proyecto y ha sido de mucha ayuda, solo quisiera saber como modificas el programa para que en vez de humedad lea temperatura?
Necesitas hacer un cambio en el código Arduino. En vez de usar dth.readHumidity usas dht.readTemperature
disculpa en vez del dht11 necesito usar un moisture como seria la libreria o que modificaciones se deben hacer
Hasta ahora no he trabajado con moisture por lo que no te podría decir que debes hacer. Necesitaría tener uno de esos sensores, estudiarle y luego publicar
vale se agradece la atención
oye crees que pueda servir para un electrocardiografo…. ya tengo la parte analogica, y ya la conecte al arduino pero no se como graficarla, esto ayudaria?
gracias por tu ayudaa
Necesitarías hacer conversión análogo-digital y registrar los datos en intérvalos muy cortos en el orden de los milisegundos, quizás mirosegundos. Pero sí es posible y sí se puede hacer con este programa. También podrías usar MatLab y Excel
gracias… oye disculpa no tengo el programa para hacer el codigo que tu pones…. tengo matlab pero la verdad no se como ocuparlo, sabes como pasar los datos que arroja el arduino a matlab y graficarlos???, ya hice la conversion analogo-digital en intervalos de 5ms… pero no se como pasar esos datos a tu programa…. y bueno en este caso a matlab… me ayudarias con eso
Para pasarlos a MatLab necesitarías investigar como se logra comunicación serial en esa plataforma. Yo no tengo experiencia en eso, planeo algún día publicar algo de MatLab pero como estoy en la Universidad no tengo mucho tiempo aparte de los fines de semanas para dedicarme a este blog. Para usar mi programa necesitarías usar analogRead() en Arduino y luego usar Serial.println() para mandar los datos a Java y graficar
amigo disculpa como podria hacer sto de graficar pero con un lm35
Necesitas aprender como leer el LM35 (con analogRead()) y luego lo cambias donde dice h=readHumidity() en el código Arduino. Lo demás todo se queda igual
como haria para una comunicacion con bluetooth rs232 (HC-06) ? …porque con este no se conecta..o que podria hacer o cambiar en en el codig fuente.. = ( !!!
me sale un error en los gnu ?
import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
falta importar la libreria RXTX
Excelente esta ayuda. Estoy trabajando en un proyecto en mi universidad y necesito graficar una señal de un sensor de distancia marca Sharp pero no he podido porque a pesar de que el proyecto que usted nos facilita no me genera errores simplemente cuando le doy «graficar» no lo hace, no sé cómo debe ser el contenido de la instrucción Serial.print(«contenido») para que el proyecto que usted nos facilita funcione porque tampoco sé si sea necesaria la implementación de la librería «Ultrasonido» si es así dónde la puedo descargar y además no sé si sea posible utilizarla con un sensor de distancia.
Muchas gracias!
Recomiendo utilizar la graficadora de datos de sensores con jFreeChart que se encuentra en este blog. Es mucho más versátil que este programa. Recomiendo probar el sensor en Arduino antes de acoplarlo a Java. Debemos recordar que Arduino no es programado en Java sino que interactúan ambos lenguajes entre sí a través de comunicación serial. Si se logra imprimir los datos de los sensores en el monitor Serial, Java podrá recibir dichos datos y graficarlos
Muchas gracias Antony por tu ayuda, ya solucioné el inconveniente, ahora me gustaría aprender a finalizar la conexión con Arduino sin tener que cerrar la aplicación, pues tengo dos diferentes programas que ya están configurados independientemente con su conexión serial a Arduino y quiero conservarlos así, pero para poder ejecutar uno y luego el otro debo finalizar la conexión con Arduino del primero, cómo se puede hacer esto?
Muchas gracias!
Puedes compartir el diagrama o cirtuito por favor. Saludos
alguien mas tiene este error?
…Graphicsnbprojectbuild-impl.xml:1040: The following error occurred while executing this line:
…Graphicsnbprojectbuild-impl.xml:805: Java returned: 64
BUILD FAILED (total time: 4 seconds)
necesito ayuda 🙁
1040
805
Hola pudiste componer el error?
Hola, en que unidades estaria la temperatura?
tengo el siguiente problema:
WARNING: RXTX Version mismatch
Jar version = RXTX-2.2pre1
native lib Version = RXTX-2.2pre2
Por ultimo tambien me sale esto:
C:UsersjeancarloDesktopGraficadora con sensor HC-SR04JavaGraphicsnbprojectbuild-impl.xml:1040: The following error occurred while executing this line:
C:UsersjeancarloDesktopGraficadora con sensor HC-SR04JavaGraphicsnbprojectbuild-impl.xml:805: Java returned: 64
Este código es muy viejo. Recomiendo ver el post https://panamahitek.com/arduino-java-facil-y-rapido/
Grados Celsius
Buenas, tengo un problema, estoy usando el mismo sensor y me lee y grafica, el problema está en el eje y, porque me salen valores que no concuerdan con los que leen, es decir me sale que lee 40,41,42… De humedad, pero en la parte de la gráfica salen valores -5,-6…
¿Cuál podría ser el problema? He usado vuestros archivos de esta guía.
Gracias por su aporte, me sirvió mucho 🙂
Me dice que Mensaje y DatoEntrada no estan declarados
Verifica las librerías
hola quisiera adquirir datos de este sensor de color TCS3200, tenes algun ejemplo para mandar?
Dejo mi mail: [email protected]. Cualquier idea y/o ejemplo voy a estar muy agradecido. Saludos!
Cómo puedo aplicar esto si lo que necesito es medir potencia, ayuda por favor
Usa medidores de voltaje y corriente
Necesito ayuda por el siguiente error:
java.lang.NumberFormatException: For input string: «nan,nan,nan,nan,nan,nan,-10»
El programa sigue intacto pero no logro que grafique.