Skip to content

Commit 00aed84

Browse files
authored
Merge pull request #394 from com-pas/feat/390_RSR-993_reuse_old_appid_and_mac_addresses
Feat/390 RSR-993 reuse old appid and mac addresses
2 parents 3c1533e + befecc5 commit 00aed84

File tree

7 files changed

+321
-161
lines changed

7 files changed

+321
-161
lines changed

pom.xml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,8 @@
5050
<jaxb2-maven-plugin.version>3.1.0</jaxb2-maven-plugin.version>
5151

5252
<junit-version>5.9.0</junit-version>
53-
<logback-classic.version>1.4.5</logback-classic.version>
53+
<logback-classic.version>1.5.6</logback-classic.version>
5454
<assertj.version>3.22.0</assertj.version>
55-
<!-- with version 18.30, lombok is compatible with JDK 21 -->
5655
<lombok.version>1.18.30</lombok.version>
5756
<mockito.version>5.5.0</mockito.version>
5857
<jackson-databind.version>2.13.4.1</jackson-databind.version>

sct-app/src/test/java/org.lfenergy.compas.sct.app/SclAutomationServiceIntegrationTest.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.lfenergy.compas.scl2007b4.model.LN0;
1010
import org.lfenergy.compas.scl2007b4.model.SCL;
1111
import org.lfenergy.compas.sct.commons.ControlBlockEditorService;
12+
import org.lfenergy.compas.sct.commons.LdeviceService;
1213
import org.lfenergy.compas.sct.commons.SclService;
1314
import org.lfenergy.compas.sct.commons.SubstationService;
1415
import org.lfenergy.compas.sct.commons.api.ControlBlockEditor;
@@ -34,7 +35,7 @@ class SclAutomationServiceIntegrationTest {
3435
private SclAutomationService sclAutomationService ;
3536
private static final SclEditor sclEditor = new SclService() ;
3637
private static final SubstationEditor substationEditor = new SubstationService() ;
37-
private static final ControlBlockEditor controlBlockEditor = new ControlBlockEditorService(new ControlService()) ;
38+
private static final ControlBlockEditor controlBlockEditor = new ControlBlockEditorService(new ControlService(), new LdeviceService()) ;
3839

3940
private HeaderDTO headerDTO;
4041

@@ -106,7 +107,7 @@ void createSCD_WithManyHItem_should_return_generatedSCD() {
106107
// Then
107108
assertThat(scd.getHeader().getId()).isNotNull();
108109
assertThat(scd.getHeader().getHistory().getHitem()).hasSize(1);
109-
assertThat(scd.getHeader().getHistory().getHitem().get(0).getWhat()).isEqualTo("what");
110+
assertThat(scd.getHeader().getHistory().getHitem().getFirst().getWhat()).isEqualTo("what");
110111
assertIsMarshallable(scd);
111112
}
112113

@@ -165,7 +166,7 @@ void createSCD_should_delete_ControlBlocks_DataSet_and_ExtRef_src_attributes() {
165166

166167
assertThat(ln0.getDataSet()).isEmpty();
167168
assertThat(ln0.getInputs().getExtRef()).hasSize(2);
168-
assertThat(ln0.getInputs().getExtRef().get(0).isSetSrcLDInst()).isFalse();
169+
assertThat(ln0.getInputs().getExtRef().getFirst().isSetSrcLDInst()).isFalse();
169170
assertIsMarshallable(scd);
170171
}
171172

sct-commons/src/main/java/org/lfenergy/compas/sct/commons/ControlBlockEditorService.java

Lines changed: 94 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import org.lfenergy.compas.sct.commons.exception.ScdException;
1313
import org.lfenergy.compas.sct.commons.model.cbcom.*;
1414
import org.lfenergy.compas.sct.commons.model.da_comm.DACOMM;
15-
import org.lfenergy.compas.sct.commons.model.da_comm.FCDAs;
1615
import org.lfenergy.compas.sct.commons.scl.ControlService;
1716
import org.lfenergy.compas.sct.commons.scl.SclRootAdapter;
1817
import org.lfenergy.compas.sct.commons.scl.ied.IEDAdapter;
@@ -26,8 +25,10 @@
2625
import java.math.BigInteger;
2726
import java.util.*;
2827
import java.util.stream.Collectors;
28+
import java.util.stream.LongStream;
2929
import java.util.stream.Stream;
3030

31+
import static java.util.AbstractMap.SimpleEntry;
3132
import static org.lfenergy.compas.sct.commons.util.SclConstructorHelper.newAddress;
3233
import static org.lfenergy.compas.sct.commons.util.SclConstructorHelper.newP;
3334

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

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

8992
@Override
9093
public List<SclReportItem> configureNetworkForAllControlBlocks(SCL scd, CBCom cbCom) {
94+
return configureNetworkForAllControlBlocks(scd, cbCom, Collections.emptyList());
95+
}
96+
97+
@Override
98+
public List<SclReportItem> configureNetworkForAllControlBlocks(SCL scd, CBCom cbCom, List<TSubNetwork> subnetworksToReuse) {
99+
Map<CbKey, AppIdAndMac> appIdsAndMacsToReuse = subnetworksToReuse != null && !subnetworksToReuse.isEmpty() ?
100+
computeAppIdsAndMacToReuse(scd, subnetworksToReuse)
101+
: Collections.emptyMap();
91102
return Stream.concat(
92-
configureNetworkForControlBlocks(scd, cbCom, TCBType.GOOSE),
93-
configureNetworkForControlBlocks(scd, cbCom, TCBType.SV))
103+
configureNetworkForControlBlocks(scd, appIdsAndMacsToReuse, cbCom, TCBType.GOOSE),
104+
configureNetworkForControlBlocks(scd, appIdsAndMacsToReuse, cbCom, TCBType.SV))
94105
.toList();
95106
}
96107

