Saber que sucede en un sistema eléctrico es y será un gran misterio. Para comprender un poco mas este proceso de equilibrio entre producción, almacenaje y consumo; y con ánimo experimental y educativo, desarrollé este Voltímetro de bajo coste que monitoriza un sistema fotovoltaico mínimo a 12V; con una placa de 60 W , un regulador de 10A -con toma de corriente a 5V en usb- y una batería de 12V-14Ah. Este sistema alimenta una raspberry pi, el arduino a través del usb a 5V, y además dos lamparas de 1 W y 2 W a 12V directamente. Cualquier cosa mas grande necesitara elementos de protección aquí no empleados.

Las
tres resistencias en arco, arduino con señal y tierra.
El truco de hardware consiste en colocar tres resistencias de 2k Ohms en serie, desde el polo positivo al negativo de la batería -lugar que vamos a monitorizar- y tomar la medida entre la segunda y la tercera resistencia, conectando además la tierra en el mismo borne negativo de la batería. Al segmentar la corriente en tres y utilizar el código del archivo voltimetro.ino enviamos la lectura al serial cada cierto tiempo. Para calibrar la medida empleamos un voltímetro de mano hasta ajustar el factorCal o factor de calibración, ajustándonos a la peculiaridad de nuestras soldaduras de estaño, - Las mías bastante chapuceras, pero efectivas al fin y al cabo.-
Batería,
regulador de carga, raspi y lámpara de 2 W
Este curioso conjunto se conecta al arduino mediante su cable usb. El arduino, en este caso, realiza una lectura cada dos segundos. Para ello empleamos el siguiente código que cargaremos mediante el IDE.
Voltimetro.ino
_______________________________________________________
const int pinVoltaje = A0;
float factorCal = 0.967;
void setup() {
Serial.begin(9600);
}
void loop() {
float suma = 0;
for(int i=0; i<30; i++) { suma += analogRead(pinVoltaje); delay(200); }
float vBat = (suma / 30.0) * (5.0 / 1024.0) * 3.0 * factorCal;
// Enviamos un XML por el USB
Serial.print("<solar><v>");
Serial.print(vBat, 2);
Serial.print("</v><m>");
Serial.print(millis());
Serial.println("</m></solar>");
delay(200); // Una lectura cada 2 segundos
}
____________________________________________________________
Regulador
de carga con toma 5V usb y Raspberry pi 
Ahora necesitamos capturar los datos del arduino y crear una base de datos con la información que iremos recabando. Para ello disponemos de este script de python que funcionara una vez cargadas las librerías pertinentes.
capturador.py
_______________________________________________
import serial
import sqlite3
import json
import time
import os
# --- CONFIGURACIÓN ---
DB_PATH = '/var/www/html/fv/datos.db' # En este ejemplo publicamos todo con apache2 en dicha ruta
SERIAL_PORT = '/dev/ttyUSB0' # pon aquí tu dispositivo usb arduino
BAUD_RATE = 9600
INTERVALO = 5 # Segundos entre capturas
def conectar_bd():
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute('''CREATE TABLE IF NOT EXISTS lecturas
(id INTEGER PRIMARY KEY AUTOINCREMENT,
fecha DATETIME DEFAULT CURRENT_TIMESTAMP,
voltaje REAL)''')
conn.commit()
return conn
def capturar():
print(f"Iniciando capturador en {SERIAL_PORT} (Intervalo: {INTERVALO}s)...")
try:
ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=5)
time.sleep(2)
ser.reset_input_buffer()
except serial.SerialException as e:
print(f"Error abriendo puerto: {e}")
return
conn = conectar_bd()
cursor = conn.cursor()
while True:
try:
line = ser.readline().decode('utf-8', errors='ignore').strip()
if line and line.startswith('{') and line.endswith('}'):
data = json.loads(line)
if 'v' in data:
v = float(data['v'])
cursor.execute("INSERT INTO lecturas (voltaje) VALUES (?)", (v,))
conn.commit()
print(f"[{time.strftime('%H:%M:%S')}] Voltaje guardado: {v}V")
# Esperamos los 5 segundos antes de la siguiente lectura
time.sleep(INTERVALO)
except Exception as e:
print(f"Error: {e}")
time.sleep(1)
if __name__ == "__main__":
directorio = os.path.dirname(DB_PATH)
if not os.path.exists(directorio):
os.makedirs(directorio)
os.chmod(directorio, 0o777) # Intentar dar permisos automáticamente
capturar()
____________________________________________________
A veces los datos tardan en cargar en la gráfica por lo que empleamos este archivo php para tomar una lectura instantánea rápida:
valor_actual.php
____________________________________
<?php
$db = new PDO('sqlite:/var/www/html/fv/datos.db');
$res = $db->query("SELECT voltaje FROM lecturas ORDER BY id DESC LIMIT 1")->fetch();
echo $res ? number_format($res['voltaje'], 2) : "--.--";
?>
_______________________________________________
Gráfica de 5 días mostrando ciclos de carga y descarga.
El
archivo que genera la gráfica es el siguiente:
grafica.php
_____________________________________________
<?php
header("Content-Type: image/png");
date_default_timezone_set('Atlantic/Canary'); // Ajusta a tu zona horaria local
try {
// 1. Conexión a la base de datos en la nueva ruta fv4
$db = new PDO('sqlite:/var/www/html/fv/datos.db');
// 2. Obtener rango de tiempo (por defecto 6 horas si no se especifica)
$horas = isset($_GET['h']) ? intval($_GET['h']) : 6;
$query = "SELECT * FROM lecturas
WHERE fecha >= datetime('now', '-$horas hours')
ORDER BY id ASC";
$datos = $db->query($query)->fetchAll(PDO::FETCH_ASSOC);
$ultimo = end($datos);
$totalPuntos = count($datos);
// 3. Configuración del lienzo
$ancho = 600; $alto = 330;
$img = imagecreatetruecolor($ancho, $alto);
$blanco = imagecolorallocate($img, 255, 255, 255);
$rojo = imagecolorallocate($img, 220, 53, 69);
$gris = imagecolorallocate($img, 230, 230, 230); // Líneas de rejilla
$grisFuerte = imagecolorallocate($img, 180, 180, 180);
$negro = imagecolorallocate($img, 33, 37, 41);
imagefill($img, 0, 0, $blanco);
// 4. Dibujar rejilla horizontal (11V a 15V)
for ($v = 11; $v <= 15; $v++) {
$yPos = 250 - (($v - 11) * 50);
imageline($img, 50, $yPos, 550, $yPos, $gris);
imagestring($img, 2, 15, $yPos - 7, $v . "V", $negro);
}
imageline($img, 50, 250, 550, 250, $grisFuerte); // Eje X
imageline($img, 50, 50, 50, 250, $grisFuerte); // Eje Y
// 5. Dibujar la línea de datos
$prevX = null; $prevV = null;
if ($totalPuntos > 1) {
foreach($datos as $i => $fila) {
// Calcular X proporcional al tiempo
$x = 50 + ($i * (500 / ($totalPuntos - 1)));
// Calcular Y (Voltaje limitado a la escala)
$vVal = $fila['voltaje'];
if($vVal < 11) $vVal = 11;
if($vVal > 15) $vVal = 15;
$yV = 250 - (($vVal - 11) * 50);
if ($prevX !== null) {
imageline($img, $prevX, $prevV, $x, $yV, $rojo);
}
// 6. Marcas de tiempo dinámicas en el eje X
// Mostramos aproximadamente 5 etiquetas de tiempo
if ($i % floor($totalPuntos / 5 + 1) == 0) {
$formato = ($horas > 24) ? "d/m H:i" : "H:i";
$fechaEtiqueta = date($formato, strtotime($fila['fecha']));
imageline($img, $x, 250, $x, 255, $negro);
imagestring($img, 1, $x - 15, 265, $fechaEtiqueta, $negro);
}
$prevX = $x; $prevV = $yV;
}
}
// 7. Cuadro de Info flotante (Estilo oscuro)
if ($ultimo) {
$boxColor = imagecolorallocatealpha($img, 0, 0, 0, 80);
imagefilledrectangle($img, 380, 10, 580, 60, $boxColor);
imagestring($img, 3, 395, 18, "ACTUAL: " . number_format($ultimo['voltaje'], 2) . "V", $blanco);
imagestring($img, 1, 395, 40, "Rango: " . $horas . "h | " . date("H:i:s"), $blanco);
} else {
imagestring($img, 4, 180, 140, "SIN DATOS EN ESTE RANGO", $negro);
}
imagepng($img);
imagedestroy($img);
} catch (Exception $e) {
// Imagen de error
$imgErr = imagecreatetruecolor(600, 50);
imagefill($imgErr, 0, 0, imagecolorallocate($imgErr, 150, 0, 0));
imagestring($imgErr, 3, 10, 15, "ERROR: " . $e->getMessage(), imagecolorallocate($imgErr, 255, 255, 255));
imagepng($imgErr);
imagedestroy($imgErr);
}
?>
____________________________________________________
Este código no funciona para intervalos de mas de 120 horas, o algo así. Probablemente algún limite en el tamaño de consulta. La resolución de nuestros datos es muy alta, es decir, cada poco tiempo y la base de datos crece mucho.
| Gráfica de 24 horas |
Para disponer de una página donde leer los datos tenemos el siguiente archivo:
index.html
___________________________________________________
*<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Voltímetro sistema fotovoltaica</title>
<style>
body { font-family: sans-serif; background: #f4f4f9; text-align: center; }
.container { max-width: 800px; margin: 20px auto; background: white; padding: 20px; border-radius: 15px; box-shadow: 0 4px 10px rgba(0,0,0,0.1); }
.valor-actual { font-size: 4rem; font-weight: bold; color: #dc3545; }
.controles { margin: 20px 0; padding: 15px; background: #eee; border-radius: 8px; }
img { width: 100%; border: 1px solid #ddd; }
</style>
</head>
<body>
<div class="container">
<h1>Magec 0.00</h1>
<div>
<p>panel 60W + regulador 10A + bateria 12V-14Ah</p>
<div class="valor-actual"><span id="num-voltaje">--.--</span><span style="font-size:2rem; color:#666"> V</span></div>
</div>
<div class="controles">
<strong>Consultar histórico:</strong>
<select id="rango" onchange="cambiarRango()">
<option value="1">Última hora</option>
<option value="6" selected>Últimas 6 horas</option>
<option value="24">Últimas 24 horas</option>
<option value="168">Última semana</option>
</select>
<strong>Consulta Personalizada:</strong><br><br>
Desde: <input type="datetime-local" id="fecha_inicio">
Hasta: <input type="datetime-local" id="fecha_fin">
<button onclick="consultarIntervalo()">Consultar</button>
<button onclick="location.reload()">Reset (6h)</button>
</div>
<script>
function consultarIntervalo() {
const inicio = document.getElementById('fecha_inicio').value;
const fin = document.getElementById('fecha_fin').value;
if(!inicio || !fin) {
alert("Por favor, selecciona ambas fechas.");
return;
}
// Enviamos las fechas al PHP por la URL
const url = `grafica.php?desde=${inicio}&hasta=${fin}&t=${new Date().getTime()}`;
document.getElementById('imgGrafica').src = url;
}
</script>
<img id="imgGrafica" src="grafica.php?h=6" alt="Gráfica">
</div>
<script>
function actualizarValor() {
// Lee el número del mini-php y lo pone en el HTML
fetch('valor_actual.php')
.then(response => response.text())
.then(data => document.getElementById('num-voltaje').innerText = data);
}
function cambiarRango() {
const horas = document.getElementById('rango').value;
document.getElementById('imgGrafica').src = 'grafica.php?h=' + horas + '&t=' + new Date().getTime();
}
// Actualiza el número cada 5 segundos
setInterval(actualizarValor, 5000);
// Actualiza la gráfica cada 30 segundos (o al cambiar el selector)
setInterval(cambiarRango, 30000);
actualizarValor(); // Carga inicial
</script>
</body>
</html>
__________________________________
| Vista del interfaz web de monitoreo / voltímetro Magec 0.00 |
Para que el archivo de captura de datos persista al reiniciar puedes añadir estas lineas a tu archivo rc.local mediante el comando:
sudo nano /etc/rc.local , y añadimos estas lineas.
__________________________
sudo python3 /your/sript/location/folder/capturador.py
&
exit 0
____________________
En conclusión y a modo de opinión personal, la gestión descentralizada y distribuida de los sistemas eléctricos -pequeños, medianos y grandes- es un terreno de trabajo por cultivar, siendo necesario la experimentación práctica sobre sistemas reales. La capa física en la que operamos, entendiendo esta como la arquitectura e infraestructura actual, sistemas de generación, transporte y consumo energético, precisa de una atención pública y auditorable de la capa lógica, entendiendo esta como la arquitectura y elementos físicos que regulan de forma automatizada mediante el empleo de software especializado todo el sistema, con vistas a una racionalización y planificación de las necesidades energéticas de la población.
Sin embargo, en tanto y cuanto los intereses de las empresas prevalezcan sobre los de la población en su conjunto, será del todo imposible desarrollar las condiciones anteriormente mencionadas, existiendo por necesidad una gigantesca contradicción sobre los medios de los que ya disponemos -incluye centrales térmicas, fotovoltaica, ventiladores o molinos, desaladoras, bombas impulsoras de agua, etc.- y el servicio que se presta, no con vistas a abastecer a la población, sino al acceso al mismo por parte de toda la población, creando contrastadas situaciones de pobreza energética junto a derroche irresponsable prácticamente en el mismo territorio.
Esta situación , en su desarrollo actual, no deja otra opción que trabajar para poner en servicio real todo el despliegue de medios tecnológicos que el sistema económico actual, capitalista, ha desarrollado tan magníficamente, pero que en su fase superior pretende acumular en manos de pocas personas dispuestas a vivir a costa de los demás. Consciente de que en la actualidad dicha lucha se libra todos los días, encontramos pues en el derecho a la energía, el agua potable y el bienestar de toda la población un apasionante frente en el que sin duda queda mucho por hacer, pudiendo alcanzar para la humanidad -y en particular para nuestro pequeño territorio archipielágico- cotas de bienestar nunca antes alcanzadas, verdadera Revolución con las condiciones materiales ya preparadas.
¿Te interesa lo que ves? Si quieres saber mas acerca de esta experiencia, contacta:
habitainer (arroba) gmail (punto) com
Creative Commons Licensed Work.
Julio 2026 – Luís Rodríguez Alonso.
