- Présentation de Jelina
- EMS Builder
- Exemples de configuration
Exemple : équilibrage de charge d’un groupe de bornes
Cet exemple construit une stratégie d’équilibrage acceptant un nombre variable d’équipements sans modifier les scripts, uniquement avec les capacités génériques d’Edge-EMS Builder : découverte dynamique de données, calcul d’expressions, déclenchement et limitation de fréquence. Marques et préfixes sont illustratifs ; remplacez-les par ceux du projet.
1. Contexte métier
Section intitulée « 1. Contexte métier »Un tableau électrique alimente plusieurs bornes de recharge, utilisant OCPP ou un protocole propriétaire, ainsi que des charges essentielles comme éclairage et climatisation. La capacité de l’arrivée réseau est fixe. Si toutes les bornes chargent à pleine puissance, la consommation totale peut dépasser la capacité nominale et entraîner un déclenchement ou des pénalités de dépassement.
Objectif : donner la priorité aux charges essentielles et partager la capacité restante entre toutes les bornes en ligne, en ajustant leur courant de charge. Une connexion ou déconnexion doit être rééquilibrée en quelques secondes, et l’ajout d’une borne ne doit pas nécessiter de modifier les scripts de stratégie.
2. Modèle de puissance
Section intitulée « 2. Modèle de puissance »Le système comporte deux points de mesure essentiels :
Grid_total_power: puissance totale instantanée entrant depuis le réseau, incluant toutes les charges du tableau : charges essentielles, bornes et autres.- Les charges essentielles sont déjà incluses dans cette mesure. Contrôler la consommation totale des bornes suffit donc à maintenir l’entrée totale sous la puissance nominale, en donnant naturellement priorité aux charges essentielles.
-- Marge attribuable (kW) = (puissance nominale - puissance réseau totale mesurée) × coefficient de sécurité-- La marge de 10% évite les dépassements ; la mesure inclut déjà les charges essentielles, sans déduction supplémentairelocal avail_kw = (rated_kw - grid_kw) * 0.9local per_A = (avail_kw / n) * 1000 / voltage_V -- Répartir également en courant (A)3. Découverte dynamique : ajouter des bornes sans modifier les scripts
Section intitulée « 3. Découverte dynamique : ajouter des bornes sans modifier les scripts »La stratégie ne fixe pas le nombre de bornes. À chaque exécution, une recherche par préfixe et suffixe découvre automatiquement toutes les bornes en ligne :
local online = {}
-- Parcourir les préfixes de marque, à compléter ou réduire selon le besoinfor _, brandPrefix in ipairs({"VENDORA", "VENDORB"}) do local statusMap = getLatestDataValuesByPrefixAndSuffix("Collection", brandPrefix, "_status") if statusMap then -- Le retour est une table indexée par dataKey, à parcourir avec pairs() ; comparer à la sentinelle "__Null__" pour détecter l’absence for dataKey, dto in pairs(statusMap) do if dto.DataValue ~= "__Null__" then table.insert(online, dto) end end endendSi les points d’une nouvelle borne respectent les conventions ci-dessous, elle participe automatiquement au prochain calcul de répartition, sans modification de script ni redémarrage de service. Cela résulte de la combinaison du modèle de données et du moteur Lua, sans fonction dédiée.
4. Déclenchement : actualisation des données et limitation dans le script
Section intitulée « 4. Déclenchement : actualisation des données et limitation dans le script »Une interrogation périodique fixe peut produire des écritures même sans changement de données. Ajouter la limitation de fréquence au déclencheur complexifie sa condition. Le modèle retenu est plus simple :
Déclenchement sur actualisation + limitation par horodatage dans le script : le déclencheur détecte l’actualisation ; le script maîtrise la fréquence réelle des commandes.
Actualisation des données → Condition satisfaite → Déclencher le script → Lire le point agrégé last_ctrl_ms(Horodatage de dernière exécution) → Temps écoulé < 5000ms:Retour immédiat, sans commande → Temps écoulé ≥ 5000ms:Exécuter le contrôle → Instructions d’écriture → Actualiser l’horodatagePréalable au déploiement : créez manuellement un point agrégé servant de mémoire au script :
{ "dataKey": "last_ctrl_ms", "dataName": "充电控制上次执行时间戳", "expression": "return 0", "collectionCycle": -1}5. Table de décision par borne
Section intitulée « 5. Table de décision par borne »À chaque déclenchement, le script lit les paramètres réseau, vérifie la fréquence, découvre les bornes en ligne, calcule le courant disponible, décide pour chaque borne, actualise l’horodatage et retourne la liste d’instructions. Règles :
| Condition | État de la borne | Action |
|---|---|---|
| Surcharge ou courant réparti < courant minimal | En charge | Arrêter la charge (stop_transaction) |
| Surcharge ou courant réparti < courant minimal | Autre | Ignorer |
| Pas de surcharge et courant réparti ≥ courant minimal | En charge | Envoyer la nouvelle limite de courant (set_charging_profile) |
| Pas de surcharge et courant réparti ≥ courant minimal | Hors charge | Démarrer la charge (start_transaction, avec Tag d’autorisation valide) |
Extrait principal du script d’action, renvoyant un tableau de descriptions d’écriture :
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$", "") -- Retrouver le préfixe de la borne depuis dataKey, par exemple 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. Conventions de nommage pour la découverte par lot
Section intitulée « 6. Conventions de nommage pour la découverte par lot »Le script recherche les points selon le format {préfixe-marque}{numéro-sur-deux-chiffres}_{propriété}, à respecter strictement :
| Élément | Règle | Exemple |
|---|---|---|
| Préfixe de marque | Majuscules, sans trait de soulignement | VENDORA, VENDORB, à remplacer par les identifiants de marques réellement utilisés |
| Numéro | Deux chiffres, complétés par zéro | 01, 02…10 |
| Propriété | Voir ci-dessous | status, type |
Exemples de points requis pour chaque borne :
| Propriété | Exemple de dataKey complet | Lecture/écriture | Description |
|---|---|---|---|
_status |
VENDORA01_status |
Lecture | État de la borne, utilisé pour la découverte ; sans ce point, la borne n’est pas détectée |
_transaction_id |
VENDORA01_transaction_id |
Lecture | ID de transaction courante |
_type |
VENDORA01_type |
Écriture | Type de commande : start/stop/set_charging_profile |
_periods |
VENDORA01_periods |
Écriture | JSON des périodes de limitation, par exemple [{"start":0,"limit":16}] |
Pour ajouter une sixième borne de cette marque, raccordez ses points selon ces noms, VENDORA06_status, VENDORA06_type, etc. Elle sera automatiquement incluse dans la répartition.
7. Gérer les évolutions futures
Section intitulée « 7. Gérer les évolutions futures »| Scénario | Modifier le script ? | Opération |
|---|---|---|
| Ajouter une borne de même marque | Non | Ajouter ses points selon les conventions |
| Ajuster la limite de capacité nominale | Non | Modifier la valeur du point agrégé Grid_current |
| Ajuster la puissance minimale de recharge | Non | Modifier le point agrégé min_charge_power |
| Borne temporairement hors ligne | Non | Après déconnexion, _status devient vide et la borne est exclue de la liste en ligne |
| Ajouter une marque de bornes | Oui | Ajouter un préfixe à la liste, par exemple {"VENDORA", "VENDORB", "VENDORC"} |
| Rendre le coefficient de marge configurable | Oui, une seule fois | Créer un point agrégé pour le coefficient et modifier le script pour le lire |
Cela illustre les principes de conception : la plupart des évolutions métier modifient seulement la configuration ; quelques évolutions structurelles exigent un changement de script, dans un périmètre prévisible et maîtrisé.
8. Combinaison avec les autres capacités
Section intitulée « 8. Combinaison avec les autres capacités »- Si les bornes sont réparties entre plusieurs sous-tableaux, la coordination des équipements partage leurs puissances mesurées avec un contrôleur principal pour une répartition globale.
- Si marques et modèles augmentent d’un projet à l’autre avec une structure similaire, l’extension dynamique des unités transforme préfixes, conventions et points associés en modèles pour accélérer les projets suivants.
- Pour envoyer les limites depuis le cloud ou consulter l’état global, utilisez la coordination cloud-edge afin de remonter périodiquement marge réseau et commandes de chaque borne.
Ce schéma représente le flux de données et de contrôle selon la configuration décrite ici. Référez-vous aux configurations et scripts pour les champs exacts.