• Autor de la entrada:
  • Tiempo de lectura:12 minutos de lectura

Debido a la canibalización de precios en energía solar, controlar un inversor según el precio de REE y un protocolo como Modbus cobra sentido. Utilizando la plataforma Arduino se consulta el precio en la API de REE y se envían órdenes al inversor.

Introducción

Quien posee una instalación fotovoltaica doméstica con vertido a red, puede ver (según su contrato) que durante determinadas horas paga por inyectar energía a la red. Algo que ocurre especialmente en horas centrales del día debido a precios negativos de la energía.

Las instalaciones de generación fotovoltaica han experimentado un crecimiento casi exponencial desde 2019. Este aumento ha provocado un efecto conocido como «canibalización», donde la cantidad de energía solar que podría generarse es tal que no hay demanda para consumirla. El exceso de oferta hunde los precios, haciendo que el productor solar vea sus ingresos desaparecer precisamente apostando por dicha tecnología. Actualmente, esto ocurre en primavera y otoño, cuando la generación solar ya es importante, pero sin consumos como calefacción o aire acondicionado.

Histórico potencia fotovoltaica instalada en España
Potencia fotovoltaica en España. Elaboración propia.

Aprovechando que las instalaciones con potencia inferior a 100kW no tienen compromisos de generación con la Red Eléctrica*, lo más inteligente es detener el vertido cuando el precio no sea interesante.

De esta manera no solo se evita perder dinero, también alargar la vida de la electrónica de potencia. Como se ve al calcular las pérdidas de un inversor monofásico, estas crecen con el cuadrado de la corriente, por ello tiene sentido reducirlas disminuyendo la producción mediante un protocolo como Modbus en función del precio de REE.

Se muestra de forma gráfica la idea que se pretende conseguir.

Diagrama control de inversor mediante Modbus TCP según el precio de la energía
Diagrama control de inversor mediante Modbus TCP según el precio de la energía. Elaboración propia.

* Cualquier instalación eléctrica tiene derechos, pero también obligaciones, este artículo tiene un interés puramente académico y depende de cada individuo estar al tanto de sus obligaciones.

Formas de comandar un inversor

Existen diferentes formas de comandar un inversor, en general se encuentran las siguientes:

  • Entrada digital: Activando una entrada digital puede ejecutarse alguna acción o pasar a un determinado modo de funcionamiento.
  • Conexión TCP: Al conectar el inversor a una red es posible acceder a él mediante distintos protocolos: una API propia del fabricante, Modbus TCP o MQTT.
  • Servidor web: Es un caso particular del anterior, pero merece apartado propio. Muchos equipos corren un servidor web donde se aloja una página web. Al entrar desde un navegador es posible realizar cambios y lecturas, cuya facilidad dependerá del cariño que haya puesto la marca en diseñar la página.
  • Puerto serie: Es uno de los métodos más utilizados históricamente. Sobre una conexión RS-232, RS-422, RS-485, CAN-BUS, etc. es posible la lectura y escritura de parámetros o estados.
Configuración antivertido en inversor Fronius PRIMO mediante servidor Web
Configuración antivertido en inversor Fronius PRIMO mediante servidor Web

Sin embargo, que exista una vía de acceso real depende de cada fabricante. Una capa hardware estandarizada no asegura que el protocolo de comunicación sea público. Cada método requiere estudiar la documentación y en ocasiones ponerse en contacto con el fabricante bajo NDA. A veces es sencillamente imposible para un usuario utilizar un determinado método.

En este inversor en concreto es posible configurar una entrada para que fije la energía máxima que puede inyectar a red. Bajo TCP ofrece un servidor web, MQTT y Modbus.

Este último método: Modbus TCP, será el utilizado en este artículo. El motivo no es otro que su simplicidad, un microcontrolador sencillo puede implementarlo. Tampoco es necesaria una conexión física con el equipo. El inconveniente es su bajo nivel de seguridad, y es que cualquier persona conectada a la misma red que el inversor podría manipularlo.

Inversor Fronius Primo GEN24 y Modbus

El inversor objetivo es un Fronius Primo GEN24 8.0 PLUS. Si bien cada inversor es un mundo, muchos comparten características y funciones, especialmente las relacionadas con funcionalidad. Por ello, se anima a que cada usuario investigue las posibilidades de su instalación y los componentes que la forman.

