Zum Inhalt springen
JelinaJelina DocumentationTechnische Dokumentation
Zur Website

Beispiel: Lastmanagement für Ladestationsgruppen

Dieses Beispiel zeigt, wie sich allein mit den allgemeinen Funktionen von Edge-EMS Builder – dynamischer Datensuche, Ausdrucksberechnung, Triggerschemata und Frequenzbegrenzung – eine Lastverteilungsstrategie für wechselnde Gerätezahlen ohne Skriptänderungen aufbauen lässt. Gerätehersteller und Namenspräfixe sind beispielhaft; ersetzen Sie sie durch die tatsächlichen Bezeichnungen Ihres Projekts.

An einem Verteiler sind mehrere Ladestationen mit OCPP oder herstellerspezifischen Protokollen sowie notwendige Verbraucher wie Beleuchtung und Klimaanlage angeschlossen. Die netzseitige Anschlusskapazität ist begrenzt. Laden alle Stationen gleichzeitig mit voller Leistung, kann der Gesamtverbrauch die Nennkapazität überschreiten und eine Abschaltung oder Mehrkosten wegen Leistungsüberschreitung verursachen.

Ziel: Notwendige Verbraucher werden vorrangig versorgt; alle online verfügbaren Ladestationen teilen sich die verbleibende Kapazität. Der Ladestrom jeder Station wird dynamisch angepasst. Beim Zu- oder Abschalten einer Station erfolgt innerhalb weniger Sekunden eine Neuverteilung. Zusätzliche Ladestationen erfordern keine Änderungen an Strategieskripten.

Das System besitzt zwei zentrale Messpunkte:

  • Grid_total_power: aktuelle gesamte netzseitige Eingangsleistung einschließlich aller Verbraucher im Verteiler: notwendige Lasten, Ladestationen und weitere Lasten;
  • Notwendige Lasten sind bereits in diesem Messwert enthalten. Daher genügt es, den Gesamtverbrauch der Ladestationen zu steuern, um die gesamte Eingangsleistung unter der Nennleistung zu halten und notwendige Lasten vorrangig zu versorgen.
-- Verteilbare Reserve (kW) = (Nennleistung - gemessene Netzgesamtleistung) × Sicherheitsfaktor
-- 10 % Sicherheitsreserve verhindern ein Überschwingen; notwendige Lasten sind im Messwert enthalten und werden nicht nochmals abgezogen
local avail_kw = (rated_kw - grid_kw) * 0.9
local per_A = (avail_kw / n) * 1000 / voltage_V -- Gleichmäßig aufgeteilten Strom berechnen (A)

3. Dynamische Geräteerkennung: neue Ladestationen ohne Skriptänderung

Abschnitt betitelt „3. Dynamische Geräteerkennung: neue Ladestationen ohne Skriptänderung“

Die Strategie setzt keine feste Anzahl von Ladestationen voraus. Bei jeder Ausführung erkennt eine Sammelabfrage nach Präfix und Suffix automatisch alle online verfügbaren Ladestationen:

local online = {}
-- Markenpräfixe nacheinander durchsuchen; bei Bedarf ergänzen oder entfernen
for _, brandPrefix in ipairs({"VENDORA", "VENDORB"}) do
local statusMap = getLatestDataValuesByPrefixAndSuffix("Collection", brandPrefix, "_status")
if statusMap then
-- Die Rückgabe ist eine nach dataKey indizierte Tabelle: mit pairs() iterieren und auf den Sentinel-String "__Null__" prüfen
for dataKey, dto in pairs(statusMap) do
if dto.DataValue ~= "__Null__" then
table.insert(online, dto)
end
end
end
end

Sobald die Erfassungsdatenpunkte einer neuen Ladestation gemäß den vereinbarten Namensregeln eingebunden sind (siehe „Namensregeln“ unten), wird sie beim nächsten Trigger automatisch in die gleichmäßige Verteilung einbezogen. Weder Skriptänderung noch Dienstneustart sind erforderlich. Diese Fähigkeit entsteht aus der Kombination von Datenmodell und Lua-Engine, nicht aus einer speziellen Einzelfunktion.

4. Auslösemodell: Datenaktualisierung und Frequenzbegrenzung im Skript

Abschnitt betitelt „4. Auslösemodell: Datenaktualisierung und Frequenzbegrenzung im Skript“

Rein periodische Abfragen können redundante Schreibzugriffe verursachen, obwohl sich die Daten nicht geändert haben. Wird zusätzlich die Frequenzbegrenzung im Trigger abgebildet, werden die Bedingungen komplexer. Hier wird ein einfacheres Modell verwendet:

Auslösung bei Datenaktualisierung plus zeitstempelbasierte Frequenzbegrenzung im Skript: Der Trigger erkennt nur die Aktualisierung; das Skript steuert selbst, wie häufig tatsächlich Befehle gesendet werden.

Datenaktualisierung → Bedingung erfüllt → Skript auslösen → Aggregationspunkt last_ctrl_ms lesen (Zeitstempel der letzten Ausführung)
→ Vergangene Zeit < 5000ms: sofort zurückkehren, keinen Befehl senden
→ Vergangene Zeit ≥ 5000ms: Steuerung ausführen → Befehl schreiben → Zeitstempel aktualisieren

