25 jun 2026

MAGEC 0.00: Energía Fotovoltaica + Arduino y Raspberry Pi

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()


____________________________________________________



Placa solar


 

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.