Tag: C++

Neurtura – O cómo sobrevivir en el mundo de la Monitorización Industrial

Ha costado que llegue el segundo invitado a Mascando Bits, pero nunca es tarde si la dicha es buena, y en este caso lo es. Hoy Xabier, compañero de fatigas en el trabajo, nos trae una retrospectiva con aplicación práctica de ingeniería de hardware y software al estilo open source que tanto ha marcado los inicios de Ingran Engineering, empresa de la que me siento orgulloso de pertenecer. Xabier ha sido y será siempre un MacGyver de la electrónica y la mecánica. Esperemos que esta sea la primera de otras muchas entradas.


Actualmente vivimos en un mundo complicado, luchando entre el software y hardware abierto y sistemas propietarios. En la confrontación entre la solución basada en Arduino-ESP8266 de 5€ y el Siemens S7 de 500€. En el dilema de dar servicio a un precio adecuado y cumplir las directivas y el marcaje CE. Puede que aquí encontréis algo intermedio, o no. Pero por lo menos, fue nuestra solución durante un tiempo.

 

Introducción

La verdad que empecé a escribir esta entrada hace bastante tiempo, puede que hace más de uno año. Pero por fin lo he completado. A falta de que todavía monte un blog, quisiera agradecer a Rubén que me deje espacio en su blog para publicar.

Este va a ser la primera de varias entradas, comentando la funcionalidad de un sistema que en su día desarrollamos en Ingran Engineering, que actualmente ha quedado en desuso, al migrar a sistemas de adquisición de datos basados en Modbus-TCP, y también porque el módulo central del sistema, el Industruino que usamos en sus primeras versiones, nos dio bastantes problemas para los niveles de calidad y servicio que buscábamos. No obstante el fabricante ha sacado una revisión del Industruino que soluciona los problemas del primero y que además añade nuevas capacidades al dispositivo.

El las siguientes fotos os muestro la versión básica del Neurtura (nombre comercial del dispositivo de monitorización), en la primera podéis ver el aspecto exterior con los conectores:

 

En esta podéis ver los componentes internos:

Podéis observar de izquierda a derecha: Un magnetotérmico, un analizador de energía eléctrica, el Industruino D21G IND I/O (la versión más reciente) con su módulo ethernet al lado, una fuente de alimentación de 24 Voltios y un router Teltonika RUT230.

El la parte exterior tenemos conectores Harting, para la alimentación, para la conexión de las pinzas amperimétricas, para el analizador de energía, y para las conexiones de los sensores, actuadores y Modbus-485.

Si alguien no tiene demasiadas ganas de leer, que vaya al final, al apartado "Compilando".

 

Contexto

¿Cómo narices se puede monitorizar un proceso industrial, en este caso concreto, una bomba de agua para regadío, de manera efectiva, eficaz y barata? Esa pregunta nos la hicimos en Ingran Engineering hace bastante tiempo, con la certeza de que tenía que haber una solución que tuviese las tres Bs (Bueno, Bonito y Barato). Esto era lo que necesitábamos:

  • Medida de parámetros eléctricos de una bomba: Potencia, Voltaje, Factor de Potencia...
  • Medidas de parámetros del acuífero: Conductividad eléctrica (salinidad), Nivel del agua bajo tierra...
  • Medida de parámetros del agua extraída: Caudal, Presión...
  • Registro de datos cada 10 segundos, y mostrarlos vía Web y en el móvil

Además, por si no fuera poco, teníamos las siguientes restricciones:

  • Necesidad de utilizar GSM/GPRS/3G para la comunicación.
  • Necesitaba tener marcaje CE y cumplirlo.
  • Tenía que ser compatible con lo que había ya montado: diversos tipos de caudalímtros/contadores, medidores de nivel de acuífero...

Aqualogy tenía una solución llamada IdroSmartWell que hacía todo eso, y más, a un precio que casi nos dio un infarto al escucharlo. La solución de software vino de la mano del proyecto EmonCMS de Open Energy Monitor y la de hardware tuvimos que apañárnosla.

 