Inversor Fronius tipo PRIMO
Inversor Fronius tipo PRIMO

Como es de esperar este modelo permite muchas configuraciones e integraciones, entre ellas la generación solar con conexión y vertido a red.

Esquema posible modo de operación inversor Fronius PRIMO
Esquema posible modo de operación inversor Fronius PRIMO. Fuente: Fronius.

La última parte de este apartado será obviamente el mapa de registros. Este es ofrecido por Fronius bajo petición en este enlace. Cada individuo debe ser responsable de la obtención y protección de dicha documentación bajo los términos que impone Fronius. Sobra decir hoy día, en internet, es posible encontrar casi cualquier tipo de información.

ModBus TCP en Arduino

Modbus es sumamente sencillo y transparente, el protocolo conecta distintos equipos electrónicos de automatización industrial, es fácilmente manejable tanto por humanos como por microcontroladores sencillos. En su creación durante el final de la década de los 70, a la automatización distribuida todavía le quedaba un largo camino hasta la Industria 4.0. El canal físico sobre el que funciona también suele ser simple, siendo los clásicos canales asíncronos RS-232, RS-422 y RS-485 los más utilizados. Toda esta simpleza tiene un precio, su seguridad se basa en la inaccesibilidad de las instalaciones y el desconocimiento del protocolo. Solamente las implementaciones sobre TCP gozan de cierta seguridad, la cual no es más que la propia que puede ofrecer este tipo de redes.

Aunque existen librerías Modbus TCP para Arduino se implementará una propia. Será muy simple, conteniendo lo estrictamente necesario, que es este caso será escribir y leer HoldingRegisters.

Escribir en Holding Registers

El código a continuación realiza una escritura a partir del Holding Register startAddress el número de registros indicado por reg2write. La información a escribir llega desde el buffer tx_buffer. Dicho de otra forma, tx_buffer es transmitido a los Holding Registers.

A diferencia del código del repositorio aquí se omiten las comprobaciones de datos, centrando la atención en la implementación del protocolo.

bool writeHoldingRegisters(WiFiClient& client, uint16_t startAddress, const uint16_t* tx_buffer, uint16_t regs2write){
  /*** Check connection and verify data ***/

  uint8_t byteCount = regs2write * 2;
  uint16_t packetSize = 13 + byteCount; // 13 = header + byteCount byte

  byte request[260]; //Safe buffer

  //Modbus TCP header
  request[0] = 0x00; request[1] = 0x02;              // Transaction ID
  request[2] = 0x00; request[3] = 0x00;              // Protocol ID
  request[4] = 0x00; request[5] = 7 + byteCount;     // Length = 7 + byte count
  request[6] = 0x01;                                 // Unit ID
  request[7] = 0x10;                                 // Function code (write multiple registers)
  request[8] = startAddress >> 8;
  request[9] = startAddress & 0xFF;
  request[10] = regs2write >> 8;
  request[11] = regs2write & 0xFF;
  request[12] = byteCount;

  //Load write buffer to transaction buffer
  for (int i = 0; i < regs2write; ++i){
    request[13 + i * 2]     = tx_buffer[i] >> 8;
    request[13 + i * 2 + 1] = tx_buffer[i] & 0xFF;
  }

  client.write(request, packetSize);

  /*** Wait response with timeout ***/

  //Get response
  byte response[12]; //Expected response size
  int index = 0;
  while (client.available() && index < sizeof(response)){
    response[index++] = client.read();
  }

  /*** Verify response data ***/

  return true;
}

Leer Holding Registers

Al contrario que en la escritura, esta función recoge en el buffer buff_rx el contenido solicitado. Se lee desde la dirección startAddress el número de registros indicados por regs2read. De nuevo se omiten las comprobaciones sobre la integridad de datos y transmisión.