Vorbereitung der Bereitstellung: Erstellen Sie zunächst manuell einen Aggregationsdatenpunkt als „Gedächtnis“ des Skripts:

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

Nach jeder Auslösung führt das Skript folgende Schritte aus: Netzparameter lesen → Frequenzbegrenzung prüfen → verfügbare Ladestationen suchen → verfügbaren Strom berechnen → je Station entscheiden → Zeitstempel aktualisieren → Befehlsliste zurückgeben. Entscheidungsregeln:

Bedingung Ladestationszustand Aktion
Überlast oder gleichmäßig zugeteilter Strom < Mindeststrom Lädt Ladevorgang stoppen (stop_transaction)
Überlast oder gleichmäßig zugeteilter Strom < Mindeststrom Anderer Zustand Überspringen
Keine Überlast und gleichmäßig zugeteilter Strom ≥ Mindeststrom Lädt Neue Stromgrenze senden (set_charging_profile)
Keine Überlast und gleichmäßig zugeteilter Strom ≥ Mindeststrom Lädt nicht Ladevorgang starten (start_transaction; gültiger Autorisierungs-Tag erforderlich)

Zentraler Skriptausschnitt als Triggeraktion, die ein Array von Schreibbeschreibungen zurückgibt:

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$", "") -- Ladesäulenpräfix aus dataKey ableiten, beispielsweise 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. Namensregeln als Voraussetzung für die Sammelerkennung

Abschnitt betitelt „6. Namensregeln als Voraussetzung für die Sammelerkennung“

Das Skript sucht Ladestationsdatenpunkte gesammelt im Format {Herstellerpräfix}{zweistellige Nummer}_{Attribut}. Folgende Regeln sind strikt einzuhalten:

Bestandteil Regel Beispiel
Herstellerpräfix Nur Großbuchstaben, keine Unterstriche VENDORA, VENDORB; durch die tatsächlichen Herstellerkennungen ersetzen
Nummer Zweistellig mit führender Null 01, 02…10
Attribut Siehe folgende Tabelle status, type

Erforderliche Erfassungsdatenpunkte je Ladestation, auszugsweise:

Attribut Vollständiger dataKey als Beispiel Lesen/Schreiben Beschreibung
_status VENDORA01_status Lesen Ladestationszustand und Grundlage der Suche; fehlt dieser Punkt, wird die Station nicht erkannt
_transaction_id VENDORA01_transaction_id Lesen Aktuelle Transaktions-ID
_type VENDORA01_type Schreiben Befehlstyp: start/stop/set_charging_profile
_periods VENDORA01_periods Schreiben JSON mit Begrenzungszeiträumen, z. B. [{"start":0,"limit":16}]

Für die sechste Ladestation dieses Herstellers genügt es, ihre Datenpunkte als VENDORA06_status, VENDORA06_type usw. anzubinden. Sie wird automatisch in die gleichmäßige Verteilung einbezogen.

Szenario Skriptänderung erforderlich? Vorgehen
Zusätzliche Ladestation desselben Herstellers Nein Datenpunkte gemäß den Namensregeln hinzufügen
Nennkapazitätsgrenze anpassen Nein Wert des Aggregationsdatenpunkts Grid_current ändern
Mindestladeleistung anpassen Nein Wert des Aggregationsdatenpunkts min_charge_power ändern
Ladestation vorübergehend offline Nein Nach Trennung wird _status leer; die Station entfällt automatisch aus der Onlineliste
Neuer Ladestationshersteller Ja Präfix zur Suchliste hinzufügen, z. B. {"VENDORA", "VENDORB", "VENDORC"}
Sicherheitsreserve konfigurierbar machen Ja, einmalig Aggregationsdatenpunkt für den Faktor anlegen und das Skript auf das Lesen dieses Punkts umstellen

Die Tabelle verdeutlicht einen Grundsatz des Entwurfskonzepts: Die meisten fachlichen Änderungen erfordern nur eine Konfigurationsanpassung; wenige strukturelle Änderungen benötigen Skriptanpassungen. Deren Umfang bleibt vorhersehbar und beherrschbar.

  • Verteilen sich Ladestationen auf mehrere Unterverteiler, können deren gemessene Netzleistungen über Gerätekoordination an das Hauptgerät freigegeben werden. Dort erfolgt die globale gleichmäßige Verteilung.
  • Kommen laufend weitere Hersteller und Modelle hinzu und ähneln sich die Ladestationsgruppen verschiedener Projekte, können UDE-Vorlagen Herstellerpräfix, Namensregeln und zugehörige Datenpunkte wiederverwendbar bündeln und den Aufbau neuer Projekte verkürzen.
  • Für Stromgrenzwerte aus der Cloud und die Übersicht des Lastmanagementzustands können über Cloud-Edge-Koordination Netzreserven und Befehlsprotokolle je Ladestation regelmäßig gemeldet werden.
Erfassung, Berechnung, Verteilung und Ausführung beim Lastmanagement für Ladestationen

Diese Abbildung basiert auf der Konfigurationslogik dieser Seite und zeigt Daten- und Steuerungsabläufe. Konkrete Felder entnehmen Sie den beschriebenen Konfigurationen und Skripten.