Especificaciones técnicas

Las especificaciones técnicas de Neurtura que habéis visto en las fotos anteriores son las siguientes:

  • Analizador de energía eléctrica trifásica: Conexión a pinzas amperimétricas externas de 5A o 1A. Medida de V, I, P, PF, THD y más cosas...
  • 4 Entradas analógicas de 0-20mA o 0-10 Voltios: Resolución máxima de 18 bits.
  • 2 Salidas analógicas de 0-20mA o 0-10 Voltios: Resolución máxima de 12 bits.
  • 8 Entradas/Salidas digitales: Opción de usarlas como contadores de pulsos.
  • Conexión de Modbus-485 para conectar mas dispositivos.
  • Opción de postear (enviar) a EmonCMS directamente.
  • Guardado de datos en la SD local.
  • Guardado de parámetros de configuración en la EEPROM.
  • Guardado de datos de contadores y demás datos cambiantes en la FRAM propia.
  • RTC local.
  • Actualización del firmware remota: Utilizando el RUT230.

 

Compilando y cargando el firmware

El Industruino se programa utilizando el IDE de Arduino por USB, aunque también se puede programar por Ethernet utilizando TFTP, cosa que ya comentaré explicaré mas adelante en otra entrada.

En la Organización de Ingran Engineering de Github podéis encontrar el firmware y las librerías necesarias:

Para compilarlo y subirlo al Industruino, seguid estos pasos:

  1. Decargar el IDE portable de Arduino, versión 1.8.5
  2. Extraer arduino-1.8.5-windows.zip
  3. Dentro de la carpeta de arduino-1.8.5 crear una carpeta llamada "portable"
  4. Abrir el programa arduino.exe
  5. Instalar la placa del industruino -> clicando Tools -> Boards Manager y buscando "industruino" -> Insalar "Industruino SAMD Boards 1.01"
  6. Cerrar el entorno de programación de Arduino
  7. Descargar el proyecto Ikusi-Makusi de GitHub y extraerlo en donde se quiera
  8. Descargar el proyecto Ikusi-Makusi-libraries de GitHub y copiar las librerías en la carpeta arduino-1.8.5\libraries
  9. Abrir el programa arduino.exe de nuevo, en Herramienta->Placa seleccionar Industruino D21G y en Herramientas->Puerto el puerto de series correspondiente.
  10. Abrir el archivo Industruino_ethernet.ino del proyecto Ikusi-Makusi con el IDE de Arduino.
  11. Compilarlo y subirlo al Industruino.
  12. Conectad el Industruino a una red con DHCP y encenderlo.
  13. Con los botones navegar hasta la pantalla RED y mirad la IP asignada.
  14. Meter la IP en cualquier navegador web y empezar a experimentar.

Continuará...

 

OpenCV – Mouse Event Handler (parte 2)

Continuando la entrada "OpenCV - Mouse Event Handler (parte 1)", que ya publiqué hace un tiempo, os traigo la segunda parte que da cierre al manejo de eventos del ratón con OpenCV. Si en la primera parte me centraba en el manejo puro de los eventos del ratón, en esta entrada hablaré sobre la combinación de eventos de ratón con eventos de teclado.

opencv_mouse-event_display

La combinación de eventos de teclado con los eventos de ratón, son muy útiles por ejemplo cuando queremos asegurarnos que las acciones con el ratón no son fortuitas, y por ello se asegura la acción del ratón con la pulsación de alguna tecla. Un ejemplo común puede ser el pulsar la tecla "CRTL" o "SHIFT" cuando se hace click con el ratón. Otro ejemplo puede ser la de diferenciar dos acciones distintas desde un click de ratón, gracias a la pulsación combinada con distintas teclas.

El siguiente ejemplo toma como partida el ya expuesto en la entrada de la parte 1 y modifica la función "mouseEventHandler" de la siguiente forma:

