- Visión general de Jelina
- EMS Builder
- Ejemplos de configuración
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.
1. Contexto de negocio
Sección titulada «1. Contexto de negocio»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.
2. Modelo de potencia
Sección titulada «2. Modelo de potencia»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 nuevolocal avail_kw = (rated_kw - grid_kw) * 0.9local 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 necesariofor _, 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 endendSi 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 marcaPaso 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}5. Tabla de decisiones por punto de recarga
Sección titulada «5. Tabla de decisiones por punto de recarga»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.001local 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 endend
table.insert(resultList, buildCommonWriteMap("Aggregation", "last_ctrl_ms", OffsetDateTime.now():unixMilli(), nil, nil, false))return resultList6. 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.
7. Adaptarse a cambios futuros
Sección titulada «7. Adaptarse a cambios futuros»| 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.
8. Combinación con otras capacidades
Sección titulada «8. Combinación con otras capacidades»- 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.
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.