Ir al contenido
JelinaJelina DocumentationDocumentación técnica
Volver al sitio oficial

Ejemplo: equilibrado de cargas de un conjunto de puntos de recarga

Este ejemplo combina capacidades genéricas de Edge-EMS Builder —exploración dinámica de datos, expresiones, disparadores y limitación de frecuencia— para crear una estrategia de equilibrado que admite un número variable de dispositivos sin modificar scripts. Las marcas y los prefijos son ilustrativos; sustitúyelos por los del proyecto.

Un cuadro de distribución alimenta varios puntos de recarga, mediante OCPP o protocolos propietarios, y cargas esenciales como iluminación o climatización. Su capacidad de entrada desde la red es fija. Si todos los puntos cargan a plena potencia, el consumo total puede superar la capacidad nominal, provocando disparos de protección o penalizaciones por exceso de potencia.

Objetivo: dar prioridad a las cargas esenciales y repartir la capacidad restante entre todos los puntos de recarga conectados. La corriente de cada punto se ajusta dinámicamente; la conexión o desconexión de uno provoca un nuevo equilibrio en segundos, y añadir otro no requiere modificar scripts.

El sistema considera dos elementos de medición fundamentales:

  • Grid_total_power: potencia total instantánea de entrada desde la red, incluidas todas las cargas del cuadro: esenciales, recarga y otras.
  • Las cargas esenciales ya están incluidas en esa medida. Por tanto, basta con controlar el consumo total de recarga para mantener la entrada del cuadro por debajo de la potencia nominal y dar prioridad a las cargas esenciales.
-- Margen disponible (kW) = (potencia nominal - potencia total de red medida) × factor de seguridad
-- El margen del 10% evita sobreimpulsos; la medida ya incluye las cargas esenciales y no deben restarse de nuevo
local avail_kw = (rated_kw - grid_kw) * 0.9
local per_A = (avail_kw / n) * 1000 / voltage_V -- Repartir por igual como corriente (A)

3. Descubrimiento dinámico: añadir puntos de recarga sin modificar scripts

Sección titulada «3. Descubrimiento dinámico: añadir puntos de recarga sin modificar scripts»

La estrategia no fija de antemano la cantidad de puntos. En cada ejecución, una consulta por lotes de prefijo y sufijo descubre los puntos conectados:

local online = {}
-- Explorar cada prefijo de marca; añadir o eliminar según sea necesario
for _, brandPrefix in ipairs({"VENDORA", "VENDORB"}) do
local statusMap = getLatestDataValuesByPrefixAndSuffix("Collection", brandPrefix, "_status")
if statusMap then
-- El resultado es una tabla con dataKey como clave; recorrer con pairs() y comparar con el centinela "__Null__"
for dataKey, dto in pairs(statusMap) do
if dto.DataValue ~= "__Null__" then
table.insert(online, dto)
end
end
end
end

Si los puntos adquiridos de un nuevo cargador siguen la convención de nombres indicada más adelante, se incorporará automáticamente al reparto en la siguiente activación, sin modificar el script ni reiniciar el servicio. Esta capacidad combina el modelo de datos y el motor Lua; no es una función específica de un escenario.

4. Modelo de disparo: actualización de datos y limitación de frecuencia en el script

Sección titulada «4. Modelo de disparo: actualización de datos y limitación de frecuencia en el script»

Un sondeo a periodo fijo puede producir escrituras redundantes aunque no cambien los datos. Incluir también la limitación de frecuencia en el disparador complica sus condiciones. Aquí se utiliza este modelo:

Activación por actualización de datos y limitación mediante marcas de tiempo en el script: el disparador detecta la actualización; el script controla la frecuencia real de envío de órdenes.

Actualización de datos → Condición cumplida → Script → Leer last_ctrl_ms (marca de la última ejecución)
→ Tiempo transcurrido < 5000ms: salir sin enviar órdenes
→ Tiempo transcurrido ≥ 5000ms: controlar → escribir → actualizar marca

Paso previo al despliegue: crea manualmente un punto agregado que actúe como memoria del script:

{
"dataKey": "last_ctrl_ms",
"dataName": "充电控制上次执行时间戳",
"expression": "return 0",
"collectionCycle": -1
}

En cada activación, el script lee los parámetros de red, comprueba la frecuencia, descubre puntos conectados, calcula la corriente disponible, decide para cada punto, actualiza la marca de tiempo y devuelve las órdenes. Las reglas son:

Condición Estado del punto de recarga Acción
Sobrecarga o corriente repartida < corriente mínima Cargando Detener la recarga (stop_transaction)
Sobrecarga o corriente repartida < corriente mínima Otro Omitir
Sin sobrecarga y corriente repartida ≥ corriente mínima Cargando Enviar nuevo límite de corriente (set_charging_profile)
Sin sobrecarga y corriente repartida ≥ corriente mínima Sin cargar Iniciar la recarga (start_transaction; requiere Tag de autorización válido)

