Repository navigation
feat(M4-P0A): compile 3 SCD Merging Unit SV profiles with bounded common 4kHz roster #334
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
02358e2
feat(m4): introduce bounded three-SV SCL roster and explicit shared-g…
masarray fd53b4d
feat(m4): compile three SCD profiles independently and preserve repea…
masarray 77d09ae
fix(m4): match exact sample/frame rate optional uint32 types
masarray d6ab56c
test(m4): 3-MU 9-2LE SCD regression fixture with explicit SmvOpts
masarray 78b7ff6
test(m4): three SCD MU profiles, V1 binary roundtrip, repeated APPID,…
masarray d05e15a
build(m4): compile three-SV roster into core and register CTest regre…
masarray File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||
| #pragma once | ||
|
|
||
| #include "ariec61850/sampled_values/compiled_device_profile.hpp" | ||
| #include "ariec61850/scl/model.hpp" | ||
|
|
||
| #include <array> | ||
| #include <cstddef> | ||
| #include <cstdint> | ||
| #include <optional> | ||
| #include <string> | ||
| #include <vector> | ||
|
|
||
| namespace ar::iec61850::sampled_values { | ||
|
|
||
| // M4.0 intentionally fixes the number of source-backed virtual MUs at three. | ||
| // A firmware capability change, rather than an unbounded vector, raises this limit. | ||
| inline constexpr std::size_t m4_three_sv_capacity = 3U; | ||
| inline constexpr std::uint32_t m4_three_sv_rate_hz = 4000U; | ||
|
|
||
| struct M4CompiledSvStream final { | ||
| std::string ied_name; | ||
| std::string control_block_reference; | ||
| CompiledSvDeviceProfile device_profile; | ||
| }; | ||
|
|
||
| struct M4ThreeSvRoster final { | ||
| std::array<M4CompiledSvStream, m4_three_sv_capacity> streams{}; | ||
| std::uint32_t common_sample_rate_hz{m4_three_sv_rate_hz}; | ||
| std::uint32_t total_ethernet_frames_per_second{ | ||
| static_cast<std::uint32_t>(m4_three_sv_capacity) * m4_three_sv_rate_hz}; | ||
| }; | ||
|
|
||
| struct M4ThreeSvCompileResult final { | ||
| std::optional<M4ThreeSvRoster> roster; | ||
| std::vector<std::string> errors; | ||
| [[nodiscard]] bool ok() const noexcept { return roster.has_value() && errors.empty(); } | ||
| }; | ||
|
|
||
| // Pure host-side compiler. No hardware/clock/USB dependency. Never parses XML | ||
| // twice: the existing SclParser creates SclDocument and the existing one-stream | ||
| // compiler/codec remains the only authority for each stream's wire fields. | ||
| // The caller EXPLICITLY selects the 9-2LE family and counter policy. | ||
| class M4ThreeSvRosterCompiler final { | ||
| public: | ||
| [[nodiscard]] static M4ThreeSvCompileResult compile( | ||
| const scl::SclDocument& document, | ||
| const SvPublisherProfileCompileContext& context); | ||
| }; | ||
|
|
||
| } // namespace ar::iec61850::sampled_values |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| // SPDX-License-Identifier: GPL-3.0-or-later | ||
| #include "ariec61850/sampled_values/m4_three_sv_roster.hpp" | ||
| #include "ariec61850/sampled_values/esp32p4_profile_support.hpp" | ||
|
|
||
| #include <cstddef> | ||
| #include <cstdint> | ||
| #include <string> | ||
| #include <string_view> | ||
| #include <utility> | ||
|
|
||
| namespace ar::iec61850::sampled_values { | ||
| namespace { | ||
|
|
||
| [[nodiscard]] bool same_wire_identity( | ||
| const CompiledSvDeviceProfile& lhs, | ||
| const CompiledSvDeviceProfile& rhs) noexcept { | ||
| const auto id = [](const CompiledSvDeviceProfile& p) { | ||
| return std::string_view{p.sv_id.data(), p.sv_id_length}; | ||
| }; | ||
| return lhs.app_id == rhs.app_id && | ||
| lhs.destination_mac == rhs.destination_mac && | ||
| lhs.vlan_present == rhs.vlan_present && | ||
| (!lhs.vlan_present || | ||
| (lhs.vlan_id == rhs.vlan_id && | ||
| lhs.vlan_priority == rhs.vlan_priority)) && | ||
| lhs.transport_mode == rhs.transport_mode && | ||
| id(lhs) == id(rhs); | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| M4ThreeSvCompileResult M4ThreeSvRosterCompiler::compile( | ||
| const scl::SclDocument& document, | ||
| const SvPublisherProfileCompileContext& context) { | ||
| M4ThreeSvCompileResult result; | ||
| if (document.sampled_values_streams.size() != m4_three_sv_capacity) { | ||
| result.errors.push_back( | ||
| "M4.0 requires exactly three SCL SampledValueControl streams; found " + | ||
| std::to_string(document.sampled_values_streams.size())); | ||
| return result; | ||
| } | ||
|
|
||
| // The family is deliberate engineering intent, never inferred from a | ||
| // filename, APPID, DataSet, or vendor metadata. | ||
| if (context.profile_family != SvProfileFamily::legacy_9_2le && | ||
| context.profile_family != SvProfileFamily::iec61850_9_2) { | ||
| result.errors.push_back( | ||
| "Select an explicit supported IEC 61850-9-2/9-2LE profile family."); | ||
| return result; | ||
| } | ||
| if (context.sample_counter_modulus != | ||
| std::optional<std::uint16_t>{static_cast<std::uint16_t>(4000U)}) { | ||
| result.errors.push_back( | ||
| "M4.0 needs an explicitly validated 4000-sample counter modulus " | ||
| "for one shared PTP-aligned second."); | ||
| return result; | ||
| } | ||
|
|
||
| M4ThreeSvRoster candidate; | ||
| for (std::size_t i = 0U; i < m4_three_sv_capacity; ++i) { | ||
| const auto& stream = document.sampled_values_streams[i]; | ||
| const std::string name = stream.ied_name + " / " + stream.control_name; | ||
| const auto compiled = SvPublisherProfileCompiler::compile(stream, context); | ||
| for (const auto& error : compiled.errors) { | ||
| result.errors.push_back(name + ": " + error); | ||
| } | ||
| if (!compiled.ok()) continue; | ||
|
|
||
| const SvPublisherProfile& p = *compiled.profile; | ||
| if (p.timing.exact_sample_rate_hz() != | ||
| std::optional<std::uint32_t>{m4_three_sv_rate_hz} || | ||
| p.timing.exact_frame_rate_hz() != | ||
| std::optional<std::uint32_t>{m4_three_sv_rate_hz} || | ||
| p.timing.asdus_per_frame != 1U || | ||
| p.sample_counter_modulus != context.sample_counter_modulus || | ||
| !p.asdu_options.sample_synchronized || | ||
| p.payload_size_bytes != 64U || p.channels.size() != 16U) { | ||
| result.errors.push_back( | ||
| name + ": M4.0 needs synchronized 4000 Hz 4I+4V, one " | ||
| "ASDU/frame and 64-byte canonical payload."); | ||
| continue; | ||
| } | ||
| if (classify_esp32p4_sv_profile(p) != Esp32P4SvProfileSupport::ready) { | ||
| result.errors.push_back( | ||
| name + ": host SCL profile is not supported by the " | ||
| "ESP32-P4 4I+4V device boundary."); | ||
| continue; | ||
| } | ||
|
|
||
| const auto device = compile_esp32p4_device_profile(p); | ||
| if (!device.ok()) { | ||
| result.errors.push_back( | ||
| name + ": failed to compile validated binary device profile."); | ||
| for (const auto& error : device.errors) { | ||
| result.errors.push_back(name + ": " + error); | ||
| } | ||
| continue; | ||
| } | ||
|
|
||
| candidate.streams[i] = M4CompiledSvStream{ | ||
| stream.ied_name, stream.control_block_reference, *device.profile}; | ||
|
|
||
| // The real SCD repeats APPID 0x4000 on three DIFFERENT MAC addresses; | ||
| // APPID-only uniqueness would wrongly reject this valid lab scenario. | ||
| for (std::size_t previous = 0U; previous < i; ++previous) { | ||
| if (stream.ied_name == candidate.streams[previous].ied_name && | ||
| stream.control_block_reference == | ||
| candidate.streams[previous].control_block_reference) { | ||
| result.errors.push_back( | ||
| name + ": duplicate IED-qualified control block."); | ||
| } | ||
| if (same_wire_identity(candidate.streams[i].device_profile, | ||
| candidate.streams[previous].device_profile)) { | ||
| result.errors.push_back( | ||
| name + ": duplicate complete SV wire identity."); | ||
| } | ||
| } | ||
| } | ||
| if (result.errors.empty()) result.roster = std::move(candidate); | ||
| return result; | ||
| } | ||
|
|
||
| } // namespace ar::iec61850::sampled_values | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <SCL xmlns="http://www.iec.ch/61850/2003/SCL"> | ||
| <Header id="AR_M4_3_MU_REFERENCE" version="1" revision="A"/> | ||
| <Communication><SubNetwork name="ProcessBus" type="8-MMS"> | ||
| <ConnectedAP iedName="MU01_4I_4V_1" apName="LD0"><SMV ldInst="MU01" cbName="MSVCB01"><Address><P type="VLAN-ID">0</P><P type="VLAN-PRIORITY">4</P><P type="MAC-Address">01-0C-CD-04-00-01</P><P type="APPID">4000</P></Address></SMV></ConnectedAP> | ||
| <ConnectedAP iedName="MU01_4I_4V_2" apName="LD0"><SMV ldInst="MU01" cbName="MSVCB01"><Address><P type="VLAN-ID">0</P><P type="VLAN-PRIORITY">4</P><P type="MAC-Address">01-0C-CD-04-00-02</P><P type="APPID">4000</P></Address></SMV></ConnectedAP> | ||
| <ConnectedAP iedName="MU01_4I_4V_3" apName="LD0"><SMV ldInst="MU01" cbName="MSVCB01"><Address><P type="VLAN-ID">0</P><P type="VLAN-PRIORITY">4</P><P type="MAC-Address">01-0C-CD-04-00-03</P><P type="APPID">4000</P></Address></SMV></ConnectedAP> | ||
| </SubNetwork></Communication> | ||
| <IED name="MU01_4I_4V_1" manufacturer="AR" type="MergingUnit" configVersion="G"><AccessPoint name="LD0"><Server><LDevice inst="MU01"><LN0 lnClass="LLN0" lnType="MULLN0"> | ||
| <DataSet name="PhsMeas1"> | ||
| <FCDA ldInst="MU01" lnClass="TCTR" lnInst="1" doName="AmpSv" daName="instMag.i" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TCTR" lnInst="1" doName="AmpSv" daName="q" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TCTR" lnInst="2" doName="AmpSv" daName="instMag.i" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TCTR" lnInst="2" doName="AmpSv" daName="q" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TCTR" lnInst="3" doName="AmpSv" daName="instMag.i" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TCTR" lnInst="3" doName="AmpSv" daName="q" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TCTR" lnInst="4" doName="AmpSv" daName="instMag.i" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TCTR" lnInst="4" doName="AmpSv" daName="q" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TVTR" lnInst="1" doName="VolSv" daName="instMag.i" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TVTR" lnInst="1" doName="VolSv" daName="q" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TVTR" lnInst="2" doName="VolSv" daName="instMag.i" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TVTR" lnInst="2" doName="VolSv" daName="q" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TVTR" lnInst="3" doName="VolSv" daName="instMag.i" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TVTR" lnInst="3" doName="VolSv" daName="q" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TVTR" lnInst="4" doName="VolSv" daName="instMag.i" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TVTR" lnInst="4" doName="VolSv" daName="q" fc="MX"/> | ||
| </DataSet> | ||
| <SampledValueControl multicast="true" name="MSVCB01" datSet="PhsMeas1" confRev="1" smvID="MU01_SV1" smpRate="4000" smpMod="SmpPerSec" nofASDU="1"><SmvOpts refreshTime="false" sampleSynchronized="true" sampleRate="false" dataSet="false" security="false" synchSourceId="false"/></SampledValueControl> | ||
| </LN0> | ||
| <LN lnType="CTR" lnClass="TCTR" inst="1"/> | ||
| <LN lnType="CTR" lnClass="TCTR" inst="2"/> | ||
| <LN lnType="CTR" lnClass="TCTR" inst="3"/> | ||
| <LN lnType="CTR" lnClass="TCTR" inst="4"/> | ||
| <LN lnType="VTR" lnClass="TVTR" inst="1"/> | ||
| <LN lnType="VTR" lnClass="TVTR" inst="2"/> | ||
| <LN lnType="VTR" lnClass="TVTR" inst="3"/> | ||
| <LN lnType="VTR" lnClass="TVTR" inst="4"/> | ||
| </LDevice></Server></AccessPoint></IED> | ||
| <IED name="MU01_4I_4V_2" manufacturer="AR" type="MergingUnit" configVersion="G"><AccessPoint name="LD0"><Server><LDevice inst="MU01"><LN0 lnClass="LLN0" lnType="MULLN0"> | ||
| <DataSet name="PhsMeas1"> | ||
| <FCDA ldInst="MU01" lnClass="TCTR" lnInst="1" doName="AmpSv" daName="instMag.i" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TCTR" lnInst="1" doName="AmpSv" daName="q" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TCTR" lnInst="2" doName="AmpSv" daName="instMag.i" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TCTR" lnInst="2" doName="AmpSv" daName="q" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TCTR" lnInst="3" doName="AmpSv" daName="instMag.i" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TCTR" lnInst="3" doName="AmpSv" daName="q" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TCTR" lnInst="4" doName="AmpSv" daName="instMag.i" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TCTR" lnInst="4" doName="AmpSv" daName="q" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TVTR" lnInst="1" doName="VolSv" daName="instMag.i" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TVTR" lnInst="1" doName="VolSv" daName="q" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TVTR" lnInst="2" doName="VolSv" daName="instMag.i" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TVTR" lnInst="2" doName="VolSv" daName="q" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TVTR" lnInst="3" doName="VolSv" daName="instMag.i" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TVTR" lnInst="3" doName="VolSv" daName="q" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TVTR" lnInst="4" doName="VolSv" daName="instMag.i" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TVTR" lnInst="4" doName="VolSv" daName="q" fc="MX"/> | ||
| </DataSet> | ||
| <SampledValueControl multicast="true" name="MSVCB01" datSet="PhsMeas1" confRev="1" smvID="MU01_SV2" smpRate="4000" smpMod="SmpPerSec" nofASDU="1"><SmvOpts refreshTime="false" sampleSynchronized="true" sampleRate="false" dataSet="false" security="false" synchSourceId="false"/></SampledValueControl> | ||
| </LN0> | ||
| <LN lnType="CTR" lnClass="TCTR" inst="1"/> | ||
| <LN lnType="CTR" lnClass="TCTR" inst="2"/> | ||
| <LN lnType="CTR" lnClass="TCTR" inst="3"/> | ||
| <LN lnType="CTR" lnClass="TCTR" inst="4"/> | ||
| <LN lnType="VTR" lnClass="TVTR" inst="1"/> | ||
| <LN lnType="VTR" lnClass="TVTR" inst="2"/> | ||
| <LN lnType="VTR" lnClass="TVTR" inst="3"/> | ||
| <LN lnType="VTR" lnClass="TVTR" inst="4"/> | ||
| </LDevice></Server></AccessPoint></IED> | ||
| <IED name="MU01_4I_4V_3" manufacturer="AR" type="MergingUnit" configVersion="G"><AccessPoint name="LD0"><Server><LDevice inst="MU01"><LN0 lnClass="LLN0" lnType="MULLN0"> | ||
| <DataSet name="PhsMeas1"> | ||
| <FCDA ldInst="MU01" lnClass="TCTR" lnInst="1" doName="AmpSv" daName="instMag.i" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TCTR" lnInst="1" doName="AmpSv" daName="q" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TCTR" lnInst="2" doName="AmpSv" daName="instMag.i" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TCTR" lnInst="2" doName="AmpSv" daName="q" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TCTR" lnInst="3" doName="AmpSv" daName="instMag.i" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TCTR" lnInst="3" doName="AmpSv" daName="q" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TCTR" lnInst="4" doName="AmpSv" daName="instMag.i" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TCTR" lnInst="4" doName="AmpSv" daName="q" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TVTR" lnInst="1" doName="VolSv" daName="instMag.i" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TVTR" lnInst="1" doName="VolSv" daName="q" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TVTR" lnInst="2" doName="VolSv" daName="instMag.i" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TVTR" lnInst="2" doName="VolSv" daName="q" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TVTR" lnInst="3" doName="VolSv" daName="instMag.i" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TVTR" lnInst="3" doName="VolSv" daName="q" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TVTR" lnInst="4" doName="VolSv" daName="instMag.i" fc="MX"/> | ||
| <FCDA ldInst="MU01" lnClass="TVTR" lnInst="4" doName="VolSv" daName="q" fc="MX"/> | ||
| </DataSet> | ||
| <SampledValueControl multicast="true" name="MSVCB01" datSet="PhsMeas1" confRev="1" smvID="MU01_SV3" smpRate="4000" smpMod="SmpPerSec" nofASDU="1"><SmvOpts refreshTime="false" sampleSynchronized="true" sampleRate="false" dataSet="false" security="false" synchSourceId="false"/></SampledValueControl> | ||
| </LN0> | ||
| <LN lnType="CTR" lnClass="TCTR" inst="1"/> | ||
| <LN lnType="CTR" lnClass="TCTR" inst="2"/> | ||
| <LN lnType="CTR" lnClass="TCTR" inst="3"/> | ||
| <LN lnType="CTR" lnClass="TCTR" inst="4"/> | ||
| <LN lnType="VTR" lnClass="TVTR" inst="1"/> | ||
| <LN lnType="VTR" lnClass="TVTR" inst="2"/> | ||
| <LN lnType="VTR" lnClass="TVTR" inst="3"/> | ||
| <LN lnType="VTR" lnClass="TVTR" inst="4"/> | ||
| </LDevice></Server></AccessPoint></IED> | ||
| <DataTypeTemplates> | ||
| <LNodeType id="MULLN0" lnClass="LLN0"/> | ||
| <LNodeType id="CTR" lnClass="TCTR"><DO name="AmpSv" type="AmpSAV"/></LNodeType> | ||
| <LNodeType id="VTR" lnClass="TVTR"><DO name="VolSv" type="VolSAV"/></LNodeType> | ||
| <DOType id="AmpSAV" cdc="SAV"><DA name="instMag" bType="Struct" type="AnalogueValue" fc="MX"/><DA name="q" bType="Quality" fc="MX"/></DOType> | ||
| <DOType id="VolSAV" cdc="SAV"><DA name="instMag" bType="Struct" type="AnalogueValue" fc="MX"/><DA name="q" bType="Quality" fc="MX"/></DOType> | ||
| <DAType id="AnalogueValue"><BDA name="i" bType="INT32"/></DAType> | ||
| </DataTypeTemplates></SCL> |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the SCD contains duplicate
IED@namedeclarations with different control-block names, this compiler returns a successful roster despite the parser reporting anIEDconflict. Reproduced by renaming the fixture's second IED toMU01_4I_4V_1and its control/binding toMSVCB02:document.conflictsreports the duplicate IED, butcompile().ok()is true. The pairwise control-reference check does not catch this, allowing profiles compiled from ambiguous IED/DataSet namespaces through the validation boundary. Preserve the deliberate repeated-APPID exception, but reject duplicate-IED conflicts before publishing the roster.AGENTS.md reference: AGENTS.md:L152-L152
Useful? React with 👍 / 👎.