diff --git a/java/pypowsybl/pom.xml b/java/pypowsybl/pom.xml
index 1287f88cff..0c4ae19dc9 100644
--- a/java/pypowsybl/pom.xml
+++ b/java/pypowsybl/pom.xml
@@ -292,6 +292,18 @@
com.powsybl
powsybl-dynawo-simulation
+
+ com.powsybl
+ powsybl-dynawo-extensions-api
+
+
+ com.powsybl
+ powsybl-dynawo-extensions-impl
+
+
+ com.powsybl
+ powsybl-dynawo-extensions-serde
+
com.powsybl
powsybl-entsoe-commons
diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/GeneratorConnectionLevelDataframeAdder.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/GeneratorConnectionLevelDataframeAdder.java
new file mode 100644
index 0000000000..eb0dbf62cc
--- /dev/null
+++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/GeneratorConnectionLevelDataframeAdder.java
@@ -0,0 +1,69 @@
+/**
+ * Copyright (c) 2022, RTE (http://www.rte-france.com)
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+package com.powsybl.dataframe.dynamic.extensions;
+
+import com.powsybl.commons.PowsyblException;
+import com.powsybl.dataframe.SeriesMetadata;
+import com.powsybl.dataframe.network.adders.AbstractSimpleAdder;
+import com.powsybl.dataframe.network.adders.SeriesUtils;
+import com.powsybl.dataframe.update.StringSeries;
+import com.powsybl.dataframe.update.UpdatingDataframe;
+import com.powsybl.dynawo.extensions.api.generator.connection.GeneratorConnectionLevel;
+import com.powsybl.dynawo.extensions.api.generator.connection.GeneratorConnectionLevelAdder;
+import com.powsybl.iidm.network.Generator;
+import com.powsybl.iidm.network.Network;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Gautier Bureau {@literal }
+ * @author Laurent Issertial {@literal }
+ */
+public class GeneratorConnectionLevelDataframeAdder extends AbstractSimpleAdder {
+
+ private static final List METADATA = List.of(
+ SeriesMetadata.stringIndex("id"),
+ SeriesMetadata.strings("level")
+ );
+
+ @Override
+ public List> getMetadata() {
+ return Collections.singletonList(METADATA);
+ }
+
+ private static class GeneratorConnectionLevelSeries {
+
+ private final StringSeries id;
+ private final StringSeries level;
+
+ GeneratorConnectionLevelSeries(UpdatingDataframe dataframe) {
+ this.id = dataframe.getStrings("id");
+ this.level = dataframe.getStrings("level");
+ }
+
+ void create(Network network, int row) {
+ String generatorId = this.id.get(row);
+ Generator g = network.getGenerator(generatorId);
+ if (g == null) {
+ throw new PowsyblException("Invalid generator id : could not find " + generatorId);
+ }
+ var adder = g.newExtension(GeneratorConnectionLevelAdder.class);
+ SeriesUtils.applyIfPresent(level, row, l -> adder.withLevel(GeneratorConnectionLevel.GeneratorConnectionLevelType.valueOf(l)));
+ adder.add();
+ }
+ }
+
+ @Override
+ public void addElements(Network network, UpdatingDataframe dataframe) {
+ GeneratorConnectionLevelSeries series = new GeneratorConnectionLevelSeries(dataframe);
+ for (int row = 0; row < dataframe.getRowCount(); row++) {
+ series.create(network, row);
+ }
+ }
+}
diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/GeneratorConnectionLevelDataframeProvider.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/GeneratorConnectionLevelDataframeProvider.java
new file mode 100644
index 0000000000..2eed4e3a63
--- /dev/null
+++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/GeneratorConnectionLevelDataframeProvider.java
@@ -0,0 +1,85 @@
+/**
+ * Copyright (c) 2021-2022, RTE (http://www.rte-france.com)
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+package com.powsybl.dataframe.dynamic.extensions;
+
+import com.google.auto.service.AutoService;
+import com.powsybl.commons.PowsyblException;
+import com.powsybl.dataframe.network.ExtensionInformation;
+import com.powsybl.dataframe.network.NetworkDataframeMapper;
+import com.powsybl.dataframe.network.NetworkDataframeMapperBuilder;
+import com.powsybl.dataframe.network.adders.NetworkElementAdder;
+import com.powsybl.dataframe.network.extensions.AbstractSingleDataframeNetworkExtension;
+import com.powsybl.dataframe.network.extensions.NetworkExtensionDataframeProvider;
+import com.powsybl.dynawo.extensions.api.generator.connection.GeneratorConnectionLevel;
+import com.powsybl.iidm.network.Generator;
+import com.powsybl.iidm.network.Network;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Stream;
+
+/**
+ * @author Gautier Bureau {@literal }
+ * @author Laurent Issertial {@literal }
+ */
+@AutoService(NetworkExtensionDataframeProvider.class)
+public class GeneratorConnectionLevelDataframeProvider extends AbstractSingleDataframeNetworkExtension {
+
+ @Override
+ public String getExtensionName() {
+ return GeneratorConnectionLevel.NAME;
+ }
+
+ @Override
+ public ExtensionInformation getExtensionInformation() {
+ return new ExtensionInformation(GeneratorConnectionLevel.NAME,
+ "Provides information, for dynamic simulation only, about the characteristics of a Synchronized generator",
+ "index : id (str), " +
+ "level (str)");
+ }
+
+ private Stream itemsStream(Network network) {
+ return network.getGeneratorStream()
+ .map(g -> (GeneratorConnectionLevel) g.getExtension(GeneratorConnectionLevel.class))
+ .filter(Objects::nonNull);
+ }
+
+ private GeneratorConnectionLevel getOrThrow(Network network, String id) {
+ Generator gen = network.getGenerator(id);
+ if (gen == null) {
+ throw new PowsyblException("Generator '" + id + "' not found");
+ }
+ GeneratorConnectionLevel sgp = gen.getExtension(GeneratorConnectionLevel.class);
+ if (sgp == null) {
+ throw new PowsyblException("Generator '" + id + "' has no GeneratorConnectionLevel extension");
+ }
+ return sgp;
+ }
+
+ @Override
+ public NetworkDataframeMapper createMapper() {
+ return NetworkDataframeMapperBuilder.ofStream(this::itemsStream, this::getOrThrow)
+ .stringsIndex("id", ext -> ext.getExtendable().getId())
+ .enums("level", GeneratorConnectionLevel.GeneratorConnectionLevelType.class, GeneratorConnectionLevel::getLevel, GeneratorConnectionLevel::setLevel)
+ .build();
+ }
+
+ @Override
+ public void removeExtensions(Network network, List ids) {
+ ids.stream().filter(Objects::nonNull)
+ .map(network::getGenerator)
+ .filter(Objects::nonNull)
+ .forEach(g -> g.removeExtension(GeneratorConnectionLevel.class));
+ }
+
+ @Override
+ public NetworkElementAdder createAdder() {
+ return new GeneratorConnectionLevelDataframeAdder();
+ }
+
+}
diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronizedGeneratorPropertiesDataframeAdder.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronizedGeneratorPropertiesDataframeAdder.java
new file mode 100644
index 0000000000..322c759b5f
--- /dev/null
+++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronizedGeneratorPropertiesDataframeAdder.java
@@ -0,0 +1,73 @@
+/**
+ * Copyright (c) 2022, RTE (http://www.rte-france.com)
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+package com.powsybl.dataframe.dynamic.extensions;
+
+import com.powsybl.commons.PowsyblException;
+import com.powsybl.dataframe.SeriesMetadata;
+import com.powsybl.dataframe.network.adders.AbstractSimpleAdder;
+import com.powsybl.dataframe.network.adders.SeriesUtils;
+import com.powsybl.dataframe.update.IntSeries;
+import com.powsybl.dataframe.update.StringSeries;
+import com.powsybl.dataframe.update.UpdatingDataframe;
+import com.powsybl.dynawo.extensions.api.generator.SynchronizedGeneratorPropertiesAdder;
+import com.powsybl.iidm.network.Generator;
+import com.powsybl.iidm.network.Network;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Gautier Bureau {@literal }
+ * @author Laurent Issertial {@literal }
+ */
+public class SynchronizedGeneratorPropertiesDataframeAdder extends AbstractSimpleAdder {
+
+ private static final List METADATA = List.of(
+ SeriesMetadata.stringIndex("id"),
+ SeriesMetadata.strings("type"),
+ SeriesMetadata.booleans("rpcl2")
+ );
+
+ @Override
+ public List> getMetadata() {
+ return Collections.singletonList(METADATA);
+ }
+
+ private static class SynchronizedGeneratorPropertiesSeries {
+
+ private final StringSeries id;
+ private final StringSeries type;
+ private final IntSeries rpcl2;
+
+ SynchronizedGeneratorPropertiesSeries(UpdatingDataframe dataframe) {
+ this.id = dataframe.getStrings("id");
+ this.type = dataframe.getStrings("type");
+ this.rpcl2 = dataframe.getInts("rpcl2");
+ }
+
+ void create(Network network, int row) {
+ String generatorId = this.id.get(row);
+ Generator g = network.getGenerator(generatorId);
+ if (g == null) {
+ throw new PowsyblException("Invalid generator id : could not find " + generatorId);
+ }
+ var adder = g.newExtension(SynchronizedGeneratorPropertiesAdder.class);
+ SeriesUtils.applyIfPresent(type, row, adder::withType);
+ SeriesUtils.applyBooleanIfPresent(rpcl2, row, adder::withRpcl2);
+ adder.add();
+ }
+ }
+
+ @Override
+ public void addElements(Network network, UpdatingDataframe dataframe) {
+ SynchronizedGeneratorPropertiesSeries series = new SynchronizedGeneratorPropertiesSeries(dataframe);
+ for (int row = 0; row < dataframe.getRowCount(); row++) {
+ series.create(network, row);
+ }
+ }
+}
diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronizedGeneratorPropertiesDataframeProvider.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronizedGeneratorPropertiesDataframeProvider.java
new file mode 100644
index 0000000000..9f119b229c
--- /dev/null
+++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronizedGeneratorPropertiesDataframeProvider.java
@@ -0,0 +1,88 @@
+/**
+ * Copyright (c) 2021-2022, RTE (http://www.rte-france.com)
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+package com.powsybl.dataframe.dynamic.extensions;
+
+import com.google.auto.service.AutoService;
+import com.powsybl.commons.PowsyblException;
+import com.powsybl.dataframe.network.ExtensionInformation;
+import com.powsybl.dataframe.network.NetworkDataframeMapper;
+import com.powsybl.dataframe.network.NetworkDataframeMapperBuilder;
+import com.powsybl.dataframe.network.adders.NetworkElementAdder;
+import com.powsybl.dataframe.network.extensions.AbstractSingleDataframeNetworkExtension;
+import com.powsybl.dataframe.network.extensions.NetworkExtensionDataframeProvider;
+import com.powsybl.dynawo.extensions.api.generator.RpclType;
+import com.powsybl.dynawo.extensions.api.generator.SynchronizedGeneratorProperties;
+import com.powsybl.iidm.network.Generator;
+import com.powsybl.iidm.network.Network;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Stream;
+
+/**
+ * @author Gautier Bureau {@literal }
+ * @author Laurent Issertial {@literal }
+ */
+@AutoService(NetworkExtensionDataframeProvider.class)
+public class SynchronizedGeneratorPropertiesDataframeProvider extends AbstractSingleDataframeNetworkExtension {
+
+ @Override
+ public String getExtensionName() {
+ return SynchronizedGeneratorProperties.NAME;
+ }
+
+ @Override
+ public ExtensionInformation getExtensionInformation() {
+ return new ExtensionInformation(SynchronizedGeneratorProperties.NAME,
+ "Provides information, for dynamic simulation only, about the characteristics of a Synchronized generator",
+ "index : id (str), " +
+ "type (str), " +
+ "rpcl2 (bool)");
+ }
+
+ private Stream itemsStream(Network network) {
+ return network.getGeneratorStream()
+ .map(g -> (SynchronizedGeneratorProperties) g.getExtension(SynchronizedGeneratorProperties.class))
+ .filter(Objects::nonNull);
+ }
+
+ private SynchronizedGeneratorProperties getOrThrow(Network network, String id) {
+ Generator gen = network.getGenerator(id);
+ if (gen == null) {
+ throw new PowsyblException("Generator '" + id + "' not found");
+ }
+ SynchronizedGeneratorProperties sgp = gen.getExtension(SynchronizedGeneratorProperties.class);
+ if (sgp == null) {
+ throw new PowsyblException("Generator '" + id + "' has no SynchronizedGeneratorProperties extension");
+ }
+ return sgp;
+ }
+
+ @Override
+ public NetworkDataframeMapper createMapper() {
+ return NetworkDataframeMapperBuilder.ofStream(this::itemsStream, this::getOrThrow)
+ .stringsIndex("id", ext -> ext.getExtendable().getId())
+ .strings("type", sgp -> String.valueOf(sgp.getType()), SynchronizedGeneratorProperties::setType)
+ .booleans("rpcl2", SynchronizedGeneratorProperties::isRpcl2, (sgp, b) -> sgp.setRpcl(b ? RpclType.RPCL2 : RpclType.NONE))
+ .build();
+ }
+
+ @Override
+ public void removeExtensions(Network network, List ids) {
+ ids.stream().filter(Objects::nonNull)
+ .map(network::getGenerator)
+ .filter(Objects::nonNull)
+ .forEach(g -> g.removeExtension(SynchronizedGeneratorProperties.class));
+ }
+
+ @Override
+ public NetworkElementAdder createAdder() {
+ return new SynchronizedGeneratorPropertiesDataframeAdder();
+ }
+
+}
diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronousGeneratorPropertiesDataframeAdder.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronousGeneratorPropertiesDataframeAdder.java
new file mode 100644
index 0000000000..2dcb800ad9
--- /dev/null
+++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronousGeneratorPropertiesDataframeAdder.java
@@ -0,0 +1,107 @@
+/**
+ * Copyright (c) 2022, RTE (http://www.rte-france.com)
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+package com.powsybl.dataframe.dynamic.extensions;
+
+import com.powsybl.commons.PowsyblException;
+import com.powsybl.dataframe.SeriesMetadata;
+import com.powsybl.dataframe.network.adders.AbstractSimpleAdder;
+import com.powsybl.dataframe.network.adders.SeriesUtils;
+import com.powsybl.dataframe.update.IntSeries;
+import com.powsybl.dataframe.update.StringSeries;
+import com.powsybl.dataframe.update.UpdatingDataframe;
+import com.powsybl.dynawo.extensions.api.generator.RpclType;
+import com.powsybl.dynawo.extensions.api.generator.SynchronousGeneratorProperties;
+import com.powsybl.dynawo.extensions.api.generator.SynchronousGeneratorPropertiesAdder;
+import com.powsybl.iidm.network.Generator;
+import com.powsybl.iidm.network.Network;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Gautier Bureau {@literal }
+ * @author Laurent Issertial {@literal }
+ */
+public class SynchronousGeneratorPropertiesDataframeAdder extends AbstractSimpleAdder {
+
+ private static final List METADATA = List.of(
+ SeriesMetadata.stringIndex("id"),
+ SeriesMetadata.strings("numberOfWindings"),
+ SeriesMetadata.strings("governor"),
+ SeriesMetadata.strings("voltageRegulator"),
+ SeriesMetadata.strings("pss"),
+ SeriesMetadata.booleans("auxiliaries"),
+ SeriesMetadata.booleans("internalTransformer"),
+ SeriesMetadata.strings("rpcl"),
+ SeriesMetadata.strings("uva"),
+ SeriesMetadata.booleans("aggregated"),
+ SeriesMetadata.booleans("qlim")
+ );
+
+ @Override
+ public List> getMetadata() {
+ return Collections.singletonList(METADATA);
+ }
+
+ private static class SynchronousGeneratorPropertiesSeries {
+
+ private final StringSeries id;
+ private final StringSeries numberOfWindings;
+ private final StringSeries governor;
+ private final StringSeries voltageRegulator;
+ private final StringSeries pss;
+ private final IntSeries auxiliaries;
+ private final IntSeries internalTransformer;
+ private final StringSeries rpcl;
+ private final StringSeries uva;
+ private final IntSeries aggregated;
+ private final IntSeries qlim;
+
+ SynchronousGeneratorPropertiesSeries(UpdatingDataframe dataframe) {
+ this.id = dataframe.getStrings("id");
+ this.numberOfWindings = dataframe.getStrings("numberOfWindings");
+ this.governor = dataframe.getStrings("governor");
+ this.voltageRegulator = dataframe.getStrings("voltageRegulator");
+ this.pss = dataframe.getStrings("pss");
+ this.auxiliaries = dataframe.getInts("auxiliaries");
+ this.internalTransformer = dataframe.getInts("internalTransformer");
+ this.rpcl = dataframe.getStrings("rpcl");
+ this.uva = dataframe.getStrings("uva");
+ this.aggregated = dataframe.getInts("aggregated");
+ this.qlim = dataframe.getInts("qlim");
+ }
+
+ void create(Network network, int row) {
+ String generatorId = this.id.get(row);
+ Generator g = network.getGenerator(generatorId);
+ if (g == null) {
+ throw new PowsyblException("Invalid generator id : could not find " + generatorId);
+ }
+ var adder = g.newExtension(SynchronousGeneratorPropertiesAdder.class);
+ SeriesUtils.applyIfPresent(numberOfWindings, row, w -> adder.withNumberOfWindings(SynchronousGeneratorProperties.Windings.valueOf(w)));
+ SeriesUtils.applyIfPresent(governor, row, adder::withGovernor);
+ SeriesUtils.applyIfPresent(voltageRegulator, row, adder::withVoltageRegulator);
+ SeriesUtils.applyIfPresent(pss, row, adder::withPss);
+ SeriesUtils.applyBooleanIfPresent(auxiliaries, row, adder::withAuxiliaries);
+ SeriesUtils.applyBooleanIfPresent(internalTransformer, row, adder::withInternalTransformer);
+ SeriesUtils.applyIfPresent(rpcl, row, r -> adder.withRpcl(RpclType.valueOf(r)));
+ SeriesUtils.applyIfPresent(uva, row, u -> adder.withUva(SynchronousGeneratorProperties.Uva.valueOf(u)));
+ SeriesUtils.applyBooleanIfPresent(aggregated, row, adder::withAggregated);
+ SeriesUtils.applyBooleanIfPresent(qlim, row, adder::withQlim);
+ adder.add();
+ }
+ }
+
+ @Override
+ public void addElements(Network network, UpdatingDataframe dataframe) {
+ SynchronousGeneratorPropertiesSeries series = new SynchronousGeneratorPropertiesSeries(dataframe);
+ for (int row = 0; row < dataframe.getRowCount(); row++) {
+ series.create(network, row);
+ }
+ }
+}
diff --git a/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronousGeneratorPropertiesDataframeProvider.java b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronousGeneratorPropertiesDataframeProvider.java
new file mode 100644
index 0000000000..875d142187
--- /dev/null
+++ b/java/pypowsybl/src/main/java/com/powsybl/dataframe/dynamic/extensions/SynchronousGeneratorPropertiesDataframeProvider.java
@@ -0,0 +1,104 @@
+/**
+ * Copyright (c) 2021-2022, RTE (http://www.rte-france.com)
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+package com.powsybl.dataframe.dynamic.extensions;
+
+import com.google.auto.service.AutoService;
+import com.powsybl.commons.PowsyblException;
+import com.powsybl.dataframe.network.ExtensionInformation;
+import com.powsybl.dataframe.network.NetworkDataframeMapper;
+import com.powsybl.dataframe.network.NetworkDataframeMapperBuilder;
+import com.powsybl.dataframe.network.adders.NetworkElementAdder;
+import com.powsybl.dataframe.network.extensions.AbstractSingleDataframeNetworkExtension;
+import com.powsybl.dataframe.network.extensions.NetworkExtensionDataframeProvider;
+import com.powsybl.dynawo.extensions.api.generator.RpclType;
+import com.powsybl.dynawo.extensions.api.generator.SynchronousGeneratorProperties;
+import com.powsybl.iidm.network.Generator;
+import com.powsybl.iidm.network.Network;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Stream;
+
+/**
+ * @author Gautier Bureau {@literal }
+ * @author Laurent Issertial {@literal }
+ */
+@AutoService(NetworkExtensionDataframeProvider.class)
+public class SynchronousGeneratorPropertiesDataframeProvider extends AbstractSingleDataframeNetworkExtension {
+
+ @Override
+ public String getExtensionName() {
+ return SynchronousGeneratorProperties.NAME;
+ }
+
+ @Override
+ public ExtensionInformation getExtensionInformation() {
+ return new ExtensionInformation(SynchronousGeneratorProperties.NAME,
+ "Provides information, for dynamic simulation only, about the characteristics of a synchronous generator",
+ "index : id (str), " +
+ "numberOfWindings (str), " +
+ "governor (str), " +
+ "voltageRegulator (str), " +
+ "pss (str), " +
+ "auxiliaries (bool), " +
+ "internalTransformer (bool), " +
+ "rpcl (str), " +
+ "uva (str), " +
+ "aggregated (bool), " +
+ "qlim (bool)");
+ }
+
+ private Stream itemsStream(Network network) {
+ return network.getGeneratorStream()
+ .map(g -> (SynchronousGeneratorProperties) g.getExtension(SynchronousGeneratorProperties.class))
+ .filter(Objects::nonNull);
+ }
+
+ private SynchronousGeneratorProperties getOrThrow(Network network, String id) {
+ Generator gen = network.getGenerator(id);
+ if (gen == null) {
+ throw new PowsyblException("Generator '" + id + "' not found");
+ }
+ SynchronousGeneratorProperties sgp = gen.getExtension(SynchronousGeneratorProperties.class);
+ if (sgp == null) {
+ throw new PowsyblException("Generator '" + id + "' has no SynchronousGeneratorProperties extension");
+ }
+ return sgp;
+ }
+
+ @Override
+ public NetworkDataframeMapper createMapper() {
+ return NetworkDataframeMapperBuilder.ofStream(this::itemsStream, this::getOrThrow)
+ .stringsIndex("id", ext -> ext.getExtendable().getId())
+ .enums("numberOfWindings", SynchronousGeneratorProperties.Windings.class, SynchronousGeneratorProperties::getNumberOfWindings, SynchronousGeneratorProperties::setNumberOfWindings)
+ .strings("governor", sgp -> String.valueOf(sgp.getGovernor()), SynchronousGeneratorProperties::setGovernor)
+ .strings("voltageRegulator", sgp -> String.valueOf(sgp.getVoltageRegulator()), SynchronousGeneratorProperties::setVoltageRegulator)
+ .strings("pss", sgp -> String.valueOf(sgp.getPss()), SynchronousGeneratorProperties::setPss)
+ .booleans("auxiliaries", SynchronousGeneratorProperties::isAuxiliaries, SynchronousGeneratorProperties::setAuxiliaries)
+ .booleans("internalTransformer", SynchronousGeneratorProperties::isInternalTransformer, SynchronousGeneratorProperties::setInternalTransformer)
+ .enums("rpcl", RpclType.class, SynchronousGeneratorProperties::getRpcl, SynchronousGeneratorProperties::setRpcl)
+ .enums("uva", SynchronousGeneratorProperties.Uva.class, SynchronousGeneratorProperties::getUva, SynchronousGeneratorProperties::setUva)
+ .booleans("aggregated", SynchronousGeneratorProperties::isAggregated, SynchronousGeneratorProperties::setAggregated)
+ .booleans("qlim", SynchronousGeneratorProperties::isQlim, SynchronousGeneratorProperties::setQlim)
+ .build();
+ }
+
+ @Override
+ public void removeExtensions(Network network, List ids) {
+ ids.stream().filter(Objects::nonNull)
+ .map(network::getGenerator)
+ .filter(Objects::nonNull)
+ .forEach(g -> g.removeExtension(SynchronousGeneratorProperties.class));
+ }
+
+ @Override
+ public NetworkElementAdder createAdder() {
+ return new SynchronousGeneratorPropertiesDataframeAdder();
+ }
+
+}
diff --git a/java/pypowsybl/src/test/java/com/powsybl/dataframe/dynamic/extensions/DynamicExtensionAddersTest.java b/java/pypowsybl/src/test/java/com/powsybl/dataframe/dynamic/extensions/DynamicExtensionAddersTest.java
new file mode 100644
index 0000000000..a52ce8bfe1
--- /dev/null
+++ b/java/pypowsybl/src/test/java/com/powsybl/dataframe/dynamic/extensions/DynamicExtensionAddersTest.java
@@ -0,0 +1,115 @@
+/**
+ * Copyright (c) 2025, RTE (http://www.rte-france.com)
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ * SPDX-License-Identifier: MPL-2.0
+ */
+package com.powsybl.dataframe.dynamic.extensions;
+
+import com.powsybl.dataframe.network.adders.NetworkElementAdders;
+import com.powsybl.dataframe.update.DefaultUpdatingDataframe;
+import com.powsybl.dataframe.update.TestIntSeries;
+import com.powsybl.dataframe.update.TestStringSeries;
+import com.powsybl.dynawo.extensions.api.generator.RpclType;
+import com.powsybl.dynawo.extensions.api.generator.SynchronizedGeneratorProperties;
+import com.powsybl.dynawo.extensions.api.generator.SynchronousGeneratorProperties;
+import com.powsybl.dynawo.extensions.api.generator.connection.GeneratorConnectionLevel;
+import com.powsybl.iidm.network.test.EurostagTutorialExample1Factory;
+import org.junit.jupiter.api.Test;
+
+import static java.util.Collections.singletonList;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * @author Laurent Issertial {@literal }
+ */
+class DynamicExtensionAddersTest {
+
+ @Test
+ void synchronousGeneratorPropertiesExtension() {
+ var network = EurostagTutorialExample1Factory.create();
+ String genId = "GEN";
+ SynchronousGeneratorProperties extension = network.getGenerator(genId).getExtension(SynchronousGeneratorProperties.class);
+ assertNull(extension);
+ SynchronousGeneratorProperties.Windings numberOfWindings = SynchronousGeneratorProperties.Windings.FOUR_WINDINGS;
+ String governor = "Proportional";
+ String voltageRegulator = "Proportional";
+ String pss = "";
+ RpclType rpcl = RpclType.RPCL1;
+ SynchronousGeneratorProperties.Uva uva = SynchronousGeneratorProperties.Uva.DISTANT;
+
+ DefaultUpdatingDataframe dataframe = new DefaultUpdatingDataframe(1);
+ addStringColumn(dataframe, "id", genId);
+ addStringColumn(dataframe, "numberOfWindings", String.valueOf(numberOfWindings));
+ addStringColumn(dataframe, "governor", governor);
+ addStringColumn(dataframe, "voltageRegulator", voltageRegulator);
+ addStringColumn(dataframe, "pss", pss);
+ addIntColumn(dataframe, "auxiliaries", 1);
+ addIntColumn(dataframe, "internalTransformer", 0);
+ addStringColumn(dataframe, "rpcl", String.valueOf(rpcl));
+ addStringColumn(dataframe, "uva", String.valueOf(uva));
+ addIntColumn(dataframe, "fictitious", 0);
+ addIntColumn(dataframe, "qlim", 0);
+ NetworkElementAdders.addExtensions("synchronousGeneratorProperties", network, singletonList(dataframe));
+
+ extension = network.getGenerator(genId).getExtension(SynchronousGeneratorProperties.class);
+ assertNotNull(extension);
+ assertEquals(numberOfWindings, extension.getNumberOfWindings());
+ assertEquals(governor, extension.getGovernor());
+ assertEquals(voltageRegulator, extension.getVoltageRegulator());
+ assertEquals(pss, extension.getPss());
+ assertTrue(extension.isAuxiliaries());
+ assertFalse(extension.isInternalTransformer());
+ assertEquals(rpcl, extension.getRpcl());
+ assertEquals(uva, extension.getUva());
+ assertFalse(extension.isAggregated());
+ assertFalse(extension.isQlim());
+ }
+
+ @Test
+ void synchronizedGeneratorPropertiesExtension() {
+ var network = EurostagTutorialExample1Factory.create();
+ String genId = "GEN";
+ SynchronizedGeneratorProperties extension = network.getGenerator(genId).getExtension(SynchronizedGeneratorProperties.class);
+ assertNull(extension);
+ String type = "PV";
+
+ DefaultUpdatingDataframe dataframe = new DefaultUpdatingDataframe(1);
+ addStringColumn(dataframe, "id", genId);
+ addStringColumn(dataframe, "type", type);
+ addIntColumn(dataframe, "rpcl2", 0);
+ NetworkElementAdders.addExtensions("synchronizedGeneratorProperties", network, singletonList(dataframe));
+
+ extension = network.getGenerator(genId).getExtension(SynchronizedGeneratorProperties.class);
+ assertNotNull(extension);
+ assertEquals(type, extension.getType());
+ assertFalse(extension.isRpcl2());
+ }
+
+ @Test
+ void generatorConnectionLevelExtension() {
+ var network = EurostagTutorialExample1Factory.create();
+ String genId = "GEN";
+ GeneratorConnectionLevel extension = network.getGenerator(genId).getExtension(GeneratorConnectionLevel.class);
+ assertNull(extension);
+ String level = "TSO";
+
+ DefaultUpdatingDataframe dataframe = new DefaultUpdatingDataframe(1);
+ addStringColumn(dataframe, "id", genId);
+ addStringColumn(dataframe, "level", level);
+ NetworkElementAdders.addExtensions("generatorConnectionLevel", network, singletonList(dataframe));
+
+ extension = network.getGenerator(genId).getExtension(GeneratorConnectionLevel.class);
+ assertNotNull(extension);
+ assertEquals(GeneratorConnectionLevel.GeneratorConnectionLevelType.valueOf(level), extension.getLevel());
+ }
+
+ private void addStringColumn(DefaultUpdatingDataframe dataframe, String column, String... value) {
+ dataframe.addSeries(column, false, new TestStringSeries(value));
+ }
+
+ private void addIntColumn(DefaultUpdatingDataframe dataframe, String column, int... value) {
+ dataframe.addSeries(column, false, new TestIntSeries(value));
+ }
+}
diff --git a/tests/test_dynamic.py b/tests/test_dynamic.py
index f8087769ea..1090ff0c43 100644
--- a/tests/test_dynamic.py
+++ b/tests/test_dynamic.py
@@ -8,6 +8,7 @@
import pypowsybl.dynamic as dyn
import pytest
import pandas as pd
+import pypowsybl.network as pn
@pytest.fixture(autouse=True)
def set_up():
@@ -165,3 +166,88 @@ def test_parameters():
assert 100.0 == parameters.stop_time
assert 'IDA'== parameters.provider_parameters['solver.type']
assert '1e-5' == parameters.provider_parameters['precision']
+
+
+def test_synchronous_generator_properties():
+ n = pn.create_four_substations_node_breaker_network()
+ extension_name = 'synchronousGeneratorProperties'
+ element_id = 'GH1'
+
+ extensions = n.get_extensions(extension_name)
+ assert extensions.empty
+
+ n.create_extensions(extension_name, id=element_id, numberOfWindings="THREE_WINDINGS",
+ governor="Proportional", voltageRegulator="Proportional", pss="",
+ auxiliaries=True, internalTransformer=False, rpcl="RPCL1",
+ aggregated=False, qlim=False)
+ e = n.get_extensions(extension_name).loc[element_id]
+ assert e.numberOfWindings == "THREE_WINDINGS"
+ assert e.governor == "Proportional"
+ assert e.voltageRegulator == "Proportional"
+ assert e.pss == ""
+ assert e.auxiliaries == True
+ assert e.internalTransformer == False
+ assert e.rpcl == "RPCL1"
+ assert e.uva == "LOCAL"
+ assert e.aggregated == False
+ assert e.qlim == False
+
+ n.update_extensions(extension_name, id=element_id, numberOfWindings="FOUR_WINDINGS",
+ governor="ProportionalIntegral", voltageRegulator="ProportionalIntegral", pss="Pss",
+ auxiliaries=False, internalTransformer=True, rpcl="RPCL2",
+ uva="DISTANT", aggregated=True, qlim=True)
+ e = n.get_extensions(extension_name).loc[element_id]
+ assert e.numberOfWindings == "FOUR_WINDINGS"
+ assert e.governor == "ProportionalIntegral"
+ assert e.voltageRegulator == "ProportionalIntegral"
+ assert e.pss == "Pss"
+ assert e.auxiliaries == False
+ assert e.internalTransformer == True
+ assert e.rpcl == "RPCL2"
+ assert e.uva == "DISTANT"
+ assert e.aggregated == True
+ assert e.qlim == True
+
+ n.remove_extensions(extension_name, [element_id])
+ assert n.get_extensions(extension_name).empty
+
+def test_synchronized_generator_properties():
+ n = pn.create_four_substations_node_breaker_network()
+ extension_name = 'synchronizedGeneratorProperties'
+ element_id = 'GH1'
+
+ extensions = n.get_extensions(extension_name)
+ assert extensions.empty
+
+ n.create_extensions(extension_name, id=element_id,
+ type="PV", rpcl2=True)
+ e = n.get_extensions(extension_name).loc[element_id]
+ assert e.type == "PV"
+ assert e.rpcl2 == True
+
+ n.update_extensions(extension_name, id=element_id, type="PfQ", rpcl2=False)
+ e = n.get_extensions(extension_name).loc[element_id]
+ assert e.type == "PfQ"
+ assert e.rpcl2 == False
+
+ n.remove_extensions(extension_name, [element_id])
+ assert n.get_extensions(extension_name).empty
+
+def test_generator_connection_level_properties():
+ n = pn.create_four_substations_node_breaker_network()
+ extension_name = 'generatorConnectionLevel'
+ element_id = 'GH1'
+
+ extensions = n.get_extensions(extension_name)
+ assert extensions.empty
+
+ n.create_extensions(extension_name, id=element_id, level='TSO')
+ e = n.get_extensions(extension_name).loc[element_id]
+ assert e.level == 'TSO'
+
+ n.update_extensions(extension_name, id=element_id, level='DSO')
+ e = n.get_extensions(extension_name).loc[element_id]
+ assert e.level == 'DSO'
+
+ n.remove_extensions(extension_name, [element_id])
+ assert n.get_extensions(extension_name).empty
diff --git a/tests/test_network_extensions.py b/tests/test_network_extensions.py
index f163238d74..e97f13dd52 100644
--- a/tests/test_network_extensions.py
+++ b/tests/test_network_extensions.py
@@ -588,7 +588,6 @@ def test_batteries_voltage_regulation():
network.remove_extensions('voltageRegulation', ['BAT'])
assert network.get_extensions('voltageRegulation').empty
-
def test_get_extensions_information():
extensions_information = pypowsybl.network.get_extensions_information()
assert extensions_information.loc['cgmesMetadataModels']['detail'] == 'Provides information about CGMES metadata models'
@@ -640,3 +639,6 @@ def test_get_extensions_information():
assert extensions_information.loc['voltagePerReactivePowerControl']['attributes'] == 'index : id (str), slope (float)'
assert extensions_information.loc['voltageRegulation']['detail'] == 'it allows to specify the voltage regulation mode for batteries'
assert extensions_information.loc['voltageRegulation']['attributes'] == 'index : id (str), voltage_regulator_on (bool), target_v (float), regulated_element_id (str)'
+ assert extensions_information.loc['synchronousGeneratorProperties']['attributes'] == 'index : id (str), numberOfWindings (str), governor (str), voltageRegulator (str), pss (str), auxiliaries (bool), internalTransformer (bool), rpcl (str), uva (str), aggregated (bool), qlim (bool)'
+ assert extensions_information.loc['synchronizedGeneratorProperties']['attributes'] == 'index : id (str), type (str), rpcl2 (bool)'
+ assert extensions_information.loc['generatorConnectionLevel']['attributes'] == 'index : id (str), level (str)'