diff --git a/src/frequenz/sdk/actor/_data_sourcing/_component_metric_request.py b/src/frequenz/sdk/actor/_data_sourcing/_component_metric_request.py index 11eec9451..5300a1361 100644 --- a/src/frequenz/sdk/actor/_data_sourcing/_component_metric_request.py +++ b/src/frequenz/sdk/actor/_data_sourcing/_component_metric_request.py @@ -6,7 +6,7 @@ from dataclasses import dataclass from datetime import datetime -from ...microgrid.component._component import ComponentMetricId +from ...microgrid.component._component import ComponentId, ComponentMetricId @dataclass @@ -24,7 +24,7 @@ class ComponentMetricRequest: possible. """ - component_id: int + component_id: ComponentId """The ID of the requested component.""" metric_id: ComponentMetricId diff --git a/src/frequenz/sdk/actor/power_distributing/_battery_status_tracker.py b/src/frequenz/sdk/actor/power_distributing/_battery_status_tracker.py index adcb88436..755d86e05 100644 --- a/src/frequenz/sdk/actor/power_distributing/_battery_status_tracker.py +++ b/src/frequenz/sdk/actor/power_distributing/_battery_status_tracker.py @@ -26,6 +26,7 @@ BatteryData, ComponentCategory, ComponentData, + ComponentId, InverterData, ) from ._component_status import ( @@ -40,7 +41,7 @@ @dataclass class _ComponentStreamStatus: - component_id: int + component_id: ComponentId """Component id.""" data_recv_timer: Timer @@ -165,7 +166,7 @@ class BatteryStatusTracker(ComponentStatusTracker): @override def __init__( # pylint: disable=too-many-arguments self, - component_id: int, + component_id: ComponentId, max_data_age_sec: float, max_blocking_duration_sec: float, status_sender: Sender[ComponentStatus], diff --git a/src/frequenz/sdk/actor/power_distributing/_component_status.py b/src/frequenz/sdk/actor/power_distributing/_component_status.py index a5c22938b..0f039ed68 100644 --- a/src/frequenz/sdk/actor/power_distributing/_component_status.py +++ b/src/frequenz/sdk/actor/power_distributing/_component_status.py @@ -11,6 +11,8 @@ from frequenz.channels import Receiver, Sender +from ...microgrid.component._component import ComponentId + @dataclass class ComponentPoolStatus: @@ -58,7 +60,7 @@ class ComponentStatusEnum(enum.Enum): class ComponentStatus: """Status of a single component.""" - component_id: int + component_id: ComponentId """Component ID.""" value: ComponentStatusEnum @@ -82,7 +84,7 @@ class ComponentStatusTracker(ABC): @abstractmethod def __init__( # pylint: disable=too-many-arguments self, - component_id: int, + component_id: ComponentId, max_data_age_sec: float, max_blocking_duration_sec: float, status_sender: Sender[ComponentStatus], diff --git a/src/frequenz/sdk/actor/power_distributing/_distribution_algorithm/_battery_distribution_algorithm.py b/src/frequenz/sdk/actor/power_distributing/_distribution_algorithm/_battery_distribution_algorithm.py index 2132bc03c..8448e17f4 100644 --- a/src/frequenz/sdk/actor/power_distributing/_distribution_algorithm/_battery_distribution_algorithm.py +++ b/src/frequenz/sdk/actor/power_distributing/_distribution_algorithm/_battery_distribution_algorithm.py @@ -9,7 +9,7 @@ from typing import NamedTuple, Sequence from ...._internal._math import is_close_to_zero -from ....microgrid.component import BatteryData, InverterData +from ....microgrid.component import BatteryData, ComponentId, InverterData from ..result import PowerBounds _logger = logging.getLogger(__name__) @@ -19,7 +19,7 @@ class AggregatedBatteryData: """Aggregated battery data.""" - component_id: int + component_id: ComponentId """The component ID of the first battery. This is only used to identify the pair of battery and inverter. diff --git a/src/frequenz/sdk/microgrid/client/_client.py b/src/frequenz/sdk/microgrid/client/_client.py index a3382a625..8f101b94f 100644 --- a/src/frequenz/sdk/microgrid/client/_client.py +++ b/src/frequenz/sdk/microgrid/client/_client.py @@ -22,6 +22,7 @@ BatteryData, Component, ComponentCategory, + ComponentId, EVChargerData, InverterData, MeterData, @@ -93,7 +94,7 @@ async def connections( @abstractmethod async def meter_data( self, - component_id: int, + component_id: ComponentId, maxsize: int = RECEIVER_MAX_SIZE, ) -> Receiver[MeterData]: """Return a channel receiver that provides a `MeterData` stream. @@ -113,7 +114,7 @@ async def meter_data( @abstractmethod async def battery_data( self, - component_id: int, + component_id: ComponentId, maxsize: int = RECEIVER_MAX_SIZE, ) -> Receiver[BatteryData]: """Return a channel receiver that provides a `BatteryData` stream. @@ -133,7 +134,7 @@ async def battery_data( @abstractmethod async def inverter_data( self, - component_id: int, + component_id: ComponentId, maxsize: int = RECEIVER_MAX_SIZE, ) -> Receiver[InverterData]: """Return a channel receiver that provides an `InverterData` stream. @@ -153,7 +154,7 @@ async def inverter_data( @abstractmethod async def ev_charger_data( self, - component_id: int, + component_id: ComponentId, maxsize: int = RECEIVER_MAX_SIZE, ) -> Receiver[EVChargerData]: """Return a channel receiver that provides an `EvChargeData` stream. @@ -171,7 +172,7 @@ async def ev_charger_data( """ @abstractmethod - async def set_power(self, component_id: int, power_w: float) -> None: + async def set_power(self, component_id: ComponentId, power_w: float) -> None: """Send request to the Microgrid to set power for component. If power > 0, then component will be charged with this power. @@ -185,7 +186,9 @@ async def set_power(self, component_id: int, power_w: float) -> None: """ @abstractmethod - async def set_bounds(self, component_id: int, lower: float, upper: float) -> None: + async def set_bounds( + self, component_id: ComponentId, lower: float, upper: float + ) -> None: """Send `SetBoundsParam`s received from a channel to the Microgrid service. Args: @@ -367,7 +370,7 @@ async def connections( async def _component_data_task( self, - component_id: int, + component_id: ComponentId, transform: Callable[[microgrid_pb.ComponentData], _GenericComponentData], sender: Sender[_GenericComponentData], ) -> None: @@ -421,7 +424,7 @@ async def _component_data_task( def _get_component_data_channel( self, - component_id: int, + component_id: ComponentId, transform: Callable[[microgrid_pb.ComponentData], _GenericComponentData], ) -> Broadcast[_GenericComponentData]: """Return the broadcast channel for a given component_id. @@ -456,7 +459,7 @@ def _get_component_data_channel( async def _expect_category( self, - component_id: int, + component_id: ComponentId, expected_category: ComponentCategory, ) -> None: """Check if the given component_id is of the expected type. @@ -488,7 +491,7 @@ async def _expect_category( async def meter_data( # noqa: DOC502 (ValueError is raised indirectly by _expect_category) self, - component_id: int, + component_id: ComponentId, maxsize: int = RECEIVER_MAX_SIZE, ) -> Receiver[MeterData]: """Return a channel receiver that provides a `MeterData` stream. @@ -518,7 +521,7 @@ async def meter_data( # noqa: DOC502 (ValueError is raised indirectly by _expec async def battery_data( # noqa: DOC502 (ValueError is raised indirectly by _expect_category) self, - component_id: int, + component_id: ComponentId, maxsize: int = RECEIVER_MAX_SIZE, ) -> Receiver[BatteryData]: """Return a channel receiver that provides a `BatteryData` stream. @@ -548,7 +551,7 @@ async def battery_data( # noqa: DOC502 (ValueError is raised indirectly by _exp async def inverter_data( # noqa: DOC502 (ValueError is raised indirectly by _expect_category) self, - component_id: int, + component_id: ComponentId, maxsize: int = RECEIVER_MAX_SIZE, ) -> Receiver[InverterData]: """Return a channel receiver that provides an `InverterData` stream. @@ -578,7 +581,7 @@ async def inverter_data( # noqa: DOC502 (ValueError is raised indirectly by _ex async def ev_charger_data( # noqa: DOC502 (ValueError is raised indirectly by _expect_category) self, - component_id: int, + component_id: ComponentId, maxsize: int = RECEIVER_MAX_SIZE, ) -> Receiver[EVChargerData]: """Return a channel receiver that provides an `EvChargeData` stream. @@ -606,7 +609,7 @@ async def ev_charger_data( # noqa: DOC502 (ValueError is raised indirectly by _ EVChargerData.from_proto, ).new_receiver(maxsize=maxsize) - async def set_power(self, component_id: int, power_w: float) -> None: + async def set_power(self, component_id: ComponentId, power_w: float) -> None: """Send request to the Microgrid to set power for component. If power > 0, then component will be charged with this power. @@ -644,7 +647,7 @@ async def set_power(self, component_id: int, power_w: float) -> None: async def set_bounds( self, - component_id: int, + component_id: ComponentId, lower: float, upper: float, ) -> None: diff --git a/src/frequenz/sdk/microgrid/component/__init__.py b/src/frequenz/sdk/microgrid/component/__init__.py index f1c58851d..fb1bb9835 100644 --- a/src/frequenz/sdk/microgrid/component/__init__.py +++ b/src/frequenz/sdk/microgrid/component/__init__.py @@ -9,6 +9,7 @@ from ._component import ( Component, ComponentCategory, + ComponentId, ComponentMetadata, ComponentMetricId, GridMetadata, @@ -27,6 +28,7 @@ "BatteryData", "Component", "ComponentData", + "ComponentId", "ComponentCategory", "ComponentMetadata", "ComponentMetricId", diff --git a/src/frequenz/sdk/microgrid/component/_component.py b/src/frequenz/sdk/microgrid/component/_component.py index 9402b8615..ddf58e324 100644 --- a/src/frequenz/sdk/microgrid/component/_component.py +++ b/src/frequenz/sdk/microgrid/component/_component.py @@ -6,7 +6,7 @@ from dataclasses import dataclass from enum import Enum -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, NewType import frequenz.api.common.components_pb2 as components_pb import frequenz.api.microgrid.grid_pb2 as grid_pb @@ -17,6 +17,9 @@ from ...timeseries import Fuse +ComponentId = NewType("ComponentId", int) + + class ComponentType(Enum): """A base class from which individual component types are derived.""" @@ -157,7 +160,7 @@ def _component_metadata_from_protobuf( class Component: """Metadata for a single microgrid component.""" - component_id: int + component_id: ComponentId """The ID of this component.""" category: ComponentCategory diff --git a/src/frequenz/sdk/microgrid/component/_component_data.py b/src/frequenz/sdk/microgrid/component/_component_data.py index b5eb9a057..db7b600af 100644 --- a/src/frequenz/sdk/microgrid/component/_component_data.py +++ b/src/frequenz/sdk/microgrid/component/_component_data.py @@ -12,6 +12,8 @@ import frequenz.api.microgrid.inverter_pb2 as inverter_pb import frequenz.api.microgrid.microgrid_pb2 as microgrid_pb +from frequenz.sdk.microgrid.component import ComponentId + from ._component_states import EVChargerCableState, EVChargerComponentState @@ -19,7 +21,7 @@ class ComponentData(ABC): """A private base class for strongly typed component data classes.""" - component_id: int + component_id: ComponentId """The ID identifying this component in the microgrid.""" timestamp: datetime diff --git a/src/frequenz/sdk/microgrid/component_graph.py b/src/frequenz/sdk/microgrid/component_graph.py index 52ceb5def..a5a09c098 100644 --- a/src/frequenz/sdk/microgrid/component_graph.py +++ b/src/frequenz/sdk/microgrid/component_graph.py @@ -30,7 +30,7 @@ import networkx as nx from .client import Connection, MicrogridApiClient -from .component import Component, ComponentCategory, InverterType +from .component import Component, ComponentCategory, ComponentId, InverterType _logger = logging.getLogger(__name__) @@ -80,7 +80,7 @@ def connections( """ @abstractmethod - def predecessors(self, component_id: int) -> set[Component]: + def predecessors(self, component_id: ComponentId) -> set[Component]: """Fetch the graph predecessors of the specified component. Args: @@ -97,7 +97,7 @@ def predecessors(self, component_id: int) -> set[Component]: """ @abstractmethod - def successors(self, component_id: int) -> set[Component]: + def successors(self, component_id: ComponentId) -> set[Component]: """Fetch the graph successors of the specified component. Args: @@ -391,7 +391,7 @@ def connections( return set(map(lambda c: Connection(c[0], c[1]), selection)) - def predecessors(self, component_id: int) -> set[Component]: + def predecessors(self, component_id: ComponentId) -> set[Component]: """Fetch the graph predecessors of the specified component. Args: @@ -417,7 +417,7 @@ def predecessors(self, component_id: int) -> set[Component]: map(lambda idx: Component(**self._graph.nodes[idx]), predecessors_ids) ) - def successors(self, component_id: int) -> set[Component]: + def successors(self, component_id: ComponentId) -> set[Component]: """Fetch the graph successors of the specified component. Args: diff --git a/src/frequenz/sdk/timeseries/_grid_frequency.py b/src/frequenz/sdk/timeseries/_grid_frequency.py index 805b6aea2..1b8b87e2c 100644 --- a/src/frequenz/sdk/timeseries/_grid_frequency.py +++ b/src/frequenz/sdk/timeseries/_grid_frequency.py @@ -11,6 +11,8 @@ from frequenz.channels import Receiver, Sender +from frequenz.sdk.microgrid.component import ComponentId + from ..actor import ChannelRegistry from ..microgrid import connection_manager from ..microgrid.component import Component, ComponentCategory, ComponentMetricId @@ -24,7 +26,7 @@ _logger = logging.getLogger(__name__) -def create_request(component_id: int) -> ComponentMetricRequest: +def create_request(component_id: ComponentId) -> ComponentMetricRequest: """Create a request for grid frequency. Args: diff --git a/src/frequenz/sdk/timeseries/battery_pool/_component_metric_fetcher.py b/src/frequenz/sdk/timeseries/battery_pool/_component_metric_fetcher.py index 5086af82f..c2fbc6e37 100644 --- a/src/frequenz/sdk/timeseries/battery_pool/_component_metric_fetcher.py +++ b/src/frequenz/sdk/timeseries/battery_pool/_component_metric_fetcher.py @@ -15,6 +15,8 @@ from frequenz.channels import ChannelClosedError, Receiver +from frequenz.sdk.microgrid.component import ComponentId + from ..._internal._asyncio import AsyncConstructible from ..._internal._constants import MAX_BATTERY_DATA_AGE_SEC from ...actor._data_sourcing.microgrid_api_source import ( @@ -45,7 +47,7 @@ class ComponentMetricFetcher(AsyncConstructible, ABC): @classmethod async def async_new( - cls, component_id: int, metrics: Iterable[ComponentMetricId] + cls, component_id: ComponentId, metrics: Iterable[ComponentMetricId] ) -> Self: """Create an instance of this class. @@ -78,7 +80,7 @@ class LatestMetricsFetcher(ComponentMetricFetcher, Generic[T], ABC): @classmethod async def async_new( cls, - component_id: int, + component_id: ComponentId, metrics: Iterable[ComponentMetricId], ) -> Self: """Create instance of this class. @@ -173,7 +175,7 @@ class LatestBatteryMetricsFetcher(LatestMetricsFetcher[BatteryData]): @classmethod async def async_new( # noqa: DOC502 (ValueError is raised indirectly super.async_new) cls, - component_id: int, + component_id: ComponentId, metrics: Iterable[ComponentMetricId], ) -> LatestBatteryMetricsFetcher: """Create instance of this class. @@ -224,7 +226,7 @@ class LatestInverterMetricsFetcher(LatestMetricsFetcher[InverterData]): @classmethod async def async_new( # noqa: DOC502 (ValueError is raised indirectly by super.async_new) cls, - component_id: int, + component_id: ComponentId, metrics: Iterable[ComponentMetricId], ) -> LatestInverterMetricsFetcher: """Create instance of this class. diff --git a/src/frequenz/sdk/timeseries/battery_pool/_component_metrics.py b/src/frequenz/sdk/timeseries/battery_pool/_component_metrics.py index 2c9b3677c..4f655fec2 100644 --- a/src/frequenz/sdk/timeseries/battery_pool/_component_metrics.py +++ b/src/frequenz/sdk/timeseries/battery_pool/_component_metrics.py @@ -7,7 +7,7 @@ from collections.abc import Mapping from datetime import datetime -from ...microgrid.component import ComponentMetricId +from ...microgrid.component import ComponentId, ComponentMetricId class ComponentMetricsData: @@ -15,7 +15,7 @@ class ComponentMetricsData: def __init__( self, - component_id: int, + component_id: ComponentId, timestamp: datetime, metrics: Mapping[ComponentMetricId, float], ) -> None: diff --git a/src/frequenz/sdk/timeseries/battery_pool/_methods.py b/src/frequenz/sdk/timeseries/battery_pool/_methods.py index a9af643b3..d777cb3d8 100644 --- a/src/frequenz/sdk/timeseries/battery_pool/_methods.py +++ b/src/frequenz/sdk/timeseries/battery_pool/_methods.py @@ -17,6 +17,7 @@ from ...actor.power_distributing._component_managers._battery_manager import ( _get_battery_inverter_mappings, ) +from ...microgrid.component import ComponentId from ._component_metric_fetcher import ( ComponentMetricFetcher, LatestBatteryMetricsFetcher, @@ -178,7 +179,7 @@ async def _create_data_fetchers(self) -> dict[int, ComponentMetricFetcher]: return fetchers def _remove_metric_fetcher( - self, fetchers: dict[int, ComponentMetricFetcher], component_id: int + self, fetchers: dict[int, ComponentMetricFetcher], component_id: ComponentId ) -> None: _logger.error( "Removing component %d from the %s formula.", diff --git a/src/frequenz/sdk/timeseries/ev_charger_pool/_ev_charger_pool.py b/src/frequenz/sdk/timeseries/ev_charger_pool/_ev_charger_pool.py index 2e6e8758f..7092d6e1e 100644 --- a/src/frequenz/sdk/timeseries/ev_charger_pool/_ev_charger_pool.py +++ b/src/frequenz/sdk/timeseries/ev_charger_pool/_ev_charger_pool.py @@ -17,7 +17,7 @@ from ..._internal._asyncio import cancel_and_await from ...actor import ChannelRegistry, ComponentMetricRequest from ...microgrid import connection_manager -from ...microgrid.component import ComponentCategory, ComponentMetricId +from ...microgrid.component import ComponentCategory, ComponentId, ComponentMetricId from .. import Sample, Sample3Phase from .._quantities import Current, Power, Quantity from ..formula_engine import FormulaEngine, FormulaEngine3Phase @@ -41,7 +41,7 @@ class EVChargerPoolError(Exception): class EVChargerData: """Data for an EV Charger, including the 3-phase current and the component state.""" - component_id: int + component_id: ComponentId """The component ID of the EV Charger.""" current: Sample3Phase[Current] @@ -185,7 +185,7 @@ def power(self) -> FormulaEngine[Power]: assert isinstance(engine, FormulaEngine) return engine - def component_data(self, component_id: int) -> Receiver[EVChargerData]: + def component_data(self, component_id: ComponentId) -> Receiver[EVChargerData]: """Stream 3-phase current values and state of an EV Charger. Args: @@ -213,7 +213,7 @@ def component_data(self, component_id: int) -> Receiver[EVChargerData]: return output_chan.new_receiver() - async def set_bounds(self, component_id: int, max_current: Current) -> None: + async def set_bounds(self, component_id: ComponentId, max_current: Current) -> None: """Send given max current bound for the given EV Charger to the microgrid API. Bounds are used to limit the max current drawn by an EV, although the exact @@ -253,7 +253,7 @@ async def stop(self) -> None: await cancel_and_await(task) async def _get_current_streams( - self, component_id: int + self, component_id: ComponentId ) -> tuple[ Receiver[Sample[Quantity]], Receiver[Sample[Quantity]], @@ -289,7 +289,7 @@ async def resampler_subscribe( async def _stream_component_data( self, - component_id: int, + component_id: ComponentId, sender: Sender[EVChargerData], ) -> None: """Stream 3-phase current values and state of an EV Charger. diff --git a/src/frequenz/sdk/timeseries/ev_charger_pool/_set_current_bounds.py b/src/frequenz/sdk/timeseries/ev_charger_pool/_set_current_bounds.py index e79c849ca..7603374ee 100644 --- a/src/frequenz/sdk/timeseries/ev_charger_pool/_set_current_bounds.py +++ b/src/frequenz/sdk/timeseries/ev_charger_pool/_set_current_bounds.py @@ -13,7 +13,7 @@ from ..._internal._asyncio import cancel_and_await from ...microgrid import connection_manager -from ...microgrid.component import ComponentCategory +from ...microgrid.component import ComponentCategory, ComponentId _logger = logging.getLogger(__name__) @@ -22,7 +22,7 @@ class ComponentCurrentLimit: """A current limit, to be sent to the EV Charger.""" - component_id: int + component_id: ComponentId """The component ID of the EV Charger.""" max_amps: float @@ -53,7 +53,7 @@ def __init__(self, repeat_interval: timedelta) -> None: self._bounds_rx = self._bounds_chan.new_receiver() self._bounds_tx = self._bounds_chan.new_sender() - async def set(self, component_id: int, max_amps: float) -> None: + async def set(self, component_id: ComponentId, max_amps: float) -> None: """Send the given current limit to the microgrid for the given component id. Args: diff --git a/src/frequenz/sdk/timeseries/ev_charger_pool/_state_tracker.py b/src/frequenz/sdk/timeseries/ev_charger_pool/_state_tracker.py index abbf6c629..51636f3ae 100644 --- a/src/frequenz/sdk/timeseries/ev_charger_pool/_state_tracker.py +++ b/src/frequenz/sdk/timeseries/ev_charger_pool/_state_tracker.py @@ -11,6 +11,8 @@ from frequenz.channels import Receiver from frequenz.channels.util import Merge +from frequenz.sdk.microgrid.component import ComponentId + from ... import microgrid from ..._internal._asyncio import cancel_and_await from ...microgrid.component import ( @@ -95,7 +97,7 @@ def __init__(self, component_ids: set[int]) -> None: component_id: EVChargerState.MISSING for component_id in component_ids } - def get(self, component_id: int) -> EVChargerState: + def get(self, component_id: ComponentId) -> EVChargerState: """Return the current state of the EV Charger with the given component ID. Args: diff --git a/src/frequenz/sdk/timeseries/formula_engine/_resampled_formula_builder.py b/src/frequenz/sdk/timeseries/formula_engine/_resampled_formula_builder.py index 30f97e036..ebde957d4 100644 --- a/src/frequenz/sdk/timeseries/formula_engine/_resampled_formula_builder.py +++ b/src/frequenz/sdk/timeseries/formula_engine/_resampled_formula_builder.py @@ -10,7 +10,7 @@ from frequenz.channels import Receiver, Sender from ...actor import ChannelRegistry, ComponentMetricRequest -from ...microgrid.component import ComponentMetricId +from ...microgrid.component import ComponentId, ComponentMetricId from .. import Sample from .._quantities import QuantityT from ._formula_engine import FormulaBuilder, FormulaEngine @@ -52,7 +52,7 @@ def __init__( # pylint: disable=too-many-arguments super().__init__(formula_name, create_method) # type: ignore[arg-type] def _get_resampled_receiver( - self, component_id: int, metric_id: ComponentMetricId + self, component_id: ComponentId, metric_id: ComponentMetricId ) -> Receiver[Sample[QuantityT]]: """Get a receiver with the resampled data for the given component id. @@ -73,7 +73,7 @@ async def subscribe(self) -> None: await self._resampler_subscription_sender.send(request) def push_component_metric( - self, component_id: int, *, nones_are_zeros: bool + self, component_id: ComponentId, *, nones_are_zeros: bool ) -> None: """Push a resampled component metric stream to the formula engine.