void mouseEventHandler(int event, int x, int y, int flags, void* param)
{
     if ( flags == (EVENT_FLAG_CTRLKEY + EVENT_FLAG_LBUTTON) )
     {
          cout << "Left mouse button is clicked while pressing CTRL key - position (" << x << ", " << y << ")" << endl;
     }
     else if ( flags == (EVENT_FLAG_SHIFTKEY + EVENT_FLAG_RBUTTON) )
     {
          cout << "Right mouse button is clicked while pressing SHIFT key - position (" << x << ", " << y << ")" << endl;
     }
     else if ( event == EVENT_MOUSEMOVE && flags == EVENT_FLAG_ALTKEY)
     {
          cout << "Mouse is moved over the window while pressing ALT key - position (" << x << ", " << y << ")" << endl;
     }
}

Cada vez que se produzca alguno de los siguientes eventos:

  • EVENT_FLAG_LBUTTON
  • EVENT_FLAG_RBUTTON
  • EVENT_FLAG_MBUTTON
  • EVENT_FLAG_CTRLKEY
  • EVENT_FLAG_SHIFTKEY
  • EVENT_FLAG_ALTKEY

Se invocará a la función callback “mouseEventHandler“. En este caso, en la sucesión de los distintos “if” que permite identificar el tipo de evento, se incluye además  una condición de teclado. En el caso de los clicks izquierdo y derecho del ratón, se puede combinar las flags de pulsación del ratón con los del teclado, aplicando una suma aritmética de los valores. En el caso del movimiento del ratón, esto no es aplicable, ya que el movimiento del teclado es de tipo "EVENT" y el de la tecla es de tipo "EVENT_FLAG". A causa de ello se realiza una operación lógica entre los resultados obtenidos en las comparaciones de cada  tipo.

opencv_mouse-event_key-event_display

A continuación dejo el código completo de la arquitectura básica para el manejo de eventos con pulsaciones de teclado:

OpenCV_Mouse_Event_Handler_2.cpp

 

OpenCV – Mouse Event Handler (parte 1)

Para aquellos que os estáis introduciendo un poco en temas de visión artificial, seguramente ya habréis oído hablar de OpenCV, pero para los que no, os explico someramente lo que es.

OpenCV es una librería de visión artificial desarrollada originalmente por Intel y licenciada como BSD. OpenCV está escrito en C/C++ y es multiplataforma GNU/Linux, Mac OS XWindows, IOS y Android. El proyecto proporciona un entorno de programación fácil de utilizar, altamente eficiente y sin dependencias a librerías de terceros.

Una de las herramientas esenciales cuando se trabaja en visión es la visualización e interacción con los resultados (véase imagen). OpenCV cubre perfectamente este tipo de necesidades. Para ello voy a presentar un ejemplo sencillo de manejo de ventanas con eventos de ratón.

GMM-GC-segmentation

En el siguiente código se muestra una función que renderiza una ventana:

void showWindow()
{
  	//Create an autoresize window
	namedWindow("My Window", 1);

	//Set the callback function for any mouse event
	setMouseCallback("My Window", mouseEventHandler, NULL);

	//Show the image
	frame = cv::imread("0001_.jpg");
	imshow("My Window", frame);

	//Wait until user press some key
	waitKey(0);
}

El método "namedWindow" nos permite crear una ventana con un nombre que la identifique y que además se autodimensione dependiendo el contenido que muestre. Para cargar un fichero de imagen basta con usar "imread" especificando el nombre o path del fichero para cargarlo en un objeto Mat de OpenCV. La variable "frame" es del tipo Mat mencionado, que se ha definido como global fuera de la función. Por último "waitKey" nos permite tener renderizándose la ventana hasta que se presione alguna tecla o se cierre desde el aspa de cierre de la ventana.

Habréis apreciado que me he saltado el método "setMouseCallback", pero lo he hecho para enlazar con el siguiente fragmento de código. Dicho método, nos permite asociar un callback a una ventana usando su nombre de identificación(en nuestro caso "My Windows") y pudiendo pasar parámetros adicionales (en nuestro caso no se pasa ninguno y se pasa un NULL).