Fragmento principal de la acción del disparador, que devuelve un array de descriptores de escritura:

local OffsetDateTime = require("OffsetDateTime")
local ratedKw = tonumber(${Grid_current} or 0) * tonumber(${Grid_voltage} or 0) * 0.001
local gridKw = tonumber(${Grid_total_power} or 0)
local overload = gridKw >= ratedKw
local voltage = tonumber(${Grid_voltage} or 1)
local minChargePower = tonumber(${min_charge_power} or 4200)
local resultList = {}
if #online > 0 then
local availKw = (ratedKw - gridKw) * 0.9
local perA = math.floor((availKw / #online) * 1000 / voltage)
local minA = math.floor(minChargePower / voltage)
for _, pile in ipairs(online) do
local prefix = pile.DataKey:gsub("_status$", "") -- Obtener el prefijo del punto de recarga a partir de dataKey, por ejemplo VENDORA01
if overload or perA < minA then
if pile.DataValue == "Charging" then
table.insert(resultList, buildCommonWriteMap("Collection", prefix .. "_type", "stop_transaction", nil, nil, false))
end
else
if pile.DataValue == "Charging" then
table.insert(resultList, buildCommonWriteMap("Collection", prefix .. "_periods", '[{"start":0,"limit":' .. perA .. '}]', nil, nil, false))
elseif ${id_tag} ~= "__Null__" then
table.insert(resultList, buildCommonWriteMap("Collection", prefix .. "_type", "start_transaction", nil, nil, false))
end
end
end
end
table.insert(resultList, buildCommonWriteMap("Aggregation", "last_ctrl_ms", OffsetDateTime.now():unixMilli(), nil, nil, false))
return resultList

6. Convención de nombres: requisito para la identificación por lotes

Sección titulada «6. Convención de nombres: requisito para la identificación por lotes»

El script explora puntos con el formato {prefijoDeMarca}{númeroDeDosDígitos}_{atributo}. Respeta estrictamente estas reglas:

Parte Regla Ejemplo
Prefijo de marca Mayúsculas, sin guiones bajos VENDORA, VENDORB; sustitúyelos por los identificadores reales de las marcas
Número Dos dígitos, completados con cero 01, 02…10
Atributo Consulta la tabla siguiente status, type

Ejemplos de puntos necesarios para cada cargador:

Atributo Ejemplo de dataKey completo Lectura/escritura Descripción
_status VENDORA01_status Lectura Estado del punto de recarga, utilizado para descubrirlo; si falta, no se detecta
_transaction_id VENDORA01_transaction_id Lectura ID de la transacción actual
_type VENDORA01_type Escritura Tipo de orden: start/stop/set_charging_profile
_periods VENDORA01_periods Escritura JSON de periodos de limitación, por ejemplo [{"start":0,"limit":16}]

Al añadir el sexto punto de la marca, integra sus datos con nombres como VENDORA06_status y VENDORA06_type; se incorporará automáticamente al reparto.

Escenario ¿Modificar el script? Operación
Añadir un punto de la misma marca No Añadir puntos adquiridos con la convención de nombres
Ajustar el límite nominal de capacidad No Modificar el valor del punto agregado Grid_current
Ajustar la potencia mínima de recarga No Modificar min_charge_power
Desconexión temporal de un punto No Al quedar _status sin valor, se excluye automáticamente de la lista de conectados
Añadir una marca Sí Añadir un prefijo a la lista, por ejemplo {"VENDORA", "VENDORB", "VENDORC"}
Hacer configurable el factor de margen de seguridad Sí, una vez Crear un punto agregado para el factor y modificar el script para leerlo

Esto ilustra un principio de la filosofía de diseño: la mayoría de los cambios de negocio solo requieren configuración; únicamente algunos cambios estructurales requieren modificar scripts, con un alcance previsible y controlable.

  • Si los puntos de recarga se distribuyen entre varios cuadros secundarios, comparte sus medidas de potencia con el controlador principal mediante coordinación entre dispositivos y realiza allí el reparto global.
  • Si aumentan las marcas y modelos y los proyectos tienen estructuras similares, utiliza expansión dinámica de unidades para convertir prefijos, reglas de nombres y puntos asociados en plantillas que reduzcan el tiempo de configuración.
  • Para recibir umbrales desde la nube y consultar el estado global, utiliza coordinación entre nube y borde y envía periódicamente el margen de red y los registros de órdenes por punto.
Flujo de adquisición, cálculo, reparto y ejecución del equilibrado de cargas de recarga

El diagrama representa el flujo de datos y control según la lógica de esta página. Consulta los campos concretos en las configuraciones y scripts del texto.