Skip to content

Commit

Permalink
feat(390): RSR-993 reuse old appId and macAddresses when configureNet…
Browse files Browse the repository at this point in the history
…workForAllControlBlocks

Signed-off-by: massifben <105049157+massifben@users.noreply.github.com>
  • Loading branch information
massifben committed May 28, 2024
1 parent 3c1533e commit befecc5
Show file tree
Hide file tree
Showing 7 changed files with 321 additions and 161 deletions.
3 changes: 1 addition & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,8 @@
<jaxb2-maven-plugin.version>3.1.0</jaxb2-maven-plugin.version>

<junit-version>5.9.0</junit-version>
<logback-classic.version>1.4.5</logback-classic.version>
<logback-classic.version>1.5.6</logback-classic.version>
<assertj.version>3.22.0</assertj.version>
<!-- with version 18.30, lombok is compatible with JDK 21 -->
<lombok.version>1.18.30</lombok.version>
<mockito.version>5.5.0</mockito.version>
<jackson-databind.version>2.13.4.1</jackson-databind.version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.lfenergy.compas.scl2007b4.model.LN0;
import org.lfenergy.compas.scl2007b4.model.SCL;
import org.lfenergy.compas.sct.commons.ControlBlockEditorService;
import org.lfenergy.compas.sct.commons.LdeviceService;
import org.lfenergy.compas.sct.commons.SclService;
import org.lfenergy.compas.sct.commons.SubstationService;
import org.lfenergy.compas.sct.commons.api.ControlBlockEditor;
Expand All @@ -34,7 +35,7 @@ class SclAutomationServiceIntegrationTest {
private SclAutomationService sclAutomationService ;
private static final SclEditor sclEditor = new SclService() ;
private static final SubstationEditor substationEditor = new SubstationService() ;
private static final ControlBlockEditor controlBlockEditor = new ControlBlockEditorService(new ControlService()) ;
private static final ControlBlockEditor controlBlockEditor = new ControlBlockEditorService(new ControlService(), new LdeviceService()) ;

private HeaderDTO headerDTO;

Expand Down Expand Up @@ -106,7 +107,7 @@ void createSCD_WithManyHItem_should_return_generatedSCD() {
// Then
assertThat(scd.getHeader().getId()).isNotNull();
assertThat(scd.getHeader().getHistory().getHitem()).hasSize(1);
assertThat(scd.getHeader().getHistory().getHitem().get(0).getWhat()).isEqualTo("what");
assertThat(scd.getHeader().getHistory().getHitem().getFirst().getWhat()).isEqualTo("what");
assertIsMarshallable(scd);
}

Expand Down Expand Up @@ -165,7 +166,7 @@ void createSCD_should_delete_ControlBlocks_DataSet_and_ExtRef_src_attributes() {

assertThat(ln0.getDataSet()).isEmpty();
assertThat(ln0.getInputs().getExtRef()).hasSize(2);
assertThat(ln0.getInputs().getExtRef().get(0).isSetSrcLDInst()).isFalse();
assertThat(ln0.getInputs().getExtRef().getFirst().isSetSrcLDInst()).isFalse();
assertIsMarshallable(scd);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import org.lfenergy.compas.sct.commons.exception.ScdException;
import org.lfenergy.compas.sct.commons.model.cbcom.*;
import org.lfenergy.compas.sct.commons.model.da_comm.DACOMM;
import org.lfenergy.compas.sct.commons.model.da_comm.FCDAs;
import org.lfenergy.compas.sct.commons.scl.ControlService;
import org.lfenergy.compas.sct.commons.scl.SclRootAdapter;
import org.lfenergy.compas.sct.commons.scl.ied.IEDAdapter;
Expand All @@ -26,8 +25,10 @@
import java.math.BigInteger;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.LongStream;
import java.util.stream.Stream;

import static java.util.AbstractMap.SimpleEntry;
import static org.lfenergy.compas.sct.commons.util.SclConstructorHelper.newAddress;
import static org.lfenergy.compas.sct.commons.util.SclConstructorHelper.newP;

Expand All @@ -44,7 +45,9 @@ public class ControlBlockEditorService implements ControlBlockEditor {
private static final int APPID_LENGTH = 4;
private static final int VLAN_ID_LENGTH = 3;
private static final String MISSING_ATTRIBUTE_IN_GSE_SMV_CB_COM = "Error in Control Block communication setting file: vlan is missing attribute ";
private static final int HEXADECIMAL_BASE = 16;
private final ControlService controlService;
private final LdeviceService ldeviceService;

@Override
public List<SclReportItem> analyzeDataGroups(SCL scd) {
Expand Down Expand Up @@ -88,19 +91,31 @@ public void removeAllControlBlocksAndDatasetsAndExtRefSrcBindings(final SCL scl)

@Override
public List<SclReportItem> configureNetworkForAllControlBlocks(SCL scd, CBCom cbCom) {
return configureNetworkForAllControlBlocks(scd, cbCom, Collections.emptyList());
}

@Override
public List<SclReportItem> configureNetworkForAllControlBlocks(SCL scd, CBCom cbCom, List<TSubNetwork> subnetworksToReuse) {
Map<CbKey, AppIdAndMac> appIdsAndMacsToReuse = subnetworksToReuse != null && !subnetworksToReuse.isEmpty() ?
computeAppIdsAndMacToReuse(scd, subnetworksToReuse)
: Collections.emptyMap();
return Stream.concat(
configureNetworkForControlBlocks(scd, cbCom, TCBType.GOOSE),
configureNetworkForControlBlocks(scd, cbCom, TCBType.SV))
configureNetworkForControlBlocks(scd, appIdsAndMacsToReuse, cbCom, TCBType.GOOSE),
configureNetworkForControlBlocks(scd, appIdsAndMacsToReuse, cbCom, TCBType.SV))
.toList();
}

private Stream<SclReportItem> configureNetworkForControlBlocks(SCL scl, CBCom cbCom, TCBType tcbType) {
private Stream<SclReportItem> configureNetworkForControlBlocks(SCL scl, Map<CbKey, AppIdAndMac> appIdsAndMacsToReuse, CBCom cbCom, TCBType tcbType) {
CbComSettings cbComSettings;
try {
cbComSettings = parseCbCom(cbCom, tcbType);
} catch (ScdException ex) {
return Stream.of(SclReportItem.error("Control Block Communication setting files", ex.getMessage()));
}
List<Long> appIdToReuse = appIdsAndMacsToReuse.values().stream().map(AppIdAndMac::appId).toList();
List<Long> macToReuse = appIdsAndMacsToReuse.values().stream().map(AppIdAndMac::mac).toList();
PrimitiveIterator.OfLong appIdIterator = cbComSettings.appIds.filter(appId -> !appIdToReuse.contains(appId)).iterator();
PrimitiveIterator.OfLong macIterator = cbComSettings.macAddresses.filter(mac -> !macToReuse.contains(mac)).iterator();
return scl.getIED().stream()
.flatMap(tied ->
tied.getAccessPoint()
Expand All @@ -110,8 +125,8 @@ private Stream<SclReportItem> configureNetworkForControlBlocks(SCL scl, CBCom cb
.map(tlDevice -> new IedApLd(tied, tAccessPoint.getName(), tlDevice))
)
)
.flatMap(iedApLd -> Optional.ofNullable(iedApLd.lDevice().getLN0()).stream()
.flatMap(ln0 -> controlService.getControls(ln0, ControlBlockEnum.from(tcbType).getControlBlockClass()))
.filter(iedApLd -> iedApLd.lDevice().isSetLN0())
.flatMap(iedApLd -> controlService.getControls(iedApLd.lDevice().getLN0(), ControlBlockEnum.from(tcbType).getControlBlockClass())
.map(tControl -> {
CriteriaOrError criteriaOrError = getCriteria(iedApLd.ied(), tcbType, tControl.getName());
if (criteriaOrError.errorMessage != null) {
Expand All @@ -121,7 +136,8 @@ private Stream<SclReportItem> configureNetworkForControlBlocks(SCL scl, CBCom cb
if (settings == null) {
return newError(iedApLd, tControl, "Cannot configure communication for this ControlBlock because: No controlBlock communication settings found with these " + criteriaOrError.criteria);
}
return configureControlBlockNetwork(scl.getCommunication(), settings, cbComSettings.appIdIterator, cbComSettings.macAddressIterator, tControl, iedApLd);
AppIdAndMac reuseAppIdAndMac = appIdsAndMacsToReuse.get(new CbKey(iedApLd.ied.getName(), iedApLd.lDevice.getInst(), tControl.getName()));
return configureControlBlockNetwork(scl.getCommunication(), settings, appIdIterator, macIterator, tControl, iedApLd, reuseAppIdAndMac);
})
.flatMap(Optional::stream)
);
Expand All @@ -133,42 +149,48 @@ private CbComSettings parseCbCom(CBCom cbCom, TCBType tcbType) {
.filter(tRange -> tcbType.equals(tRange.getCBType()))
.findFirst()
.orElseThrow(() -> new ScdException("Control Block Communication setting files does not contain AppIdRange for cbType " + tcbType.value()));
PrimitiveIterator.OfLong appIdIterator = Utils.sequence(Long.parseLong(appIdRange.getStart(), 16), Long.parseLong(appIdRange.getEnd(), 16));
LongStream appIds = LongStream.range(Long.parseLong(appIdRange.getStart(), HEXADECIMAL_BASE), Long.parseLong(appIdRange.getEnd(), HEXADECIMAL_BASE) + 1);

TRange macRange = Optional.ofNullable(cbCom.getMacRanges()).map(MacRanges::getMacRange).stream()
.flatMap(Collection::stream)
.filter(tRange -> tcbType.equals(tRange.getCBType()))
.findFirst()
.orElseThrow(() -> new ScdException("Control Block Communication setting files does not contain MacRange for cbType " + tcbType.value()));
Iterator<String> macAddressIterator = Utils.macAddressSequence(macRange.getStart(), macRange.getEnd());
LongStream macAddresses = LongStream.range(Utils.macAddressToLong(macRange.getStart()), Utils.macAddressToLong(macRange.getEnd()) + 1);

Map<Criteria, Settings> settingsByCriteria = Optional.ofNullable(cbCom.getVlans()).map(Vlans::getVlan).stream()
.flatMap(Collection::stream)
.filter(vlan -> tcbType.equals(vlan.getCBType()))
.collect(Collectors.toMap(this::vlanToCriteria, this::vlanToSetting));

return new CbComSettings(appIdIterator, macAddressIterator, settingsByCriteria);
return new CbComSettings(appIds, macAddresses, settingsByCriteria);
}

private Optional<SclReportItem> configureControlBlockNetwork(TCommunication tCommunication, Settings settings, PrimitiveIterator.OfLong appIdIterator, Iterator<String> macAddressIterator, TControl tControl, IedApLd iedApLd) {
if (settings.vlanId() == null) {
return newError(iedApLd, tControl, "Cannot configure communication for this ControlBlock because no Vlan Id was provided in the settings");
}
if (!appIdIterator.hasNext()) {
return newError(iedApLd, tControl, "Cannot configure communication for this ControlBlock because range of appId is exhausted");
}
if (!macAddressIterator.hasNext()) {
return newError(iedApLd, tControl, "Cannot configure communication for this ControlBlock because range of MAC Address is exhausted");
}

private Optional<SclReportItem> configureControlBlockNetwork(TCommunication tCommunication, Settings settings, PrimitiveIterator.OfLong appIdIterator, PrimitiveIterator.OfLong macAddressIterator, TControl tControl, IedApLd iedApLd, AppIdAndMac reuseAppIdAndMac) {
Optional<TConnectedAP> optConApAdapter = findConnectedAp(tCommunication, iedApLd.ied.getName(), iedApLd.apName);
if (optConApAdapter.isEmpty()) {
return newError(iedApLd, tControl, "Cannot configure communication for ControlBlock because no ConnectedAP found for AccessPoint");
}
TConnectedAP tConnectedAP = optConApAdapter.get();
if (settings.vlanId() == null) {
return newError(iedApLd, tControl, "Cannot configure communication for this ControlBlock because no Vlan Id was provided in the settings");
}
AppIdAndMac appIdAndMac;
if (reuseAppIdAndMac != null) {
appIdAndMac = reuseAppIdAndMac;
} else {
if (!appIdIterator.hasNext()) {
return newError(iedApLd, tControl, "Cannot configure communication for this ControlBlock because range of appId is exhausted");
}
if (!macAddressIterator.hasNext()) {
return newError(iedApLd, tControl, "Cannot configure communication for this ControlBlock because range of MAC Address is exhausted");
}
appIdAndMac = new AppIdAndMac(appIdIterator.nextLong(), macAddressIterator.nextLong());
}

List<TP> listOfPs = new ArrayList<>();
listOfPs.add(newP(APPID_P_TYPE, Utils.toHex(appIdIterator.nextLong(), APPID_LENGTH)));
listOfPs.add(newP(MAC_ADDRESS_P_TYPE, macAddressIterator.next()));
listOfPs.add(newP(APPID_P_TYPE, Utils.toHex(appIdAndMac.appId(), APPID_LENGTH)));
listOfPs.add(newP(MAC_ADDRESS_P_TYPE, Utils.longToMacAddress(appIdAndMac.mac())));
listOfPs.add(newP(VLAN_ID_P_TYPE, Utils.toHex(settings.vlanId(), VLAN_ID_LENGTH)));
if (settings.vlanPriority() != null) {
listOfPs.add(newP(VLAN_PRIORITY_P_TYPE, String.valueOf(settings.vlanPriority())));
Expand All @@ -182,6 +204,26 @@ private Optional<SclReportItem> configureControlBlockNetwork(TCommunication tCom
return Optional.empty();
}

private Map<CbKey, AppIdAndMac> computeAppIdsAndMacToReuse(SCL scd, List<TSubNetwork> subnetworksToReuse) {
List<CbKey> allControlBlocksInScd = scd.getIED().stream()
.flatMap(tIed -> ldeviceService.getLdevices(tIed)
.filter(TLDevice::isSetLN0)
.flatMap(tlDevice -> Stream.concat(tlDevice.getLN0().getGSEControl().stream(), tlDevice.getLN0().getSampledValueControl().stream())
.map(tControlWithIEDName -> new CbKey(tIed.getName(), tlDevice.getInst(), tControlWithIEDName.getName()))
))
.toList();
return subnetworksToReuse.stream()
.flatMap(tSubNetwork -> tSubNetwork.getConnectedAP().stream())
.flatMap(tConnectedAP -> Stream.concat(tConnectedAP.getGSE().stream(), tConnectedAP.getSMV().stream())
.flatMap(tControlBlock -> AppIdAndMac.from(tControlBlock.getAddress())
.map(appIdAndMac -> new SimpleEntry<>(new CbKey(tConnectedAP.getIedName(), tControlBlock.getLdInst(), tControlBlock.getCbName()), appIdAndMac))
.stream()
)
)
.filter(entry -> allControlBlocksInScd.contains(entry.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}

private Optional<TConnectedAP> findConnectedAp(TCommunication tCommunication, String iedName, String apName) {
if (tCommunication == null || !tCommunication.isSetSubNetwork()) {
return Optional.empty();
Expand Down Expand Up @@ -388,14 +430,42 @@ public record Settings(Integer vlanId, Byte vlanPriority, TDurationInMilliSec mi
/**
* All settings of CbCom in a useful format
*/
record CbComSettings(PrimitiveIterator.OfLong appIdIterator, Iterator<String> macAddressIterator, Map<Criteria, Settings> settingsByCriteria) {
record CbComSettings(LongStream appIds, LongStream macAddresses, Map<Criteria, Settings> settingsByCriteria) {
}

record IedApLd(TIED ied, String apName, TLDevice lDevice) {
String getXPath() {
return """
/SCL/IED[@name="%s"]/AccessPoint[@name="%s"]/Server/LDevice[@inst="%s"]""".formatted(ied.getName(), apName, lDevice.getInst());
}
}

/**
* ControlBlock key. Values that uniquely identify a ControlBlock in a SCD.
*
* @param iedName name of IED containing the ControlBlock
* @param LDInst inst of LD containing the ControlBlock
* @param cbName name of the ControlBlock
*/
record CbKey(String iedName, String LDInst, String cbName) {
}

/**
* Pair of APPID and MAC-Address
*
* @param appId APPID
* @param mac MAC-Address
*/
record AppIdAndMac(long appId, long mac) {
static Optional<AppIdAndMac> from(TAddress address) {
if (address == null) {
return Optional.empty();
}
return Utils.extractFromP(APPID_P_TYPE, address.getP())
.map(appId -> Integer.parseInt(appId, HEXADECIMAL_BASE))
.flatMap(appId -> Utils.extractFromP(MAC_ADDRESS_P_TYPE, address.getP())
.map(Utils::macAddressToLong)
.map(macAddress -> new AppIdAndMac(appId, macAddress)));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import org.lfenergy.compas.scl2007b4.model.SCL;
import org.lfenergy.compas.scl2007b4.model.TExtRef;
import org.lfenergy.compas.scl2007b4.model.TSubNetwork;
import org.lfenergy.compas.sct.commons.dto.SclReportItem;
import org.lfenergy.compas.sct.commons.model.cbcom.CBCom;
import org.lfenergy.compas.sct.commons.model.da_comm.DACOMM;
Expand Down Expand Up @@ -65,4 +66,22 @@ public interface ControlBlockEditor {
*/
List<SclReportItem> configureNetworkForAllControlBlocks(SCL scd, CBCom cbCom);

/**
* Configure the network for all the ControlBlocks, reusing APPID and MAC-Address from given Communication section when they already exists.
* 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
* For a ControlBlock (IED.name, LD.inst, GSEControl/SampledValueControl.name), if there is a matching network configuration in communicationToReuse (ConnectedAP.iedName, GSE/SMV.ldInst, GSE/SMV.cbName),
* then reuse APPID and MAC-Address from communicationToReuse,
* else use range provided in cbCom.
* APPID and MAC-Address of communicationToReuse for ControlBlocks that exists in scd are excluded from the ranges given in cbCom.
*
* @param scd input SCD object. The object will be modified with the new GSE and SMV elements
* @param cbCom communication settings to configure Control Block Communication
* @param subnetworksToReuse subnetworks to search for existing APPID and MAC-Address for ControlBlock
* @return list of encountered errors
* @see Utils#macAddressToLong(String) for the expected MAC address format
*/
List<SclReportItem> configureNetworkForAllControlBlocks(SCL scd, CBCom cbCom, List<TSubNetwork> subnetworksToReuse);

}
Loading

0 comments on commit befecc5

Please sign in to comment.