void mouseEventHandler(int event, int x, int y, int flags, void* param)
{
	if  ( event == EVENT_LBUTTONDOWN )
	{
		cout << "Left button of the mouse is clicked DOWN - position (" << x << ", " << y << ")" << endl;
	}
	else if  ( event == EVENT_LBUTTONUP )
	{
		cout << "Left button of the mouse is clicked UP - position (" << x << ", " << y << ")" << endl;
	}
	else if  ( event == EVENT_LBUTTONDBLCLK )
	{
		cout << "Left button of the mouse is double clicked - position (" << x << ", " << y << ")" << endl;
	}
	else if  ( event == EVENT_RBUTTONDOWN )
	{
		cout << "Right button of the mouse is clicked DOWN - position (" << x << ", " << y << ")" << endl;
	}
	else if  ( event == EVENT_RBUTTONUP )
	{
		cout << "Right button of the mouse is clicked UP - position (" << x << ", " << y << ")" << endl;
	}
	else if  ( event == EVENT_RBUTTONDBLCLK )
	{
		cout << "Right button of the mouse is double clicked - position (" << x << ", " << y << ")" << endl;
	}
	else if  ( event == EVENT_MBUTTONDOWN )
	{
		cout << "Middle button of the mouse is clicked DOWN - position (" << x << ", " << y << ")" << endl;
	}
	else if  ( event == EVENT_MBUTTONUP )
	{
		cout << "Middle button of the mouse is clicked UP - position (" << x << ", " << y << ")" << endl;
	}
	else if  ( event == EVENT_MBUTTONDBLCLK )
	{
		cout << "Middle button of the mouse is double clicked - position (" << x << ", " << y << ")" << endl;
	}
	else if ( event == EVENT_MOUSEMOVE )
	{
		cout << "Mouse move over the window - position (" << x << ", " << y << ")" << endl;
	}
}

Cada vez que se produzca alguno de los siguientes eventos:

  • EVENT_MOUSEMOVE
  • EVENT_LBUTTONDOWN
  • EVENT_RBUTTONDOWN
  • EVENT_MBUTTONDOWN
  • EVENT_LBUTTONUP
  • EVENT_RBUTTONUP
  • EVENT_MBUTTONUP
  • EVENT_LBUTTONDBLCLK
  • EVENT_RBUTTONDBLCLK
  • EVENT_MBUTTONDBLCLK

Se invocará a la función callback "mouseEventHandler". En el código se puede observar que la sucesión de los distintos "if" permite identificar el tipo de evento. En el caso concreto del movimiento del ratón, además se imprimen las coordenadas de la ventana a las que se ha desplazado el ratón, utilizando los parámetros "x" e "y" que recibe "mouseEventHandler".

A continuación dejo el código completo de la arquitectura básica para el manejo de eventos:

 

Medir tiempo de ejecución en C++

Habitualmente una de las cosas que nos interesa medir cuando programamos código, es el tiempo de ejecución. Identificar las secciones de código que más impactan en el tiempo de ejecución total, nos permite optimizar el código y disminuir el tiempo de ejecución general de todo nuestro código.

chronometre

En C++ hay varias formas de hacerlo, pero dependiendo de cómo lo implementemos y qué cabeceras usemos, podemos encontrarnos que al compilar nuestro código en ciertos sistemas no nos funcione. El siguiente ejemplo muestra cómo medir tiempos en una sección de código utilizando cabeceras estándar de C++:

#include <iostream>
#include <ctime> 

unsigned t0, t1;

t0=clock()
// Code to execute
t1 = clock();

double time = (double(t1-t0)/CLOCKS_PER_SEC);
cout << "Execution Time: " << time << endl;

La función "clock" retorna el tiempo consumido por el programa medido en ticks,  que junto con la macro "CLOCKS_PER_SEC" nos permite traducir los ticks a segundos.

El código también se encuentra disponible en:

execution_time.cpp