Skip to content

Commit be2f093

Browse files
committed
Implement a PV pool with a _system_power_bounds method
These system bounds trackers need to be moved out of the `*Pool`s, but exactly how, is yet to be decided. Until then, I'm following the same pattern here as in the other `*Pool`s. This commit adds a `_system_power_bounds` method only because that's all that's needed for the PowerManager to support PV inverters. Other methods will be added later. Signed-off-by: Sahas Subramanian <[email protected]>
1 parent e3514cb commit be2f093

File tree

2 files changed

+159
-0
lines changed

2 files changed

+159
-0
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# License: MIT
2+
# Copyright © 2024 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Interactions with pools of PV inverters."""
5+
6+
import uuid
7+
8+
from ..._internal._channels import ReceiverFetcher
9+
from .._base_types import SystemBounds
10+
from ._pv_pool_reference_store import PVPoolReferenceStore
11+
12+
13+
class PVPoolError(Exception):
14+
"""An error that occurred in any of the PVPool methods."""
15+
16+
17+
class PVPool:
18+
"""An interface for interaction with pools of PV inverters.
19+
20+
!!! note
21+
`PVPool` instances are not meant to be created directly by users. Use the
22+
[`microgrid.pv_pool`][frequenz.sdk.microgrid.pv_pool] method for creating
23+
`PVPool` instances.
24+
25+
Provides:
26+
- Aggregate [`power`][frequenz.sdk.timeseries.pv_pool.PVPool.power]
27+
measurements of the PV inverters in the pool.
28+
"""
29+
30+
def __init__( # pylint: disable=too-many-arguments
31+
self,
32+
pv_pool_ref: PVPoolReferenceStore,
33+
name: str | None,
34+
priority: int,
35+
) -> None:
36+
"""Initialize the instance.
37+
38+
!!! note
39+
`PVPool` instances are not meant to be created directly by users. Use the
40+
[`microgrid.pv_pool`][frequenz.sdk.microgrid.pv_pool] method for creating
41+
`PVPool` instances.
42+
43+
Args:
44+
pv_pool_ref: The reference store for the PV pool.
45+
name: The name of the PV pool.
46+
priority: The priority of the PV pool.
47+
"""
48+
self._pv_pool_ref = pv_pool_ref
49+
unique_id = uuid.uuid4()
50+
self._source_id = unique_id if name is None else f"{name}-{unique_id}"
51+
self._priority = priority
52+
53+
@property
54+
def _system_power_bounds(self) -> ReceiverFetcher[SystemBounds]:
55+
"""Return a receiver fetcher for the system power bounds."""
56+
return self._pv_pool_ref.bounds_channel
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# License: MIT
2+
# Copyright © 2024 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Manages shared state/tasks for a set of PV inverters."""
5+
6+
7+
import asyncio
8+
import uuid
9+
from collections import abc
10+
11+
from frequenz.channels import Broadcast, Receiver, Sender
12+
from frequenz.client.microgrid import ComponentCategory, InverterType
13+
14+
from ...actor import ChannelRegistry, ComponentMetricRequest
15+
from ...actor._power_managing._base_classes import Proposal, ReportRequest
16+
from ...actor.power_distributing import ComponentPoolStatus
17+
from ...microgrid import connection_manager
18+
from .._base_types import SystemBounds
19+
from ..formula_engine._formula_engine_pool import FormulaEnginePool
20+
from ._system_bounds_tracker import PVSystemBoundsTracker
21+
22+
23+
class PVPoolReferenceStore:
24+
"""A class for maintaining the shared state/tasks for a set of pool of PV inverters.
25+
26+
This includes ownership of
27+
- the formula engine pool and metric calculators.
28+
- the tasks for calculating system bounds for the PV inverters.
29+
30+
These are independent of the priority of the actors and can be shared between
31+
multiple users of the same set of PV inverters.
32+
33+
They are exposed through the PVPool class.
34+
"""
35+
36+
def __init__( # pylint: disable=too-many-arguments
37+
self,
38+
channel_registry: ChannelRegistry,
39+
resampler_subscription_sender: Sender[ComponentMetricRequest],
40+
status_receiver: Receiver[ComponentPoolStatus],
41+
power_manager_requests_sender: Sender[Proposal],
42+
power_manager_bounds_subs_sender: Sender[ReportRequest],
43+
component_ids: abc.Set[int] | None = None,
44+
):
45+
"""Create an instance of the class.
46+
47+
Args:
48+
channel_registry: A channel registry instance shared with the resampling
49+
actor.
50+
resampler_subscription_sender: A sender for sending metric requests to the
51+
resampling actor.
52+
status_receiver: A receiver that streams the status of the PV inverters in
53+
the pool.
54+
power_manager_requests_sender: A Channel sender for sending power
55+
requests to the power managing actor.
56+
power_manager_bounds_subs_sender: A Channel sender for sending power bounds
57+
subscription requests to the power managing actor.
58+
component_ids: An optional list of component_ids belonging to this pool. If
59+
not specified, IDs of all PV inverters in the microgrid will be fetched
60+
from the component graph.
61+
"""
62+
self.channel_registry = channel_registry
63+
self.resampler_subscription_sender = resampler_subscription_sender
64+
self.status_receiver = status_receiver
65+
self.power_manager_requests_sender = power_manager_requests_sender
66+
self.power_manager_bounds_subs_sender = power_manager_bounds_subs_sender
67+
68+
if component_ids is not None:
69+
self.component_ids: frozenset[int] = frozenset(component_ids)
70+
else:
71+
graph = connection_manager.get().component_graph
72+
self.component_ids = frozenset(
73+
{
74+
inv.component_id
75+
for inv in graph.components(
76+
component_categories={ComponentCategory.INVERTER}
77+
)
78+
if inv.type == InverterType.SOLAR
79+
}
80+
)
81+
82+
self.power_bounds_subs: dict[str, asyncio.Task[None]] = {}
83+
84+
self.namespace: str = f"pv-pool-{uuid.uuid4()}"
85+
self.formula_pool = FormulaEnginePool(
86+
self.namespace,
87+
self.channel_registry,
88+
self.resampler_subscription_sender,
89+
)
90+
self.bounds_channel: Broadcast[SystemBounds] = Broadcast(
91+
name=f"System Bounds for PV inverters: {component_ids}"
92+
)
93+
self.bounds_tracker: PVSystemBoundsTracker = PVSystemBoundsTracker(
94+
self.component_ids,
95+
self.status_receiver,
96+
self.bounds_channel.new_sender(),
97+
)
98+
self.bounds_tracker.start()
99+
100+
async def stop(self) -> None:
101+
"""Stop all tasks and channels owned by the EVChargerPool."""
102+
await self.formula_pool.stop()
103+
await self.bounds_tracker.stop()

0 commit comments

Comments
 (0)