diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 86e7cc7..1ea59f2 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -207,6 +207,7 @@ When migrating: - Add `get_microgrid_info()` returning a rich `MicrogridInfo` dataclass with ID, enterprise, location, delivery area, status, and timestamps. - Add a metrics model under `frequenz.client.microgrid.metrics` including the `Metric` enum, `Bounds`, and `MetricSample`/`AggregatedMetricValue`. - Add high-level methods on `MicrogridApiClient` for listing components and connections, adding component bounds, receiving component data samples streams, and controlling active/reactive power with lifetimes. +- Add `WindTurbine` component type. ## Bug Fixes diff --git a/src/frequenz/client/microgrid/component/__init__.py b/src/frequenz/client/microgrid/component/__init__.py index e6dee16..a316ff4 100644 --- a/src/frequenz/client/microgrid/component/__init__.py +++ b/src/frequenz/client/microgrid/component/__init__.py @@ -58,6 +58,7 @@ UnspecifiedComponentTypes, ) from ._voltage_transformer import VoltageTransformer +from ._wind_turbine import WindTurbine __all__ = [ "AcEvCharger", @@ -107,4 +108,5 @@ "UnspecifiedEvCharger", "UnspecifiedInverter", "VoltageTransformer", + "WindTurbine", ] diff --git a/src/frequenz/client/microgrid/component/_category.py b/src/frequenz/client/microgrid/component/_category.py index e3e4b4c..99a5819 100644 --- a/src/frequenz/client/microgrid/component/_category.py +++ b/src/frequenz/client/microgrid/component/_category.py @@ -104,3 +104,6 @@ class ComponentCategory(enum.Enum): HVAC = electrical_components_pb2.ELECTRICAL_COMPONENT_CATEGORY_HVAC """A Heating, Ventilation, and Air Conditioning (HVAC) system.""" + + WIND_TURBINE = electrical_components_pb2.ELECTRICAL_COMPONENT_CATEGORY_WIND_TURBINE + """A wind turbine.""" diff --git a/src/frequenz/client/microgrid/component/_component_proto.py b/src/frequenz/client/microgrid/component/_component_proto.py index d92806b..662eff2 100644 --- a/src/frequenz/client/microgrid/component/_component_proto.py +++ b/src/frequenz/client/microgrid/component/_component_proto.py @@ -60,6 +60,7 @@ from ._relay import Relay from ._types import ComponentTypes from ._voltage_transformer import VoltageTransformer +from ._wind_turbine import WindTurbine _logger = logging.getLogger(__name__) @@ -255,6 +256,7 @@ def component_from_proto_with_issues( | ComponentCategory.METER | ComponentCategory.PRECHARGER | ComponentCategory.RELAY + | ComponentCategory.WIND_TURBINE ): return _trivial_category_to_class(base_data.category)( id=base_data.component_id, @@ -449,6 +451,7 @@ def _trivial_category_to_class( | Meter | Precharger | Relay + | WindTurbine ]: """Return the class corresponding to a trivial component category.""" return { @@ -461,6 +464,7 @@ def _trivial_category_to_class( ComponentCategory.METER: Meter, ComponentCategory.PRECHARGER: Precharger, ComponentCategory.RELAY: Relay, + ComponentCategory.WIND_TURBINE: WindTurbine, }[category] diff --git a/src/frequenz/client/microgrid/component/_types.py b/src/frequenz/client/microgrid/component/_types.py index 043a7be..556bfcf 100644 --- a/src/frequenz/client/microgrid/component/_types.py +++ b/src/frequenz/client/microgrid/component/_types.py @@ -23,6 +23,7 @@ ) from ._relay import Relay from ._voltage_transformer import VoltageTransformer +from ._wind_turbine import WindTurbine UnspecifiedComponentTypes: TypeAlias = ( UnspecifiedBattery @@ -59,5 +60,6 @@ | ProblematicComponentTypes | Relay | VoltageTransformer + | WindTurbine ) """All possible component types.""" diff --git a/src/frequenz/client/microgrid/component/_wind_turbine.py b/src/frequenz/client/microgrid/component/_wind_turbine.py new file mode 100644 index 0000000..12d6578 --- /dev/null +++ b/src/frequenz/client/microgrid/component/_wind_turbine.py @@ -0,0 +1,18 @@ +# License: MIT +# Copyright © 2025 Frequenz Energy-as-a-Service GmbH + +"""Wind turbine component.""" + +import dataclasses +from typing import Literal + +from ._category import ComponentCategory +from ._component import Component + + +@dataclasses.dataclass(frozen=True, kw_only=True) +class WindTurbine(Component): + """A wind turbine component.""" + + category: Literal[ComponentCategory.WIND_TURBINE] = ComponentCategory.WIND_TURBINE + """The category of this component.""" diff --git a/tests/client_test_cases/list_components/diverse_component_types_case.py b/tests/client_test_cases/list_components/diverse_component_types_case.py index 99fe4ea..4737b95 100644 --- a/tests/client_test_cases/list_components/diverse_component_types_case.py +++ b/tests/client_test_cases/list_components/diverse_component_types_case.py @@ -47,6 +47,7 @@ UnspecifiedEvCharger, UnspecifiedInverter, VoltageTransformer, + WindTurbine, ) # No client_args or client_kwargs needed for this call @@ -228,17 +229,21 @@ def assert_stub_method_call(stub_method: Any) -> None: id=25, category=ec_pb2.ELECTRICAL_COMPONENT_CATEGORY_POWER_TRANSFORMER, ), - # Problematic components ec_pb2.ElectricalComponent( id=26, - category=ec_pb2.ELECTRICAL_COMPONENT_CATEGORY_UNSPECIFIED, + category=ec_pb2.ELECTRICAL_COMPONENT_CATEGORY_WIND_TURBINE, ), + # Problematic components ec_pb2.ElectricalComponent( id=27, - category=999, # type: ignore[arg-type] + category=ec_pb2.ELECTRICAL_COMPONENT_CATEGORY_UNSPECIFIED, ), ec_pb2.ElectricalComponent( id=28, + category=999, # type: ignore[arg-type] + ), + ec_pb2.ElectricalComponent( + id=29, category=ec_pb2.ELECTRICAL_COMPONENT_CATEGORY_BATTERY, category_specific_info=ec_pb2.ElectricalComponentCategorySpecificInfo( # Mismatched: battery category with inverter metadata @@ -292,13 +297,14 @@ def assert_client_result(result: Any) -> None: primary_voltage=0.0, secondary_voltage=0.0, ), + WindTurbine(id=ComponentId(26), microgrid_id=MicrogridId(0)), # Problematic components - UnspecifiedComponent(id=ComponentId(26), microgrid_id=MicrogridId(0)), + UnspecifiedComponent(id=ComponentId(27), microgrid_id=MicrogridId(0)), UnrecognizedComponent( - id=ComponentId(27), microgrid_id=MicrogridId(0), category=999 + id=ComponentId(28), microgrid_id=MicrogridId(0), category=999 ), MismatchedCategoryComponent( - id=ComponentId(28), + id=ComponentId(29), microgrid_id=MicrogridId(0), category=ComponentCategory.BATTERY, category_specific_metadata={ diff --git a/tests/component/component_proto/test_simple.py b/tests/component/component_proto/test_simple.py index ec755f4..0fa8527 100644 --- a/tests/component/component_proto/test_simple.py +++ b/tests/component/component_proto/test_simple.py @@ -28,6 +28,7 @@ UnrecognizedComponent, UnspecifiedComponent, VoltageTransformer, + WindTurbine, ) from frequenz.client.microgrid.component._component_proto import ( ComponentBaseData, @@ -112,6 +113,7 @@ def test_category_mismatch(default_component_base_data: ComponentBaseData) -> No pytest.param(ComponentCategory.METER, Meter, id="Meter"), pytest.param(ComponentCategory.PRECHARGER, Precharger, id="Precharger"), pytest.param(ComponentCategory.RELAY, Relay, id="Relay"), + pytest.param(ComponentCategory.WIND_TURBINE, WindTurbine, id="WindTurbine"), ], ) def test_trivial( diff --git a/tests/component/test_wind_turbine.py b/tests/component/test_wind_turbine.py new file mode 100644 index 0000000..598681a --- /dev/null +++ b/tests/component/test_wind_turbine.py @@ -0,0 +1,30 @@ +# License: MIT +# Copyright © 2025 Frequenz Energy-as-a-Service GmbH + +"""Tests for WindTurbine component.""" + + +from frequenz.client.common.microgrid import MicrogridId +from frequenz.client.common.microgrid.components import ComponentId + +from frequenz.client.microgrid.component import ComponentCategory, WindTurbine + + +def test_init() -> None: + """Test WindTurbine component initialization.""" + component_id = ComponentId(1) + microgrid_id = MicrogridId(1) + component = WindTurbine( + id=component_id, + microgrid_id=microgrid_id, + name="wind_turbine_test", + manufacturer="test_manufacturer", + model_name="test_model", + ) + + assert component.id == component_id + assert component.microgrid_id == microgrid_id + assert component.name == "wind_turbine_test" + assert component.manufacturer == "test_manufacturer" + assert component.model_name == "test_model" + assert component.category == ComponentCategory.WIND_TURBINE