Pular para o conteúdo
JelinaJelina DocumentationDocumentação técnica
Voltar ao site oficial

Exemplo: balanceamento de carga de um conjunto de carregadores

Este exemplo mostra como usar apenas recursos gerais do Edge-EMS Builder — varredura dinâmica de dados, cálculo de expressões, perfis de acionamento e limitação de frequência — para construir uma estratégia de balanceamento de carga que admite “quantidade variável de dispositivos sem alterar scripts”. As marcas e os prefixos usados são ilustrativos; substitua-os pelos nomes reais do projeto.

Um quadro de distribuição alimenta vários carregadores (sem restrição de protocolo: OCPP ou protocolo proprietário do fabricante) e outras cargas essenciais, como iluminação e climatização. A capacidade de entrada da rede é fixa. Se todos os carregadores operarem simultaneamente em potência máxima, a carga total poderá exceder a capacidade nominal da entrada, provocando desarme ou penalidades por ultrapassagem de capacidade.

Objetivo: garantir prioridade de alimentação às cargas essenciais e compartilhar a capacidade restante entre todos os carregadores online, ajustando dinamicamente a corrente de cada carregador. A entrada ou saída de qualquer unidade deve provocar um novo balanceamento em poucos segundos, e a inclusão de novos carregadores não deve exigir alterações nos scripts de estratégia.

Há dois pontos de medição principais no sistema:

  • Grid_total_power: potência total de entrada da rede em tempo real, incluindo todas as cargas do quadro: cargas essenciais + carregadores + outras;
  • As cargas essenciais já estão incluídas nessa medição. Portanto, basta controlar o consumo total dos carregadores para manter a potência total de entrada do quadro dentro do valor nominal, atendendo naturalmente às cargas essenciais primeiro.
-- Margem disponível (kW) = (potência nominal - potência total medida da rede) × fator de segurança
-- A margem de 10% evita ultrapassagens de potência; a medição já inclui as cargas essenciais, sem necessidade de subtraí-las separadamente
local avail_kw = (rated_kw - grid_kw) * 0.9
local per_A = (avail_kw / n) * 1000 / voltage_V -- Divide igualmente e converte em corrente (A)

III. Descoberta dinâmica: adicionar carregadores sem alterar scripts

Seção intitulada “III. Descoberta dinâmica: adicionar carregadores sem alterar scripts”

A estratégia não pressupõe uma quantidade fixa de carregadores. A cada execução, identifica automaticamente todas as unidades online por uma varredura em lote por prefixo + sufixo:

local online = {}
-- Varre cada prefixo de marca; adicione ou remova conforme necessário
for _, brandPrefix in ipairs({"VENDORA", "VENDORB"}) do
local statusMap = getLatestDataValuesByPrefixAndSuffix("Collection", brandPrefix, "_status")
if statusMap then
-- O retorno é uma Table indexada por dataKey; percorra com pairs() e compare com a string sentinela "__Null__" para verificar ausência de valor
for dataKey, dto in pairs(statusMap) do
if dto.DataValue ~= "__Null__" then
table.insert(online, dto)
end
end
end
end

Desde que os pontos de aquisição de um novo carregador sigam a regra de nomenclatura definida (consulte “Regras de nomenclatura” abaixo), ele será incluído automaticamente no cálculo de divisão igualitária no próximo acionamento, sem alterar scripts nem reiniciar serviços. Esse recurso resulta da combinação do modelo de dados com o mecanismo Lua, e não de uma função exclusiva para esse caso.

IV. Modelo de acionamento: atualização dos dados + limitação de frequência no script

Seção intitulada “IV. Modelo de acionamento: atualização dos dados + limitação de frequência no script”

Um polling de período fixo pode causar gravações redundantes mesmo sem alteração dos dados. Incluir também a limitação de frequência nas condições do gatilho torna a configuração mais complexa. Aqui, usa-se um modelo mais simples:

Acionamento por atualização dos dados + limitação por timestamp dentro do script: o gatilho apenas detecta a atualização dos dados, enquanto o script controla a frequência real de envio dos comandos.

Atualização dos dados → Condição satisfeita → Script acionado → Ler ponto agregado last_ctrl_ms (timestamp da última execução)
→ Tempo decorrido < 5000ms: retornar sem enviar comandos
→ Tempo decorrido ≥ 5000ms: executar controle → gravar comandos → atualizar timestamp

Pré-requisito da implantação: crie manualmente um ponto agregado que servirá como “memória” do script:

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

