-
Notifications
You must be signed in to change notification settings - Fork 9
fix(#525): Bug : LNodeTypeAdapter.getDataAttributeRef should not throw exception Multiple Data Attribute found #526
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
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ | |
|
||
import lombok.NonNull; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.apache.commons.lang3.StringUtils; | ||
import org.apache.commons.lang3.tuple.Pair; | ||
import org.lfenergy.compas.scl2007b4.model.TDO; | ||
import org.lfenergy.compas.scl2007b4.model.TDOType; | ||
|
@@ -20,7 +21,6 @@ | |
import org.lfenergy.compas.sct.commons.util.Utils; | ||
|
||
import java.util.*; | ||
import java.util.stream.Collectors; | ||
|
||
/** | ||
* A representation of the model object | ||
|
@@ -41,7 +41,7 @@ | |
* <li>{@link LNodeTypeAdapter#getId() <em>Returns the value of the <b>id </b>attribute</em>}</li> | ||
* <li>{@link LNodeTypeAdapter#getLNClass <em>Returns the value of the <b>lnClass </b>attribute</em>}</li> | ||
* <li>{@link LNodeTypeAdapter#getDataAttributeRefs(DataAttributeRef)} <em>Returns <b>DataAttributeRef </b> list</em>}</li> | ||
* <li>{@link LNodeTypeAdapter#getDataAttributeRefs(String)} <em>Returns <b>DataAttributeRef </b> list</em>}</li> | ||
* <li>{@link LNodeTypeAdapter#getDataAttributeRef(String)} <em>Returns <b>DataAttributeRef </b> list</em>}</li> | ||
* </ul> | ||
* <li>Checklist functions</li> | ||
* <ul> | ||
|
@@ -95,10 +95,8 @@ public boolean hasSameContentAs(TLNodeType tlNodeType) { | |
return false; | ||
} | ||
|
||
if (Objects.equals( | ||
currentElem.getLnClass().toArray(new String[0]), | ||
tlNodeType.getLnClass().toArray(new String[0]) | ||
) || !Objects.equals(currentElem.getIedType(), tlNodeType.getIedType())) { | ||
if (Objects.equals(currentElem.getLnClass(), tlNodeType.getLnClass()) | ||
|| !Objects.equals(currentElem.getIedType(), tlNodeType.getIedType())) { | ||
return false; | ||
} | ||
|
||
|
@@ -112,9 +110,9 @@ public boolean hasSameContentAs(TLNodeType tlNodeType) { | |
TDO inTDO = inTDOs.get(i); | ||
TDO thisTDO = thisTDOs.get(i); | ||
if (!thisTDO.getType().equals(inTDO.getType()) | ||
|| !thisTDO.getName().equals(inTDO.getName()) | ||
|| thisTDO.isTransient() != inTDO.isTransient() | ||
|| !Objects.equals(thisTDO.getAccessControl(), inTDO.getAccessControl())) { | ||
|| !thisTDO.getName().equals(inTDO.getName()) | ||
|| thisTDO.isTransient() != inTDO.isTransient() | ||
|| !Objects.equals(thisTDO.getAccessControl(), inTDO.getAccessControl())) { | ||
return false; | ||
} | ||
} | ||
|
@@ -139,7 +137,7 @@ public boolean containsDOWithDOTypeId(String doTypeId) { | |
*/ | ||
public String getLNClass() { | ||
if (!currentElem.getLnClass().isEmpty()) { | ||
return currentElem.getLnClass().get(0); | ||
return currentElem.getLnClass().getFirst(); | ||
} | ||
return null; | ||
} | ||
|
@@ -195,7 +193,7 @@ public List<DataAttributeRef> getDataAttributeRefs(@NonNull DataAttributeRef fil | |
} | ||
|
||
/** | ||
* Return a list of summarized Data Attribute References beginning from given this LNodeType. | ||
* Return a DataAttributeRef beginning from this LNodeType. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good |
||
* The key point in the algorithm is to find where the DO/SDO part ends and the DA/BDA part begins. | ||
* Once it is found, we can use the usual method DOTypeAdapter#getDataAttributeRefs to retrieve the DataAttributeRef | ||
* For example, with input "Do1.da1", we want to find the "da1" which is the DA/BDA part and also the DOTypeAdapter of "Do1" (to call DOTypeAdapter#getDataAttributeRefs) | ||
|
@@ -209,19 +207,19 @@ public List<DataAttributeRef> getDataAttributeRefs(@NonNull DataAttributeRef fil | |
* | ||
* @param dataRef complete reference of Data Attribute including DO name, SDO names (optional), DA name, BDA names (optional). | ||
* Ex: Do.sdo1.sdo2.da.bda1.bda2 | ||
* @return list of completed Data Attribute References beginning from this LNodeType. | ||
* @return the Data Attribute References beginning from this LNodeType. | ||
* @apiNote This method doesn't check relationship between DO/SDO and DA. Check should be done by caller | ||
*/ | ||
public DataAttributeRef getDataAttributeRefs(@NonNull String dataRef) { | ||
public Optional<DataAttributeRef> getDataAttributeRef(@NonNull String dataRef) { | ||
LinkedList<String> dataRefList = new LinkedList<>(Arrays.asList(dataRef.split("\\."))); | ||
if (dataRefList.size() < 2) { | ||
throw new ScdException("Invalid data reference %s. At least DO name and DA name are required".formatted(dataRef)); | ||
if (dataRefList.size() < 2 || dataRefList.stream().anyMatch(StringUtils::isBlank)) { | ||
throw new ScdException("Invalid data reference '%s'. At least DO name and DA name are required".formatted(dataRef)); | ||
} | ||
// 1. Get the DO | ||
String doName = dataRefList.remove(); | ||
return getDOAdapterByName(doName) | ||
.flatMap(doAdapter -> getDataTypeTemplateAdapter().getDOTypeAdapterById(doAdapter.getType())) | ||
.map(doTypeAdapter -> { | ||
.flatMap(doTypeAdapter -> { | ||
// 2. find the SDOs, if any | ||
List<String> sdoAccumulator = new ArrayList<>(); | ||
List<String> daAccumulator = new ArrayList<>(); | ||
|
@@ -251,17 +249,14 @@ public DataAttributeRef getDataAttributeRefs(@NonNull String dataRef) { | |
rootDataAttributeRef.setDoName(doTypeName); | ||
DataAttributeRef filter = new DataAttributeRef(); | ||
filter.setDaName(new DaTypeName(String.join(".", daAccumulator))); | ||
List<DataAttributeRef> dataAttributeRefs = deepestDo.getDataAttributeRefs(rootDataAttributeRef, filter); | ||
// We want exactly one result | ||
if (dataAttributeRefs.size() > 1) { | ||
throw new ScdException("Multiple Data Attribute found for this data reference %s in LNodeType.lnClass=%s, LNodeType.id=%s. Found DA : %s ".formatted(dataRef, getLNClass(), getId(), dataAttributeRefs.stream().map(DataAttributeRef::getDataAttributes).collect(Collectors.joining(", ")))); | ||
} | ||
if (dataAttributeRefs.isEmpty() || !dataRef.equals(dataAttributeRefs.get(0).getDataAttributes())) { | ||
return null; | ||
} | ||
return dataAttributeRefs.get(0); | ||
}).orElseThrow(() -> | ||
new ScdException("No Data Attribute found with this reference %s for LNodeType.lnClass=%s, LNodeType.id=%s ".formatted(dataRef, getLNClass(), getId()))); | ||
return deepestDo.getDataAttributeRefs(rootDataAttributeRef, filter) | ||
.stream() | ||
.filter(dataAttributeRef -> dataRef.equals(dataAttributeRef.getDataAttributes())) | ||
.reduce((dar1, dar2) -> { | ||
// 2 data attribute ref with the same name in the same LNodeType means the SCL is not valid, according to XSD | ||
throw new ScdException("Multiple Data Attribute found for this data reference '%s' in LNodeType.lnClass=%s, LNodeType.id=%s. Found DA : '%s' ".formatted(dataRef, getLNClass(), getId(), dar1)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is it tested ? |
||
}); | ||
}); | ||
} | ||
|
||
/** | ||
|
@@ -353,7 +348,7 @@ public void checkDoAndDaTypeName(@NonNull DoTypeName doTypeName, @NonNull DaType | |
daTypeName.setFc(daAdapter.getCurrentElem().getFc()); | ||
DATypeAdapter daTypeAdapter = parentAdapter.getDATypeAdapterById(daAdapter.getType()) | ||
.orElseThrow(() -> new ScdException(String.format("Unknown DAType (%s) referenced by DA(%s)", daAdapter.getType(), daAdapter.getName())) | ||
); | ||
); | ||
daTypeAdapter.check(daTypeName); | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,7 @@ | |
import org.lfenergy.compas.sct.commons.scl.SclRootAdapter; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've got 2 test failing in my IDE : |
||
import java.util.List; | ||
import java.util.Optional; | ||
|
||
import static org.assertj.core.api.Assertions.*; | ||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; | ||
|
@@ -126,7 +127,7 @@ void containsDOWithDOTypeId_should_check_if_LNodeType_contains_DO_with_specific_ | |
void testGetDataAttributeRefs() { | ||
// Given | ||
DataTypeTemplateAdapter dttAdapter = initDttAdapterFromFile(SCD_DTT); | ||
LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("LN1").get()); | ||
LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("LN1").orElseThrow()); | ||
DataAttributeRef rootDataAttributeRef = new DataAttributeRef(); | ||
rootDataAttributeRef.setDoName(new DoTypeName("Op")); | ||
DataAttributeRef filter = new DataAttributeRef(); | ||
|
@@ -151,63 +152,98 @@ void testGetDataAttributeRefs() { | |
|
||
@Test | ||
@Tag("issue-321") | ||
void testGetDataAttributeRefsString() { | ||
void getDataAttributeRef_should_succeed() { | ||
// Given | ||
DataTypeTemplateAdapter dttAdapter = initDttAdapterFromFile(SCD_DTT); | ||
LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("LN1").get()); | ||
LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("LN1").orElseThrow()); | ||
// When | ||
var dataAttributeRefs = lNodeTypeAdapter.getDataAttributeRefs("StrVal.origin.origin.ctlVal"); | ||
Optional<DataAttributeRef> dataAttributeRefs = lNodeTypeAdapter.getDataAttributeRef("StrVal.origin.origin.ctlVal"); | ||
// Then | ||
assertThat(dataAttributeRefs).isNotNull(); | ||
assertThat(dataAttributeRefs).isPresent(); | ||
} | ||
|
||
@Test | ||
void getDataAttributeRefs_should_find_DO_SDO_DA_and_BDA() { | ||
void getDataAttributeRef_should_find_DO_SDO_DA_and_BDA() { | ||
// Given | ||
DataTypeTemplateAdapter dttAdapter = initDttAdapterFromFile(SCD_DTT_DO_SDO_DA_BDA); | ||
LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("LN1").orElseThrow()); | ||
// When | ||
DataAttributeRef dataAttributeRefs = lNodeTypeAdapter.getDataAttributeRefs("Do1.sdo1.sdo2.da2.bda1.bda2"); | ||
Optional<DataAttributeRef> optDataAttributeRef = lNodeTypeAdapter.getDataAttributeRef("Do1.sdo1.sdo2.da2.bda1.bda2"); | ||
// Then | ||
assertThat(dataAttributeRefs).extracting(DataAttributeRef::getDoRef, DataAttributeRef::getDaRef) | ||
assertThat(optDataAttributeRef).isPresent(); | ||
DataAttributeRef dataAttributeRef = optDataAttributeRef.get(); | ||
assertThat(dataAttributeRef).extracting(DataAttributeRef::getDoRef, DataAttributeRef::getDaRef) | ||
.containsExactly("Do1.sdo1.sdo2", "da2.bda1.bda2"); | ||
assertThat(dataAttributeRefs.getDoName().getCdc()).isEqualTo(TPredefinedCDCEnum.WYE); | ||
assertThat(dataAttributeRefs.getDaName()).extracting(DaTypeName::getBType, DaTypeName::getFc) | ||
assertThat(dataAttributeRef.getDoName().getCdc()).isEqualTo(TPredefinedCDCEnum.WYE); | ||
assertThat(dataAttributeRef.getDaName()).extracting(DaTypeName::getBType, DaTypeName::getFc) | ||
.containsExactly(TPredefinedBasicTypeEnum.ENUM, TFCEnum.ST); | ||
} | ||
|
||
@Test | ||
void getDataAttributeRefs_should_find_DO_and_DA() { | ||
void getDataAttributeRef_should_find_DO_and_DA() { | ||
// Given | ||
DataTypeTemplateAdapter dttAdapter = initDttAdapterFromFile(SCD_DTT_DO_SDO_DA_BDA); | ||
LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("LN1").orElseThrow()); | ||
// When | ||
DataAttributeRef dataAttributeRefs = lNodeTypeAdapter.getDataAttributeRefs("Do1.da1"); | ||
Optional<DataAttributeRef> optDataAttributeRefs = lNodeTypeAdapter.getDataAttributeRef("Do1.da1"); | ||
// Then | ||
assertThat(optDataAttributeRefs).isPresent(); | ||
DataAttributeRef dataAttributeRefs = optDataAttributeRefs.get(); | ||
assertThat(dataAttributeRefs).extracting(DataAttributeRef::getDoRef, DataAttributeRef::getDaRef) | ||
.containsExactly("Do1", "da1"); | ||
assertThat(dataAttributeRefs.getDoName().getCdc()).isEqualTo(TPredefinedCDCEnum.WYE); | ||
assertThat(dataAttributeRefs.getDaName()).extracting(DaTypeName::getBType, DaTypeName::getFc) | ||
.containsExactly(TPredefinedBasicTypeEnum.BOOLEAN, TFCEnum.ST); | ||
} | ||
|
||
|
||
@Test | ||
void getDataAttributeRef_when_same_da_in_multiple_sdo_should_find_the_correct_DO_DA() { | ||
// Given | ||
DataTypeTemplateAdapter dttAdapter = initDttAdapterFromFile(SCD_DTT_DO_MULTIPLE_DA); | ||
LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("LN1").orElseThrow()); | ||
// When | ||
Optional<DataAttributeRef> optDataAttributeRef = lNodeTypeAdapter.getDataAttributeRef("Do1.da1"); | ||
// Then | ||
assertThat(optDataAttributeRef).isPresent(); | ||
DataAttributeRef dataAttributeRef = optDataAttributeRef.get(); | ||
assertThat(dataAttributeRef).extracting(DataAttributeRef::getDoRef, DataAttributeRef::getDaRef) | ||
.containsExactly("Do1", "da1"); | ||
assertThat(dataAttributeRef.getDoName().getCdc()).isEqualTo(TPredefinedCDCEnum.WYE); | ||
assertThat(dataAttributeRef.getDaName()).extracting(DaTypeName::getBType, DaTypeName::getFc) | ||
.containsExactly(TPredefinedBasicTypeEnum.BOOLEAN, TFCEnum.ST); | ||
} | ||
|
||
|
||
@ParameterizedTest | ||
@ValueSource(strings = {"", "malformed", "Do1", "InexistantDo.da1", "Do1.inexistantDa", "Do1.da1.inexistantBda", "Do1.sdo1.inexistantSdo.da2", "Do1.sdo1.sdo2.da2.bda1.inexistantBda"}) | ||
void getDataAttributeRefs_when_dataRef_not_found_should_throw_exception(String dataRef) { | ||
@ValueSource(strings = {"", "malformed", "Do1", "Do1.", ".da1"}) | ||
void getDataAttributeRef_when_dataRef_parameter_malformed_should_throw_exception(String dataRef) { | ||
// Given | ||
DataTypeTemplateAdapter dttAdapter = initDttAdapterFromFile(SCD_DTT_DO_SDO_DA_BDA); | ||
LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("LN1").orElseThrow()); | ||
// When & Then | ||
assertThatThrownBy(() -> lNodeTypeAdapter.getDataAttributeRefs(dataRef)) | ||
assertThatThrownBy(() -> lNodeTypeAdapter.getDataAttributeRef(dataRef)) | ||
.isInstanceOf(ScdException.class); | ||
} | ||
|
||
@ParameterizedTest | ||
@ValueSource(strings = {"InexistantDo.da1", "Do1.inexistantDa", "Do1.da1.inexistantBda", "Do1.sdo1.inexistantSdo.da2", "Do1.sdo1.sdo2.da2.bda1.inexistantBda"}) | ||
void getDataAttributeRef_when_dataRef_not_found_should_return_empty(String dataRef) { | ||
// Given | ||
DataTypeTemplateAdapter dttAdapter = initDttAdapterFromFile(SCD_DTT_DO_SDO_DA_BDA); | ||
LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("LN1").orElseThrow()); | ||
// When & Then | ||
Optional<DataAttributeRef> optDataAttributeRef = lNodeTypeAdapter.getDataAttributeRef(dataRef); | ||
// Then | ||
assertThat(optDataAttributeRef).isEmpty(); | ||
} | ||
|
||
@Test | ||
@Tag("issue-321") | ||
void testCheck() { | ||
// Given | ||
DataTypeTemplateAdapter dttAdapter = initDttAdapterFromFile(SCD_DTT); | ||
LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("LN1").get()); | ||
LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("LN1").orElseThrow()); | ||
DoTypeName doTypeName1 = new DoTypeName(""); | ||
DaTypeName daTypeName1 = new DaTypeName(""); | ||
// When Then | ||
|
@@ -239,7 +275,7 @@ void getDataAttributeRefByDaName_should_return_list_of_DataAttributeRef() { | |
// Given | ||
DataTypeTemplateAdapter dttAdapter = initDttAdapterFromFile(SCD_DTT_DIFF_CONTENT_SAME_ID); | ||
DaTypeName daTypeName = new DaTypeName("antRef","origin.ctlVal"); | ||
LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("LN1").get()); | ||
LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("LN1").orElseThrow()); | ||
// When | ||
List<DataAttributeRef> dataAttributeRefs = assertDoesNotThrow(()-> lNodeTypeAdapter.getDataAttributeRefByDaName(daTypeName)); | ||
// Then | ||
|
@@ -271,7 +307,7 @@ void addPrivate_with_type_and_source_should_create_Private() { | |
void elementXPath_should_return_expected_xpath_value() { | ||
// Given | ||
DataTypeTemplateAdapter dttAdapter = initDttAdapterFromFile(SCD_DTT); | ||
LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() ->dttAdapter.getLNodeTypeAdapterById("LN1").get()); | ||
LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("LN1").orElseThrow()); | ||
// When | ||
String result = lNodeTypeAdapter.elementXPath(); | ||
// Then | ||
|
@@ -299,7 +335,7 @@ void findMatchingDOType_shouldFindOneDO() { | |
dttAdapter.getCurrentElem().getDOType().add(tdoType); | ||
|
||
dttAdapter.getCurrentElem().getLNodeType().add(tlNodeType); | ||
LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("ID").get()); | ||
LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("ID").orElseThrow()); | ||
|
||
ExtRefSignalInfo signalInfo = new ExtRefSignalInfo(); | ||
signalInfo.setPDO("P_DO12"); | ||
|
@@ -335,7 +371,7 @@ void checkMatchingDOType_whenDOUnknown_shouldThrowException() { | |
dttAdapter.getCurrentElem().getDOType().add(tdoType); | ||
|
||
dttAdapter.getCurrentElem().getLNodeType().add(tlNodeType); | ||
LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("ID").get()); | ||
LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("ID").orElseThrow()); | ||
//When Then | ||
assertThatThrownBy(() -> lNodeTypeAdapter.findMatchingDOType(signalInfo)) | ||
.isInstanceOf(IllegalArgumentException.class) | ||
|
@@ -368,7 +404,7 @@ void checkMatchingDOType_whenDONotReferenced_shouldThrowException() { | |
dttAdapter.getCurrentElem().getDOType().add(tdoType); | ||
|
||
dttAdapter.getCurrentElem().getLNodeType().add(tlNodeType); | ||
LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("ID").get()); | ||
LNodeTypeAdapter lNodeTypeAdapter = assertDoesNotThrow(() -> dttAdapter.getLNodeTypeAdapterById("ID").orElseThrow()); | ||
//When Then | ||
assertThatThrownBy(() -> lNodeTypeAdapter.findMatchingDOType(signalInfo)) | ||
.isInstanceOf(IllegalArgumentException.class) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<!-- SPDX-FileCopyrightText: 2023 RTE FRANCE --> | ||
<!-- --> | ||
<!-- SPDX-License-Identifier: Apache-2.0 --> | ||
<SCL xmlns="http://www.iec.ch/61850/2003/SCL" version="2007" revision="B" release="4"> | ||
<Header id="HeaderID" version="version" revision="Revision" toolID="toolID"/> | ||
<DataTypeTemplates> | ||
<LNodeType id="LN1" lnClass="ANCR"> | ||
<DO name="Do1" type="DO1"/> | ||
</LNodeType> | ||
<DOType cdc="WYE" id="DO1"> | ||
<SDO name="sdo1" type="SDO1"/> | ||
<SDO name="sdo2" type="SDO1"/> | ||
<DA name="da1" bType="BOOLEAN" fc="ST"/> | ||
<DA name="unused" bType="BOOLEAN" fc="ST"/> | ||
</DOType> | ||
<DOType cdc="WYE" id="SDO1"> | ||
<DA name="da1" bType="BOOLEAN" fc="ST"/> | ||
<DA name="unused" bType="BOOLEAN" fc="ST"/> | ||
</DOType> | ||
</DataTypeTemplates> | ||
</SCL> |
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.
bof