97-
private Stream<SclReportItem> configureNetworkForControlBlocks(SCL scl, CBCom cbCom, TCBType tcbType) {
108+
private Stream<SclReportItem> configureNetworkForControlBlocks(SCL scl, Map<CbKey, AppIdAndMac> appIdsAndMacsToReuse, CBCom cbCom, TCBType tcbType) {
98109
CbComSettings cbComSettings;
99110
try {
100111
cbComSettings = parseCbCom(cbCom, tcbType);
101112
} catch (ScdException ex) {
102113
return Stream.of(SclReportItem.error("Control Block Communication setting files", ex.getMessage()));
103114
}
115+
List<Long> appIdToReuse = appIdsAndMacsToReuse.values().stream().map(AppIdAndMac::appId).toList();
116+
List<Long> macToReuse = appIdsAndMacsToReuse.values().stream().map(AppIdAndMac::mac).toList();
117+
PrimitiveIterator.OfLong appIdIterator = cbComSettings.appIds.filter(appId -> !appIdToReuse.contains(appId)).iterator();
118+
PrimitiveIterator.OfLong macIterator = cbComSettings.macAddresses.filter(mac -> !macToReuse.contains(mac)).iterator();
104119
return scl.getIED().stream()
105120
.flatMap(tied ->
106121
tied.getAccessPoint()
@@ -110,8 +125,8 @@ private Stream<SclReportItem> configureNetworkForControlBlocks(SCL scl, CBCom cb
110125
.map(tlDevice -> new IedApLd(tied, tAccessPoint.getName(), tlDevice))
111126
)
112127
)
113-
.flatMap(iedApLd -> Optional.ofNullable(iedApLd.lDevice().getLN0()).stream()
114-
.flatMap(ln0 -> controlService.getControls(ln0, ControlBlockEnum.from(tcbType).getControlBlockClass()))
128+
.filter(iedApLd -> iedApLd.lDevice().isSetLN0())
129+
.flatMap(iedApLd -> controlService.getControls(iedApLd.lDevice().getLN0(), ControlBlockEnum.from(tcbType).getControlBlockClass())
115130
.map(tControl -> {
116131
CriteriaOrError criteriaOrError = getCriteria(iedApLd.ied(), tcbType, tControl.getName());
117132
if (criteriaOrError.errorMessage != null) {
@@ -121,7 +136,8 @@ private Stream<SclReportItem> configureNetworkForControlBlocks(SCL scl, CBCom cb
121136
if (settings == null) {
122137
return newError(iedApLd, tControl, "Cannot configure communication for this ControlBlock because: No controlBlock communication settings found with these " + criteriaOrError.criteria);
123138
}
124-
return configureControlBlockNetwork(scl.getCommunication(), settings, cbComSettings.appIdIterator, cbComSettings.macAddressIterator, tControl, iedApLd);
139+
AppIdAndMac reuseAppIdAndMac = appIdsAndMacsToReuse.get(new CbKey(iedApLd.ied.getName(), iedApLd.lDevice.getInst(), tControl.getName()));
140+
return configureControlBlockNetwork(scl.getCommunication(), settings, appIdIterator, macIterator, tControl, iedApLd, reuseAppIdAndMac);
125141
})
126142
.flatMap(Optional::stream)
127143
);
@@ -133,42 +149,48 @@ private CbComSettings parseCbCom(CBCom cbCom, TCBType tcbType) {
133149
.filter(tRange -> tcbType.equals(tRange.getCBType()))
134150
.findFirst()
135151
.orElseThrow(() -> new ScdException("Control Block Communication setting files does not contain AppIdRange for cbType " + tcbType.value()));
136-
PrimitiveIterator.OfLong appIdIterator = Utils.sequence(Long.parseLong(appIdRange.getStart(), 16), Long.parseLong(appIdRange.getEnd(), 16));
152+
LongStream appIds = LongStream.range(Long.parseLong(appIdRange.getStart(), HEXADECIMAL_BASE), Long.parseLong(appIdRange.getEnd(), HEXADECIMAL_BASE) + 1);
137153

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

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

150-
return new CbComSettings(appIdIterator, macAddressIterator, settingsByCriteria);
166+
return new CbComSettings(appIds, macAddresses, settingsByCriteria);
151167
}
152168

153-
private Optional<SclReportItem> configureControlBlockNetwork(TCommunication tCommunication, Settings settings, PrimitiveIterator.OfLong appIdIterator, Iterator<String> macAddressIterator, TControl tControl, IedApLd iedApLd) {
154-
if (settings.vlanId() == null) {
155-
return newError(iedApLd, tControl, "Cannot configure communication for this ControlBlock because no Vlan Id was provided in the settings");
156-
}
157-
if (!appIdIterator.hasNext()) {
158-
return newError(iedApLd, tControl, "Cannot configure communication for this ControlBlock because range of appId is exhausted");
159-
}
160-
if (!macAddressIterator.hasNext()) {
161-
return newError(iedApLd, tControl, "Cannot configure communication for this ControlBlock because range of MAC Address is exhausted");
162-
}
163-
169+
private Optional<SclReportItem> configureControlBlockNetwork(TCommunication tCommunication, Settings settings, PrimitiveIterator.OfLong appIdIterator, PrimitiveIterator.OfLong macAddressIterator, TControl tControl, IedApLd iedApLd, AppIdAndMac reuseAppIdAndMac) {
164170
Optional<TConnectedAP> optConApAdapter = findConnectedAp(tCommunication, iedApLd.ied.getName(), iedApLd.apName);
165171
if (optConApAdapter.isEmpty()) {
166172
return newError(iedApLd, tControl, "Cannot configure communication for ControlBlock because no ConnectedAP found for AccessPoint");
167173
}
168174
TConnectedAP tConnectedAP = optConApAdapter.get();
175+
if (settings.vlanId() == null) {
176+
return newError(iedApLd, tControl, "Cannot configure communication for this ControlBlock because no Vlan Id was provided in the settings");
177+
}
178+
AppIdAndMac appIdAndMac;
179+
if (reuseAppIdAndMac != null) {
180+
appIdAndMac = reuseAppIdAndMac;
181+
} else {
182+
if (!appIdIterator.hasNext()) {
183+
return newError(iedApLd, tControl, "Cannot configure communication for this ControlBlock because range of appId is exhausted");
184+
}
185+
if (!macAddressIterator.hasNext()) {
186+
return newError(iedApLd, tControl, "Cannot configure communication for this ControlBlock because range of MAC Address is exhausted");
187+
}
188+
appIdAndMac = new AppIdAndMac(appIdIterator.nextLong(), macAddressIterator.nextLong());
189+
}
190+
169191
List<TP> listOfPs = new ArrayList<>();
170-
listOfPs.add(newP(APPID_P_TYPE, Utils.toHex(appIdIterator.nextLong(), APPID_LENGTH)));
171-
listOfPs.add(newP(MAC_ADDRESS_P_TYPE, macAddressIterator.next()));
192+
listOfPs.add(newP(APPID_P_TYPE, Utils.toHex(appIdAndMac.appId(), APPID_LENGTH)));
193+
listOfPs.add(newP(MAC_ADDRESS_P_TYPE, Utils.longToMacAddress(appIdAndMac.mac())));
172194
listOfPs.add(newP(VLAN_ID_P_TYPE, Utils.toHex(settings.vlanId(), VLAN_ID_LENGTH)));
173195
if (settings.vlanPriority() != null) {
174196
listOfPs.add(newP(VLAN_PRIORITY_P_TYPE, String.valueOf(settings.vlanPriority())));
@@ -182,6 +204,26 @@ private Optional<SclReportItem> configureControlBlockNetwork(TCommunication tCom
182204
return Optional.empty();
183205
}
184206

207+
private Map<CbKey, AppIdAndMac> computeAppIdsAndMacToReuse(SCL scd, List<TSubNetwork> subnetworksToReuse) {
208+
List<CbKey> allControlBlocksInScd = scd.getIED().stream()
209+
.flatMap(tIed -> ldeviceService.getLdevices(tIed)
210+
.filter(TLDevice::isSetLN0)
211+
.flatMap(tlDevice -> Stream.concat(tlDevice.getLN0().getGSEControl().stream(), tlDevice.getLN0().getSampledValueControl().stream())
212+
.map(tControlWithIEDName -> new CbKey(tIed.getName(), tlDevice.getInst(), tControlWithIEDName.getName()))
213+
))
214+
.toList();
215+
return subnetworksToReuse.stream()
216+
.flatMap(tSubNetwork -> tSubNetwork.getConnectedAP().stream())
217+
.flatMap(tConnectedAP -> Stream.concat(tConnectedAP.getGSE().stream(), tConnectedAP.getSMV().stream())
218+
.flatMap(tControlBlock -> AppIdAndMac.from(tControlBlock.getAddress())
219+
.map(appIdAndMac -> new SimpleEntry<>(new CbKey(tConnectedAP.getIedName(), tControlBlock.getLdInst(), tControlBlock.getCbName()), appIdAndMac))
220+
.stream()
221+
)
222+
)
223+
.filter(entry -> allControlBlocksInScd.contains(entry.getKey()))
224+
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
225+
}
226+
185227
private Optional<TConnectedAP> findConnectedAp(TCommunication tCommunication, String iedName, String apName) {
186228
if (tCommunication == null || !tCommunication.isSetSubNetwork()) {
187229
return Optional.empty();
@@ -388,14 +430,42 @@ public record Settings(Integer vlanId, Byte vlanPriority, TDurationInMilliSec mi
388430
/**
389431
* All settings of CbCom in a useful format
390432
*/
391-
record CbComSettings(PrimitiveIterator.OfLong appIdIterator, Iterator<String> macAddressIterator, Map<Criteria, Settings> settingsByCriteria) {
433+
record CbComSettings(LongStream appIds, LongStream macAddresses, Map<Criteria, Settings> settingsByCriteria) {
392434
}
393435

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

443+
/**
444+
* ControlBlock key. Values that uniquely identify a ControlBlock in a SCD.
445+
*
446+
* @param iedName name of IED containing the ControlBlock
447+
* @param LDInst inst of LD containing the ControlBlock
448+
* @param cbName name of the ControlBlock
449+
*/
450+
record CbKey(String iedName, String LDInst, String cbName) {
451+
}
452+
453+
/**
454+
* Pair of APPID and MAC-Address
455+
*
456+
* @param appId APPID
457+
* @param mac MAC-Address
458+
*/
459+
record AppIdAndMac(long appId, long mac) {
460+
static Optional<AppIdAndMac> from(TAddress address) {
461+
if (address == null) {
462+
return Optional.empty();
463+
}
464+
return Utils.extractFromP(APPID_P_TYPE, address.getP())
465+
.map(appId -> Integer.parseInt(appId, HEXADECIMAL_BASE))
466+
.flatMap(appId -> Utils.extractFromP(MAC_ADDRESS_P_TYPE, address.getP())
467+
.map(Utils::macAddressToLong)
468+
.map(macAddress -> new AppIdAndMac(appId, macAddress)));
469+
}
400470
}
401471
}

sct-commons/src/main/java/org/lfenergy/compas/sct/commons/api/ControlBlockEditor.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import org.lfenergy.compas.scl2007b4.model.SCL;
88
import org.lfenergy.compas.scl2007b4.model.TExtRef;
9+
import org.lfenergy.compas.scl2007b4.model.TSubNetwork;
910
import org.lfenergy.compas.sct.commons.dto.SclReportItem;
1011
import org.lfenergy.compas.sct.commons.model.cbcom.CBCom;
1112
import org.lfenergy.compas.sct.commons.model.da_comm.DACOMM;
@@ -65,4 +66,22 @@ public interface ControlBlockEditor {
6566
*/
6667
List<SclReportItem> configureNetworkForAllControlBlocks(SCL scd, CBCom cbCom);
6768

69+
/**
70+
* Configure the network for all the ControlBlocks, reusing APPID and MAC-Address from given Communication section when they already exists.
71+
* Create (or update if already existing) these elements
72+
* - the Communication/SubNetwork/ConnectedAP/GSE element, for the GSEControl blocks
73+
* - the Communication/SubNetwork/ConnectedAP/SMV element, for the SampledValueControl blocks
74+
* 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),
75+
* then reuse APPID and MAC-Address from communicationToReuse,
76+
* else use range provided in cbCom.
77+
* APPID and MAC-Address of communicationToReuse for ControlBlocks that exists in scd are excluded from the ranges given in cbCom.
78+
*
79+
* @param scd input SCD object. The object will be modified with the new GSE and SMV elements
80+
* @param cbCom communication settings to configure Control Block Communication
81+
* @param subnetworksToReuse subnetworks to search for existing APPID and MAC-Address for ControlBlock
82+
* @return list of encountered errors
83+
* @see Utils#macAddressToLong(String) for the expected MAC address format
84+
*/
85+
List<SclReportItem> configureNetworkForAllControlBlocks(SCL scd, CBCom cbCom, List<TSubNetwork> subnetworksToReuse);
86+
6887
}

0 commit comments

Comments
 (0)