Introducción
Hace poco una persona tenia dudas de como almacenar datos del consumo de la red en una base de datos y yo le comentaba que se puede hacer con costes muy reducidos o incluso gratuitos
Hardware
Para este ejercicio práctico se usará un dispositivo de domótica por su reducido coste
Si necesitas varios siempre puedes comprar un pack para ahorrar dinero:
Para la inserción en base de datos usaremos un servidor en red con linux aunque yo recomiendo un IPC con procesador ARM por tener diversificado las funciones de cada dispositivo
Software
Como instalar Docker en ubuntu
Para la instalación del software empezaremos por instalar el gestor de contenedores de Docker en servidor linux basado en ubuntu
sudo apt update
sudo apt upgrade
sudo apt install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt upgrade
sudo apt install docker-ce docker-ce-cli containerd.io
Por ultimo verificamos que esta instalado correctamente
sudo docker --version
Como instalar Node Red en Docker
Para el software de inserción en base de datos instalaremos Node Red que es un software de open source que nos permitiré conectar con infinidad de protocolos de comunicación e insertar en base de datos
docker run -d --name mynodered -p 1880:1880 --restart unless-stopped nodered/node-red
Una vez ya instalado podemos proceder a acceder a los datos que nos comparte el dispositivo de Tasmota con su servidor web en mi caso esta almacenada en esta dirección IP (se recomienda usar la guía de configuración de la conexión por wifi del dispositivo para configurarlo correctamente):
El cual nos dará la información sobre el enchufe:
Para obtener los datos nos podemos dirigir a la siguiente:
http://192.168.0.34/cm?cmnd=STATUS%200
El cual podemos ver que esta toda la información en un JSON
"ENERGY": {
"TotalStartTime": "2024-11-06T09:10:05",
"Total": 1.058,
"Yesterday": 0.268,
"Today": 0.206,
"Power": 0,
"ApparentPower": 0,
"ReactivePower": 0,
"Factor": 0,
"Voltage": 208,
"Current": 0
},
Ahora procederemos a programar la función ciclica que recopilará la información para insertarla en la base de datos para ello haremos lo siguiente:
-Añadimos nodo que se ejecuta cada 3 segundos
-Normalizamos la hora para poder insertarla en la base de datos
-Obtenemos los datos mediante una petición HTTP mediante el método «GET»
-Extraemos los datos que nos interesa del JSON
-Creamos la consulta con los datos extraídos
-Insertamos en nuestro servidor
-Mostramos en consola para comprobar que todo funciona correctamente
[
{
"id": "72594c6cf3e187c5",
"type": "debug",
"z": "a7cda68e269cad0e",
"name": "debug 25",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 720,
"y": 940,
"wires": []
},
{
"id": "e49938d64834e868",
"type": "http request",
"z": "a7cda68e269cad0e",
"name": "",
"method": "GET",
"ret": "txt",
"paytoqs": "ignore",
"url": "http://192.168.0.34/cm?cmnd=STATUS%200",
"tls": "",
"persist": false,
"proxy": "",
"insecureHTTPParser": false,
"authType": "",
"senderr": false,
"headers": [],
"x": 530,
"y": 880,
"wires": [
[
"273d22936082a16a"
]
]
},
{
"id": "7957f9fe99d9eee6",
"type": "inject",
"z": "a7cda68e269cad0e",
"name": "",
"props": [
{
"p": "date",
"v": "iso",
"vt": "date"
}
],
"repeat": "3",
"crontab": "",
"once": true,
"onceDelay": "3",
"topic": "",
"x": 200,
"y": 880,
"wires": [
[
"23d3e98baab1ce6a"
]
]
},
{
"id": "273d22936082a16a",
"type": "function",
"z": "a7cda68e269cad0e",
"name": "Extraer datos del JSON",
"func": "// Convierte msg.payload a string si no lo es\nvar payloadStr = msg.payload.toString();\n\n// Elimina espacios en blanco al inicio y al final\npayloadStr = payloadStr.trim();\n\n// Si el payload comienza y termina con comillas dobles, las elimina\nif (payloadStr.startsWith('\"') && payloadStr.endsWith('\"')) {\n payloadStr = payloadStr.substring(1, payloadStr.length - 1);\n}\n\n// Reemplaza las comillas dobles escapadas por comillas dobles reales\npayloadStr = payloadStr.replace(/\\\\\"/g, '\"');\n\ntry {\n // Parsea el string a objeto JSON\n var payloadObj = JSON.parse(payloadStr);\n\n // Verifica si existen los datos de ENERGY\n if (payloadObj.StatusSNS && payloadObj.StatusSNS.ENERGY) {\n // Asigna los valores de ENERGY al payload\n msg.payload = payloadObj.StatusSNS.ENERGY;\n } else {\n node.error(\"No se encontró 'StatusSNS.ENERGY' en el payload\", msg);\n return null;\n }\n} catch (e) {\n // Maneja errores de parseo\n node.error(\"Error al parsear JSON: \" + e.message, msg);\n return null;\n}\n\nreturn msg;\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 250,
"y": 940,
"wires": [
[
"456e7462c5d896e9"
]
]
},
{
"id": "4086d7eafeeaf2bd",
"type": "mysql-r2",
"z": "a7cda68e269cad0e",
"name": "",
"host": "192.168.0.125",
"database": "****",
"username": "****",
"password": "****",
"sql": "",
"port": "****",
"pooling": false,
"waitForConnections": true,
"connectionLimit": "10",
"queueTimeout": "10000",
"x": 580,
"y": 940,
"wires": [
[
"72594c6cf3e187c5"
]
]
},
{
"id": "456e7462c5d896e9",
"type": "template",
"z": "a7cda68e269cad0e",
"name": "",
"field": "sql",
"fieldType": "msg",
"format": "handlebars",
"syntax": "mustache",
"template": "INSERT INTO `Grafana`.`Consume` (`con_date`, `con_voltage`, `con_current`, `con_power`, `con_AppPower`, `con_ReactPower`, `con_PowerFactor`, `con_EnergyToday`) VALUES ('{{date}}', '{{payload.Voltage}}', {{payload.Current}}, {{payload.Power}}, {{payload.ApparentPower}}, {{payload.ReactivePower}}, {{payload.Factor}}, {{payload.Today}});",
"output": "str",
"x": 440,
"y": 940,
"wires": [
[
"4086d7eafeeaf2bd"
]
]
},
{
"id": "23d3e98baab1ce6a",
"type": "function",
"z": "a7cda68e269cad0e",
"name": "Normalizar hora",
"func": "// Obtiene la fecha original de msg.date\nvar originalDate = msg.date;\n\n// Verifica si msg.date está definido\nif (!originalDate) {\n node.error(\"msg.date no está definido.\", msg);\n return null;\n}\n\n// Convierte la cadena de fecha a un objeto Date\nvar dateObj = new Date(originalDate);\n\n// Verifica si la fecha es válida\nif (isNaN(dateObj.getTime())) {\n node.error(\"La fecha proporcionada no es válida: \" + originalDate, msg);\n return null;\n}\n\n// Extrae los componentes de la fecha y hora en UTC\nvar year = dateObj.getUTCFullYear();\nvar month = ('0' + (dateObj.getUTCMonth() + 1)).slice(-2);\nvar day = ('0' + dateObj.getUTCDate()).slice(-2);\nvar hours = ('0' + dateObj.getUTCHours()).slice(-2);\nvar minutes = ('0' + dateObj.getUTCMinutes()).slice(-2);\nvar seconds = ('0' + dateObj.getUTCSeconds()).slice(-2);\n\n// Formatea la fecha al formato deseado\nmsg.date = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;\n\nreturn msg;\n",
"outputs": 1,
"timeout": 0,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 360,
"y": 880,
"wires": [
[
"e49938d64834e868"
]
]
}
]
Como instalar Grafana
Para la visualización utilizaremos Grafana el cual tiene una gran variedad de herramientas de visualización en mi caso hare que sea accesible por el puerto 1882
docker run -d --name mygrafana -p 1882:3000 --restart unless-stopped grafana/grafana
Una vez listo yo recomiendo realizar las siguientes configuraciones para poder acceder a los gráficos sin tener que iniciar sesión cada vez que se arranque el visualizador
docker exec -it -u root mygrafana /bin/sh
vi /etc/grafana/grafana.ini
Ahora habilitaremos la visualización de forma anonima modificando estas lineas:
[auth.anonymous]
enabled = true
org_role = Viewer
Por último habilitaremos el uso de graficas en iframes
[security]
allow_embedding = true
Una vez listo configuraremos nuestras gráficas como deseemos en mi caso tengo estas gráficas
Como instalar FUXA
Para la instalación de Fuxa como SCADA de código abierto al tener ya instalado docker es tan sencillo como hacer una pull del repositorio de github y hacer que se autoarranque al iniciar el servidor
docker run -d \
--name fuxa \
-p 1881:1881 \
-v fuxa_appdata:/usr/src/app/FUXA/server/_appdata \
-v fuxa_db:/usr/src/app/FUXA/server/_db \
-v fuxa_logs:/usr/src/app/FUXA/server/_logs \
-v fuxa_shapes:/usr/src/app/FUXA/client/assets/lib/svgeditor/shapes \
-v fuxa_images:/usr/src/app/FUXA/server/_images \
--restart unless-stopped \
frangoteam/fuxa:latest
Ya tenemos listo ahora es tan facil como crear un proyecto usar el objeto «iFrame» de Fuxa y ya podemos empezar a visualizar los datos