事例:充電器群の負荷分散
この事例では、Edge-EMS Builder の汎用機能である動的データ走査、式計算、トリガープラン、頻度制限だけを使い、台数が変わってもスクリプトの変更が不要な負荷分散を構築します。機器メーカー名と命名プレフィックスは例示であり、実際のプロジェクトの命名へ置き換えてください。
配電盤に複数の充電器(OCPP またはメーカー独自プロトコルなど、プロトコルは不問)と、照明・空調などの必要負荷を接続します。系統側からの受電容量は固定で、すべての充電器が同時に最大出力で動作すると、合計負荷が定格容量を超え、遮断や容量超過の罰金につながる可能性があります。
目標は、必要負荷への優先給電を確保しながら、オンラインの全充電器で残りの利用可能容量を共有することです。各充電器の電流を動的に調整し、いずれかがオンライン・オフラインになっても数秒で再配分します。充電器の追加で制御スクリプトを変更する必要はありません。
2. 電力モデル
Section titled “2. 電力モデル”システムには 2 つの主要な測定点があります。
Grid_total_power:系統側のリアルタイム総入力電力(配電盤内の全負荷:必要負荷 + 充電器 + その他を含む)。- 必要負荷はこの実測値に含まれます。そのため、充電器の総消費電力だけを制御することで、配電盤の総入力を定格以下に抑え、必要負荷を優先できます。
-- 配分可能な余裕量(kW)= (定格電力 - 実測系統総電力) × 安全係数-- 10% の安全余裕で電力オーバーシュートを防止。実測値には必要負荷が含まれ、別途差し引く必要はありませんlocal avail_kw = (rated_kw - grid_kw) * 0.9local per_A = (avail_kw / n) * 1000 / voltage_V -- 電流(A)へ均等配分3. 動的デバイス検出:充電器を追加してもスクリプト変更は不要
Section titled “3. 動的デバイス検出:充電器を追加してもスクリプト変更は不要”制御ロジックは充電器の台数を固定しません。実行のたびにプレフィックスとサフィックスで一括走査し、オンラインの全充電器を自動検出します。
local online = {}
-- ブランドプレフィックスを順に走査。必要に応じて追加・削除可能for _, brandPrefix in ipairs({"VENDORA", "VENDORB"}) do local statusMap = getLatestDataValuesByPrefixAndSuffix("Collection", brandPrefix, "_status") if statusMap then -- 戻り値は dataKey をキーとする Table。pairs() で走査し、空値はセンチネル "__Null__" と比較します for dataKey, dto in pairs(statusMap) do if dto.DataValue ~= "__Null__" then table.insert(online, dto) end end endend新しい充電器の収集ポイントを所定の命名規則(後述)で接続すると、次のトリガーで自動的に均等配分へ含められます。スクリプト変更もサービス再起動も不要です。これは専用機能ではなく、データモデル と Lua エンジン の組合せによる機能です。
4. トリガーモデル:データ更新とスクリプト内の頻度制限
Section titled “4. トリガーモデル:データ更新とスクリプト内の頻度制限”固定周期のポーリングだけでは、データが変わっていなくても実行して重複書き込みが発生します。一方、頻度制限までトリガー条件へ含めると設定が複雑になります。ここでは次の方式を使用します。
データ更新トリガー + スクリプト内のタイムスタンプによる頻度制限:トリガーはデータ更新の検知だけを担当し、実際の指令送信頻度はスクリプトが制御します。
データ更新 → 条件成立 → スクリプト起動 → 集約ポイントを読み取り last_ctrl_ms(前回実行タイムスタンプ) → 経過時間 < 5000ms:指令を送信せずそのまま戻る → 経過時間 ≥ 5000ms:制御を実行 → 書き込み指令 → タイムスタンプを更新配置前の準備:スクリプトの「記憶」として、事前に集約ポイントを 1 つ手動作成します。
{ "dataKey": "last_ctrl_ms", "dataName": "充电控制上次执行时间戳", "expression": "return 0", "collectionCycle": -1}5. 充電器ごとの判断表
Section titled “5. 充電器ごとの判断表”トリガーのたびに、系統パラメーター読取り → 頻度制限確認 → オンライン充電器走査 → 利用可能電流計算 → 各充電器の判断 → タイムスタンプ更新 → 指令一覧返却、の順に実行します。判断規則は次のとおりです。
| 条件 | 充電器の状態 | 実行アクション |
|---|---|---|
| 過負荷、または均等配分電流 < 最小電流 | 充電中 | 充電停止(stop_transaction) |
| 過負荷、または均等配分電流 < 最小電流 | その他 | スキップ |
| 過負荷でなく、均等配分電流 ≥ 最小電流 | 充電中 | 新しい電流上限を送信(set_charging_profile) |
| 過負荷でなく、均等配分電流 ≥ 最小電流 | 充電中以外 | 充電開始(start_transaction。有効な認証 Tag が必要) |
対応する主要スクリプト(トリガーアクション。書き込み記述配列を返す):
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$", "") -- dataKey から充電器のプレフィックス(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. 命名規則:一括検出の前提
Section titled “6. 命名規則:一括検出の前提”スクリプトは {ブランドプレフィックス}{2桁番号}_{属性} 形式で収集ポイントを一括走査します。次の規則を厳守してください。
| 部分 | 規則 | 例 |
|---|---|---|
| ブランドプレフィックス | 大文字のみ、アンダースコアなし | VENDORA、VENDORB(実際の機器メーカー識別子へ置換) |
| 番号 | ゼロ埋めの 2 桁 | 01、02…10 |
| 属性 | 下表を参照 | status、type |
各充電器に必要な収集ポイント(一部の例):
| 属性 | 完全な dataKey の例 | 読み/書き | 説明 |
|---|---|---|---|
_status |
VENDORA01_status |
読み | 充電器の状態。走査に使用し、欠損すると検出されない |
_transaction_id |
VENDORA01_transaction_id |
読み | 現在のトランザクション ID |
_type |
VENDORA01_type |
書き | 指令種別:start/stop/set_charging_profile |
_periods |
VENDORA01_periods |
書き | 制限期間の JSON。例:[{"start":0,"limit":16}] |
同じメーカーの 6 台目を追加する場合、VENDORA06_status、VENDORA06_type など、上記の命名で収集ポイントを接続するだけで、自動的に均等配分へ含められます。
7. 将来の変更への対応
Section titled “7. 将来の変更への対応”| シーン | スクリプト変更 | 操作 |
|---|---|---|
| 同じメーカーの充電器を追加 | 不要 | 命名規則に従って収集ポイントを追加 |
| 定格容量の上限を調整 | 不要 | 集約ポイント Grid_current の値を変更 |
| 最小充電電力を調整 | 不要 | 集約ポイント min_charge_power の値を変更 |
| 充電器が一時的にオフライン | 不要 | 切断後に _status が空になり、オンライン一覧から自動除外 |
| 新しい充電器メーカーを追加 | 必要 | 走査リストにプレフィックスを追加。例:{"VENDORA", "VENDORB", "VENDORC"} |
| 安全余裕係数を設定可能にする | 必要(初回のみ) | 係数を保持する集約ポイントを追加し、スクリプトをその読取りへ変更 |
この表は 設計思想 の原則も示しています。大部分の業務変更は設定だけで対応でき、スクリプト変更が必要なのは一部の構造的な変更だけです。変更範囲も予測・管理できます。
8. 他の機能との組合せ
Section titled “8. 他の機能との組合せ”- 充電器群が複数の子配電盤に分散する場合、デバイス協調 で各配電盤の実測系統電力を主制御デバイスへ共有し、全体を一括して均等配分できます。
- 案件ごとに充電器のメーカー・モデルが増え、各案件の構成が似ている場合、デバイスユニットの動的拡張 で「ブランドプレフィックス + 命名規則 + 関連ポイント」をテンプレート化し、新規案件の構築時間を短縮できます。
- クラウドから電流上限を配信したり、負荷分散の全体状態を確認したりする場合は、クラウド・エッジ連携 で系統余裕量と各充電器への指令記録を定期報告します。
図は、このページの設定ロジックに基づくデータと制御の流れです。具体的な操作フィールドは、本文の設定とスクリプトを参照してください。