Skip to content

Commit

Permalink
Merge pull request #255 from com-pas/develop
Browse files Browse the repository at this point in the history
Merge branch develop into main
  • Loading branch information
SaintierFr authored Mar 9, 2023
2 parents be542e2 + 6a5da6b commit 2258b1a
Show file tree
Hide file tree
Showing 73 changed files with 2,624 additions and 562 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
<sonar.coverage.exclusions>sct-coverage/**</sonar.coverage.exclusions>
<aggregate.report.dir>../sct-coverage/target/site/jacoco-aggregate/jacoco.xml</aggregate.report.dir>
<sonar.coverage.jacoco.xmlReportPaths>${basedir}/${aggregate.report.dir}</sonar.coverage.jacoco.xmlReportPaths>
<compas-core.version>0.12.0</compas-core.version>
<compas-core.version>0.13.0</compas-core.version>
<compas-scl-xsd.version>0.0.4</compas-scl-xsd.version>
<maven.plugin.javadoc>3.4.1</maven.plugin.javadoc>
<maven-source-plugin.version>3.2.1</maven-source-plugin.version>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: 2023 RTE FRANCE
//
// SPDX-License-Identifier: Apache-2.0

package org.lfenergy.compas.sct.commons.dto;

import org.lfenergy.compas.scl2007b4.model.TDurationInMilliSec;
import org.lfenergy.compas.sct.commons.scl.ied.ControlBlockAdapter;

/**
* This interface has a single method which provides network settings for a ControlBlock.
* These are used to create:
* - the Communication/SubNetwork/ConnectedAP/GSE element, which is the network configuration of a GSEControl block
* - the Communication/SubNetwork/ConnectedAP/SMV element, which is the network configuration of a SampledValueControl block
* It is a FunctionalInterface, so it can be implemented with a lambda expression.
*
* @see org.lfenergy.compas.sct.commons.util.ControlBlockNetworkSettingsCsvHelper
*/
@FunctionalInterface
public interface ControlBlockNetworkSettings {

/**
* This method provides a vlanId, vlanPriority, minTime, maxTime for this ControlBlock.
* vlanPriority will be ignored when vlanId is null.
*
* @param controlBlockAdapter ControlBlock for which we want to configure the communication section
* @return network settings : All fields are optional (meaning fields can be null).
* When the return value itself is null, the communication section will not be configured for this ControlBlock.
*/
Settings getNetworkSettings(ControlBlockAdapter controlBlockAdapter);

/**
* Network settings for ControlBlock communication
*
* @param vlanId id of the vlan
* @param vlanPriority priority for the vlan
* @param minTime minTime for GSE communication element
* @param maxTime maxTime for GSE communication element
*/
record Settings(Integer vlanId, Byte vlanPriority, TDurationInMilliSec minTime, TDurationInMilliSec maxTime) {
}

/**
* NetworkRanges for GSEControl and SampledValueControl
*
* @param gse NetworkRanges for GSEControl
* @param sampledValue NetworkRanges for SampledValueControl
*/
record RangesPerCbType(NetworkRanges gse, NetworkRanges sampledValue) {
}

/**
* Range of APPID and range of MAC-Address
*
* @param appIdStart range start for APPID (inclusive)
* @param appIdEnd range end for APPID (inclusive)
* @param macAddressStart range start for MAC-Addresses (inclusive). Ex: "01-0C-CD-01-00-00"
* @param macAddressEnd range end for MAC-Addresses (inclusive). Ex: "01-0C-CD-01-01-FF"
*/
record NetworkRanges(long appIdStart, long appIdEnd, String macAddressStart, String macAddressEnd) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,31 @@
import org.lfenergy.compas.scl2007b4.model.SCL;
import org.lfenergy.compas.scl2007b4.model.TCompasICDHeader;
import org.lfenergy.compas.scl2007b4.model.TIED;
import org.lfenergy.compas.sct.commons.dto.ControlBlockNetworkSettings;
import org.lfenergy.compas.sct.commons.dto.ControlBlockNetworkSettings.Settings;
import org.lfenergy.compas.sct.commons.dto.SclReport;
import org.lfenergy.compas.sct.commons.dto.SclReportItem;
import org.lfenergy.compas.sct.commons.exception.ScdException;
import org.lfenergy.compas.sct.commons.scl.ied.ControlBlockAdapter;
import org.lfenergy.compas.sct.commons.scl.ied.IEDAdapter;
import org.lfenergy.compas.sct.commons.scl.ied.LDeviceAdapter;
import org.lfenergy.compas.sct.commons.scl.ied.LN0Adapter;
import org.lfenergy.compas.sct.commons.util.ControlBlockEnum;
import org.lfenergy.compas.sct.commons.util.PrivateEnum;
import org.lfenergy.compas.sct.commons.util.Utils;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.lfenergy.compas.sct.commons.dto.ControlBlockNetworkSettings.NetworkRanges;
import static org.lfenergy.compas.sct.commons.dto.ControlBlockNetworkSettings.RangesPerCbType;

@UtilityClass
public class ExtRefService {

private static final String MESSAGE_MISSING_IED_NAME_PARAMETER = "IED.name parameter is missing";
private static final String CLIENT_IED_NAME = "The Client IED ";

/**
* Updates iedName attribute of all ExtRefs in the Scd.
Expand All @@ -40,21 +47,21 @@ public static SclReport updateAllExtRefIedNames(SCL scd) {
return new SclReport(sclRootAdapter, iedErrors);
}
Map<String, IEDAdapter> icdSystemVersionToIed = sclRootAdapter.streamIEDAdapters()
.collect(Collectors.toMap(
iedAdapter -> PrivateService.extractCompasPrivate(iedAdapter.getCurrentElem(), TCompasICDHeader.class)
.map(TCompasICDHeader::getICDSystemVersionUUID)
.orElseThrow(), // Value presence is checked by method validateIed called above
Function.identity()
));
.collect(Collectors.toMap(
iedAdapter -> iedAdapter.getCompasICDHeader()
.map(TCompasICDHeader::getICDSystemVersionUUID)
.orElseThrow(), // Value presence is checked by method validateIed called above
Function.identity()
));

List<SclReportItem> extRefErrors = sclRootAdapter.streamIEDAdapters()
.flatMap(IEDAdapter::streamLDeviceAdapters)
.filter(LDeviceAdapter::hasLN0)
.map(LDeviceAdapter::getLN0Adapter)
.filter(LN0Adapter::hasInputs)
.map(LN0Adapter::getInputsAdapter)
.map(inputsAdapter -> inputsAdapter.updateAllExtRefIedNames(icdSystemVersionToIed))
.flatMap(List::stream).toList();
.flatMap(IEDAdapter::streamLDeviceAdapters)
.filter(LDeviceAdapter::hasLN0)
.map(LDeviceAdapter::getLN0Adapter)
.filter(LN0Adapter::hasInputs)
.map(LN0Adapter::getInputsAdapter)
.map(inputsAdapter -> inputsAdapter.updateAllExtRefIedNames(icdSystemVersionToIed))
.flatMap(List::stream).toList();

return new SclReport(sclRootAdapter, extRefErrors);
}
Expand All @@ -67,53 +74,74 @@ private static List<SclReportItem> validateIed(SclRootAdapter sclRootAdapter) {

private static List<SclReportItem> checkIedCompasIcdHeaderAttributes(SclRootAdapter sclRootAdapter) {
return sclRootAdapter.streamIEDAdapters()
.map(iedAdapter -> {
Optional<TCompasICDHeader> compasPrivate = PrivateService.extractCompasPrivate(iedAdapter.getCurrentElem(), TCompasICDHeader.class);
if (compasPrivate.isEmpty()) {
return iedAdapter.buildFatalReportItem(String.format("IED has no Private %s element", PrivateEnum.COMPAS_ICDHEADER.getPrivateType()));
}
if (StringUtils.isBlank(compasPrivate.get().getICDSystemVersionUUID())
|| StringUtils.isBlank(compasPrivate.get().getIEDName())) {
return iedAdapter.buildFatalReportItem(String.format("IED private %s as no icdSystemVersionUUID or iedName attribute",
PrivateEnum.COMPAS_ICDHEADER.getPrivateType()));
}
return null;
}
).filter(Objects::nonNull)
.toList();
.map(iedAdapter -> {
Optional<TCompasICDHeader> compasPrivate = iedAdapter.getCompasICDHeader();
if (compasPrivate.isEmpty()) {
return iedAdapter.buildFatalReportItem(String.format("IED has no Private %s element", PrivateEnum.COMPAS_ICDHEADER.getPrivateType()));
}
if (StringUtils.isBlank(compasPrivate.get().getICDSystemVersionUUID())
|| StringUtils.isBlank(compasPrivate.get().getIEDName())) {
return iedAdapter.buildFatalReportItem(String.format("IED private %s as no icdSystemVersionUUID or iedName attribute",
PrivateEnum.COMPAS_ICDHEADER.getPrivateType()));
}
return null;
}
).filter(Objects::nonNull)
.toList();
}

private static List<SclReportItem> checkIedUnityOfIcdSystemVersionUuid(SclRootAdapter sclRootAdapter) {
Map<String, List<TIED>> systemVersionToIedList = sclRootAdapter.getCurrentElem().getIED().stream()
.collect(Collectors.groupingBy(ied -> PrivateService.extractCompasPrivate(ied, TCompasICDHeader.class)
.map(TCompasICDHeader::getICDSystemVersionUUID)
.orElse("")));
.collect(Collectors.groupingBy(ied -> PrivateService.extractCompasPrivate(ied, TCompasICDHeader.class)
.map(TCompasICDHeader::getICDSystemVersionUUID)
.orElse("")));

return systemVersionToIedList.entrySet().stream()
.filter(entry -> StringUtils.isNotBlank(entry.getKey()))
.filter(entry -> entry.getValue().size() > 1)
.map(entry -> SclReportItem.fatal(entry.getValue().stream()
.map(tied -> new IEDAdapter(sclRootAdapter, tied))
.map(IEDAdapter::getXPath)
.collect(Collectors.joining(", ")),
"/IED/Private/compas:ICDHeader[@ICDSystemVersionUUID] must be unique" +
" but the same ICDSystemVersionUUID was found on several IED."))
.toList();
.filter(entry -> StringUtils.isNotBlank(entry.getKey()))
.filter(entry -> entry.getValue().size() > 1)
.map(entry -> SclReportItem.fatal(entry.getValue().stream()
.map(tied -> new IEDAdapter(sclRootAdapter, tied))
.map(IEDAdapter::getXPath)
.collect(Collectors.joining(", ")),
"/IED/Private/compas:ICDHeader[@ICDSystemVersionUUID] must be unique" +
" but the same ICDSystemVersionUUID was found on several IED."))
.toList();
}

/**
* Create All DataSet and ControlBlock in the SCL based on the ExtRef
*
* @param scd input SCD object. It could be modified by adding new DataSet and ControlBlocks
* @return a report with all errors encountered
*/
public static SclReport createDataSetAndControlBlocks(SCL scd) {
SclRootAdapter sclRootAdapter = new SclRootAdapter(scd);
Stream<LDeviceAdapter> lDeviceAdapters = sclRootAdapter.streamIEDAdapters().flatMap(IEDAdapter::streamLDeviceAdapters);
return createDataSetAndControlBlocks(sclRootAdapter, lDeviceAdapters);
}

/**
* Create All DataSet and ControlBlock for the ExtRef in given IED
*
* @param scd input SCD object. The object will be modified with the new DataSet and ControlBlocks
* @param targetIedName the name of the IED where the ExtRef are
* @return a report with all the errors encountered
*/
public static SclReport createDataSetAndControlBlocks(SCL scd, String targetIedName) {
SclRootAdapter sclRootAdapter = new SclRootAdapter(scd);
IEDAdapter iedAdapter = sclRootAdapter.getIEDAdapterByName(targetIedName);
return createDataSetAndControlBlocks(sclRootAdapter, iedAdapter.streamLDeviceAdapters());

}

/**
* Create All DataSet and ControlBlock for the ExtRef in this LDevice
*
* @param scd input SCD object. The object will be modified with the new DataSet and ControlBlocks
* @param targetIedName the name of the IED where the ExtRef are
* @param targetLDeviceInst the name of the LDevice where the ExtRef are
* @return a report with all encountered errors
*/
public static SclReport createDataSetAndControlBlocks(SCL scd, String targetIedName, String targetLDeviceInst) {
if (StringUtils.isBlank(targetIedName)) {
throw new ScdException(MESSAGE_MISSING_IED_NAME_PARAMETER);
Expand All @@ -126,9 +154,74 @@ public static SclReport createDataSetAndControlBlocks(SCL scd, String targetIedN

private static SclReport createDataSetAndControlBlocks(SclRootAdapter sclRootAdapter, Stream<LDeviceAdapter> lDeviceAdapters) {
List<SclReportItem> sclReportItems = lDeviceAdapters
.map(LDeviceAdapter::createDataSetAndControlBlocks)
.flatMap(List::stream)
.toList();
.map(LDeviceAdapter::createDataSetAndControlBlocks)
.flatMap(List::stream)
.toList();
return new SclReport(sclRootAdapter, sclReportItems);
}

/**
* Configure the network for all the ControlBlocks.
* Create (or update if already existing) these elements
* - the Communication/SubNetwork/ConnectedAP/GSE element, for the GSEControl blocks
* - the Communication/SubNetwork/ConnectedAP/SMV element, for the SampledValueControl blocks
*
* @param scd input SCD object. The object will be modified with the new DataGSESet and SMV elements
* @param controlBlockNetworkSettings a method tha gives the network configuration information for a given ControlBlock
* @param rangesPerCbType provide NetworkRanges for GSEControl and SampledValueControl. NetworkRanges contains :
* start-end app APPID range (long value), start-end MAC-Addresses (Mac-Addresses values: Ex: "01-0C-CD-01-01-FF")
* @return a report with all the errors encountered
* @see Utils#macAddressToLong(String) for the expected MAC address format
* @see ControlBlockNetworkSettings
* @see ControlBlockNetworkSettings.RangesPerCbType
* @see ControlBlockNetworkSettings.NetworkRanges
*/
public static SclReport configureNetworkForAllControlBlocks(SCL scd, ControlBlockNetworkSettings controlBlockNetworkSettings,
RangesPerCbType rangesPerCbType) {
List<SclReportItem> sclReportItems = new ArrayList<>();
sclReportItems.addAll(configureNetworkForControlBlocks(scd, controlBlockNetworkSettings, rangesPerCbType.gse(), ControlBlockEnum.GSE));
sclReportItems.addAll(configureNetworkForControlBlocks(scd, controlBlockNetworkSettings, rangesPerCbType.sampledValue(), ControlBlockEnum.SAMPLED_VALUE));
return new SclReport(new SclRootAdapter(scd), sclReportItems);
}

private static List<SclReportItem> configureNetworkForControlBlocks(SCL scd, ControlBlockNetworkSettings controlBlockNetworkSettings,
NetworkRanges networkRanges, ControlBlockEnum controlBlockEnum) {
PrimitiveIterator.OfLong appIdIterator = Utils.sequence(networkRanges.appIdStart(), networkRanges.appIdEnd());
Iterator<String> macAddressIterator = Utils.macAddressSequence(networkRanges.macAddressStart(), networkRanges.macAddressEnd());

SclRootAdapter sclRootAdapter = new SclRootAdapter(scd);
return sclRootAdapter.streamIEDAdapters()
.flatMap(iedAdapter ->
iedAdapter.streamLDeviceAdapters()
.filter(LDeviceAdapter::hasLN0)
.map(LDeviceAdapter::getLN0Adapter)
.flatMap(ln0Adapter -> ln0Adapter.streamControlBlocks(controlBlockEnum))
.map(controlBlockAdapter -> configureControlBlockNetwork(controlBlockNetworkSettings, appIdIterator, macAddressIterator, controlBlockAdapter)))
.flatMap(Optional::stream)
.toList();
}

private static Optional<SclReportItem> configureControlBlockNetwork(ControlBlockNetworkSettings controlBlockNetworkSettings, PrimitiveIterator.OfLong appIdIterator, Iterator<String> macAddressIterator, ControlBlockAdapter controlBlockAdapter) {
Settings settings = controlBlockNetworkSettings.getNetworkSettings(controlBlockAdapter);

if (settings == null) {
return Optional.of(controlBlockAdapter.buildFatalReportItem(
"Cannot configure network for this ControlBlock because no settings was provided"));
}
if (settings.vlanId() == null) {
return Optional.of(controlBlockAdapter.buildFatalReportItem(
"Cannot configure network for this ControlBlock because no Vlan Id was provided in the settings"));
}
if (!appIdIterator.hasNext()) {
return Optional.of(controlBlockAdapter.buildFatalReportItem(
"Cannot configure network for this ControlBlock because range of appId is exhausted"));
}
if (!macAddressIterator.hasNext()) {
return Optional.of(controlBlockAdapter.buildFatalReportItem(
"Cannot configure network for this ControlBlock because range of MAC Address is exhausted"));
}

return controlBlockAdapter.configureNetwork(appIdIterator.nextLong(), macAddressIterator.next(), settings.vlanId(), settings.vlanPriority(),
settings.minTime(), settings.maxTime());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.lfenergy.compas.scl2007b4.model.*;
import org.lfenergy.compas.sct.commons.exception.ScdException;
import org.lfenergy.compas.sct.commons.scl.com.CommunicationAdapter;
import org.lfenergy.compas.sct.commons.scl.com.ConnectedAPAdapter;
import org.lfenergy.compas.sct.commons.scl.dtt.DataTypeTemplateAdapter;
import org.lfenergy.compas.sct.commons.scl.header.HeaderAdapter;
import org.lfenergy.compas.sct.commons.scl.ied.IEDAdapter;
Expand Down Expand Up @@ -291,4 +292,20 @@ public IEDAdapter checkObjRef(String val) throws ScdException {
throw new ScdException("Invalid ObjRef: " + val);
}

/**
* Find a ConnectedAp element withe given iedName and apName
* @param iedName iedName
* @param apName apName
* @return the first ConnectedAp which match the given iedName and apName, or empty Optional if none found
*/
public Optional<ConnectedAPAdapter> findConnectedApAdapter(String iedName, String apName) {
if (!currentElem.isSetCommunication()) {
return Optional.empty();
}
CommunicationAdapter communicationAdapter = new CommunicationAdapter(this, currentElem.getCommunication());
return communicationAdapter.getSubNetworkAdapters().stream()
.map(subNetworkAdapter -> subNetworkAdapter.findConnectedAPAdapter(iedName, apName))
.flatMap(Optional::stream)
.findFirst();
}
}
Loading

0 comments on commit 2258b1a

Please sign in to comment.