bool readHoldingRegisters(WiFiClient& client, uint16_t startAddress, uint16_t* buff_rx, uint16_t regs2read){
  /*** Check connection ***/

  //Modbus TCP ReadHoldingReg packet
  byte request[12] = {
    0x00, 0x01,             // Transaction ID
    0x00, 0x00,             // Protocol ID
    0x00, 0x06,             // Length
    0x01,                   // Unit ID
    0x03,                   // Function code (read holding registers)
    (byte)(startAddress >> 8), (byte)(startAddress & 0xFF),   // Start address
    (byte)(regs2read >> 8),  (byte)(regs2read & 0xFF)         // Registers to read
  };

  client.write(request, sizeof(request));

  /*** Wait response with timeout ***/

  //Move response to aux buffer
  int index = 0;
  byte response[260];  // 125 registers (250 bytes + header)
  while (client.available() && index < sizeof(response)) {
    response[index++] = client.read();
  }

  /*** Verify response data ***/

  //Get registers
  for (int i = 0; i < regs2read; ++i) {
    int offset = 9 + i * 2;
    buff_rx[i] = (response[offset] << 8) | response[offset + 1];
  }

  return true;
}

Depurar aplicación Modbus

Antes de enviar comandos directamente a un equipo conviene hacer pruebas en entornos controlados. Una forma muy recomendable para depurar Modbus es utilizar la aplicación ModRSsim2. Esta aplicación de Doug Lyons puede encontrarse en este enlace de Sourceforge, donde es posible descargar ejecutable y código fuente.

ModRSsim2 configurado para depurar Modbus TCP
ModRSsim2 configurado para depurar Modbus TCP

Una vez configurado es posible realizar operaciones de lectura/escritura contra la aplicación, ya sea desde una conexión TCP/IP como a través de un puerto serie tipo RS-232.

El siguiente código realiza una lectura de los diez primeros HoldingRegisters, incrementa su valor en una unidad y escribe el resultado en los mismos. Al ejecutar este código se verá desde ModRSsim2 como se incrementan estos registros automáticamente.

  if (client.connect(fronius_ip, fronius_port)){
    if(readHoldingRegisters(client, 0, registers, 10)){
      for (int i = 0; i < 10; ++i) {
        Serial.printf(" %04X", registers[i]);
      }
      Serial.println("");

      for(int i = 0; i < 10; i++){
        registers[i]++;
      }
      writeHoldingRegisters(client, 0, registers, 10);
    }
  }

Como resultado:

Validación de librería Modbus TCP de Arduino mediante ModRSsim2
Validación de librería Modbus TCP de Arduino mediante ModRSsim2.

Obtener precios de electricidad en España

La automatización que se plantea depende fundamentalmente del precio al que se encuentre la electricidad en un determinado momento. En España es posible obtener precios oficiales de electricidad desde Red Eléctrica de España (REE) y desde el Operador del Mercado Ibérico de la Electricidad (OMIE).

Mientras las webs proporcionan un entorno visual agradable para que un humano consulte información, las máquinas no necesitan tales decoraciones. Por ello existen protocolos de comunicación máquina-máquina (M2M), centrando el uso de recursos en transmitir únicamente datos.

Este canal de comunicación se suele denominar API, acrónimo de «interface de programación de aplicaciones». Las APIs suelen estar documentadas de forma que sean entendidas por un programador, en ocasiones incluyendo ejemplos de uso. En general es información de «especialistas» para «especialistas», y por ello será habitual tener que acabar realizando ensayo-error para conseguir algo.

API de REE y precios de la electricidad

La documentación de la API de REE se encuentra en este enlace. Esta documentación no incluye una descripción explícita de la información obtenida con cada consulta, teniendo que conocer de antemano la terminología del gremio.

La URL para obtener los precios diarios se forma de la siguiente manera:

https://apidatos.ree.es/es/datos/mercados/precios-mercados-tiempo-real?start_date=YYYY-MM-DDT00:00&end_date=YYYY-MM-DDT23:59&time_trunc=hour

Donde

  • YYYY: Año a consultar, por ejemplo 2024.
  • MM: Mes a consultar, por ejemplo 12.
  • DD: Día a consultar, por ejemplo 31.

Con la fecha de ejemplo propuesta 31/12/2024 se construye la siguiente URL:

https://apidatos.ree.es/es/datos/mercados/precios-mercados-tiempo-real?start_date=2024-12-31T00:00&end_date=2024-12-31T23:59&time_trunc=hour

Fecha nada caprichosa, ya que es la última con precios negociados hora a hora. Desde el día siguiente y hasta ahora se utilizan intervalos de 15 minutos, como puede verse en la siguiente consulta.