Após cada acionamento, o script executa nesta ordem: ler parâmetros da rede → verificar a limitação de frequência → varrer carregadores online → calcular a corrente disponível → decidir por carregador → atualizar o timestamp → retornar a lista de comandos. Regras de decisão por carregador:

Condição Estado do carregador Ação
Sobrecarga ou corrente por divisão igualitária < corrente mínima Carregando Parar o carregamento (stop_transaction)
Sobrecarga ou corrente por divisão igualitária < corrente mínima Outro Ignorar
Sem sobrecarga e corrente por divisão igualitária ≥ corrente mínima Carregando Enviar novo limite de corrente (set_charging_profile)
Sem sobrecarga e corrente por divisão igualitária ≥ corrente mínima Não está carregando Iniciar o carregamento (start_transaction, com Tag de autorização válida)

Trecho principal do script correspondente (ação de gatilho que retorna um array de descrições de gravação):

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$", "") -- Recupera o prefixo do carregador a partir do dataKey, como 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

VI. Regras de nomenclatura: pré-requisito para reconhecimento em lote

Seção intitulada “VI. Regras de nomenclatura: pré-requisito para reconhecimento em lote”

O script varre em lote os pontos de aquisição dos carregadores no formato {prefixo_da_marca}{número_de_dois_dígitos}_{propriedade}, que deve ser seguido rigorosamente:

Parte Regra Exemplo
Prefixo da marca Letras maiúsculas, sem sublinhados VENDORA, VENDORB (substitua pelos identificadores reais das marcas utilizadas)
Número Dois dígitos com preenchimento de zero à esquerda 01, 02…10
Propriedade Consulte a tabela abaixo status, type

Pontos de aquisição necessários em cada carregador (alguns exemplos):

Propriedade Exemplo de dataKey completo Leitura/gravação Descrição
_status VENDORA01_status Leitura Estado do carregador, usado na varredura; se estiver ausente, o carregador não será descoberto
_transaction_id VENDORA01_transaction_id Leitura ID da transação atual
_type VENDORA01_type Gravação Tipo de comando: start/stop/set_charging_profile
_periods VENDORA01_periods Gravação JSON do período de limitação, como [{"start":0,"limit":16}]

Para adicionar o sexto carregador dessa marca, basta integrar seus pontos conforme a nomenclatura acima — VENDORA06_status, VENDORA06_type etc. — para incluí-lo automaticamente na lógica de divisão igualitária.

Cenário Exige alteração do script? Operação
Adicionar carregador da mesma marca Não Adicione os pontos de aquisição conforme a regra de nomenclatura
Ajustar o limite de capacidade nominal Não Altere o valor do ponto agregado Grid_current
Ajustar a potência mínima de carregamento Não Altere o valor do ponto agregado min_charge_power
Carregador temporariamente offline Não Após a desconexão, _status fica vazio e o carregador é removido automaticamente da lista online
Adicionar uma marca de carregador Sim Acrescente um prefixo à lista de varredura, como {"VENDORA", "VENDORB", "VENDORC"}
Tornar configurável o fator de margem de segurança Sim, uma única vez Crie um ponto agregado para esse fator e altere o script para lê-lo

Essa tabela também ilustra um princípio destacado na Filosofia de projeto: a maioria das mudanças de negócio exige apenas ajustes de configuração; poucas alterações estruturais exigem modificar scripts, com um escopo previsível e controlável.

  • Se os carregadores estiverem distribuídos entre vários subquadros, use a colaboração entre dispositivos para compartilhar a potência medida da rede de cada subquadro com o controlador principal e realizar uma distribuição global unificada.
  • Se novas marcas e modelos de carregadores forem incorporados continuamente e os projetos tiverem estruturas semelhantes, use a expansão dinâmica de unidades de dispositivo para transformar “prefixo de marca + regra de nomenclatura + pontos relacionados” em templates, reduzindo o tempo de implantação de novos projetos.
  • Se for necessário receber limites de corrente da nuvem e consultar o estado geral do balanceamento, use a colaboração entre nuvem e borda para informar periodicamente a margem da rede e os registros de comandos enviados a cada carregador.
Fluxo de aquisição, cálculo, distribuição e execução do balanceamento de carga dos carregadores

Este diagrama foi elaborado a partir da lógica de configuração desta página e mostra os fluxos de dados e controle. Para os campos específicos, consulte as configurações e os scripts no texto.