https://apidatos.ree.es/es/datos/mercados/precios-mercados-tiempo-real?start_date=2025-01-01T00:00&end_date=2025-01-01T23:59&time_trunc=hour

Como anécdota, este cambio hizo fallar automatizaciones que dependían de esta API y no fueron preparadas para el cambio (incluyendo, como no, la que estás leyendo). Algunas automatizaciones tardaron en detectar este problema varios meses. La entrada de la primavera y la primera semana de precios negativos reveló el error, cuando el inversor seguía generando sin excepciones.

Consultar API de REE en Arduino

Construir la URL necesaria de forma dinámica en C puede hacerse de la siguiente manera.

#include <string.h>
char url_ree_api[255];

const char* url_ree_base = "https://apidatos.ree.es/";
const char* url_ree_precios = "es/datos/mercados/precios-mercados-tiempo-real?";
const char* url_ree_start_date = "start_date=";
const char* url_ree_end_date = "&end_date=";
const char* url_ree_timetrunc = "&time_trunc=hour";

sprintf(url_ree_api, "%s%s%s%04d-%02d-%02dT00:00%s%04d-%02d-%02dT23:59%s", 
                        url_ree_base, url_ree_precios, url_ree_start_date,
                        year, month, day,
                        url_ree_end_date,
                        year, month, day,
                        url_ree_timetrunc);

Con la URL lista se lanza la consulta a la API de REE desde Arduino:

#include <HTTPClient.h>

  HTTPClient http;
  http.begin(url_ree_api);

  int http_res = http.GET();
  if(http_res != 200){
    //Other response than 200 means an error to be handled
    Serial.print("HTTP Response: "); Serial.println(http_res);
  }

  //'json_in' gets the response request, including REE prices
  String json_in = http.getString(); 
  http.end();

Procesar la respuesta es laborioso, manejar un JSON en lugar de datos crudos es caro para un microcontrolador. Seguramente muchas plataformas Arduino que soporten conexión TCP-IP serán capaces de guardar en RAM el string recibido, pero no es una situación deseable. Tras la inclusión de precios cada 15 minutos, el JSON puede llegar a 11kB para un día entero. Una posible solución sería consultar el precio en intervalos de pocas horas, o incluso solamente para las horas centrales del día.

Sea cual sea la estrategia, con el JSON en memoria toca extraer los precios. La librería <ArduinoJson.h> permite hacer esto de forma fácil para el programador (no tanto para el microcontrolador).

#include <ArduinoJson.h>

  JsonDocument doc;
  deserializeJson(doc, json_in);

  JsonObject included_item =  doc["included"][1];

  JsonObject included_item_attributes = included_item["attributes"];
  JsonArray json_spot_prices = included_item_attributes["values"].as<JsonArray>();

  // The for loop only works with a full day record starting at 00:00h
  for(size_t i = 0; i < 96; i++){
    spot_price[i] = json_spot_prices[i]["value"];
  }

Integrando todo. Controlar un inversor mediante Modbus según precio de REE

Con todos los componentes funcionando no queda más que escribir una pequeña automatización. Esta comprueba si el precio de REE es negativo o positivo, deteniendo o reanudando la producción del inversor a través de Modbus.

  updateTimeInfo();
  float price_now = ree_today.get_price_at_time(timeinfo);
  Serial.print(&timeinfo, "%d/%m/%Y-%H:%M:%S=>");
  Serial.printf("Price: %f€, ", price_now);
  
  if(price_now < 0){
    Serial.printf("Stopping generation");
    fronius.stopGeneration();
  }else{
    Serial.printf("Resuming generation");
    fronius.resumeGeneration();
  }
  Serial.println("");

Aunque es una aplicación simple, puede ser la semilla de proyectos mucho más grandes. Una vez se tiene acceso al inversor, es posible controlar cualquier aspecto de este. El criterio para ejecutar acciones depende únicamente de la imaginación de cada uno.

Por otro lado, conocer cómo hacer consultas mediante API desde un microcontrolador abre un mundo de posibilidades. Los sistemas embebidos ya no acaban donde llegan sus sensores, ahora pueden obtener información de cualquier lugar del mundo.

Controlar inversor con Modbus TCP, API REE y Arduino en GitHub

Los archivos de diseño de este proyecto se repositan «Open Source» en GitHub.