Skip to content

Commit 2ad0473

Browse files
committed
Support sending power proposals to PowerManager from the PVPool
This is done through the new `PVPool.propose_power` method. This commit also adds a `component_ids` property, that exposes the IDs of the PV inverters managed by this PVPool. Signed-off-by: Sahas Subramanian <[email protected]>
1 parent 5691f78 commit 2ad0473

File tree

1 file changed

+77
-0
lines changed

1 file changed

+77
-0
lines changed

src/frequenz/sdk/timeseries/pv_pool/_pv_pool.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@
66
import asyncio
77
import typing
88
import uuid
9+
from collections import abc
10+
from datetime import timedelta
911

1012
from ..._internal._channels import ReceiverFetcher
1113
from ...actor import _power_managing
14+
from ...timeseries import Bounds
1215
from .._base_types import SystemBounds
16+
from .._quantities import Power
1317
from ._pv_pool_reference_store import PVPoolReferenceStore
1418
from ._result_types import PVPoolReport
1519

@@ -54,6 +58,79 @@ def __init__( # pylint: disable=too-many-arguments
5458
self._source_id = str(unique_id) if name is None else f"{name}-{unique_id}"
5559
self._priority = priority
5660

61+
async def propose_power(
62+
self,
63+
power: Power | None,
64+
*,
65+
request_timeout: timedelta = timedelta(seconds=5.0),
66+
bounds: Bounds[Power | None] = Bounds(None, None),
67+
) -> None:
68+
"""Send a proposal to the power manager for the pool's set of PV inverters.
69+
70+
This proposal is for the maximum power that can be set for the PV inverters in
71+
the pool. The actual production might be lower.
72+
73+
Power values need to follow the Passive Sign Convention (PSC). That is, positive
74+
values indicate charge power and negative values indicate discharge power.
75+
Only discharge powers are allowed for PV inverters.
76+
77+
If the same PV inverters are shared by multiple actors, the power manager will
78+
consider the priority of the actors, the bounds they set, and their preferred
79+
power, when calculating the target power for the PV inverters.
80+
81+
The preferred power of lower priority actors will take precedence as long as
82+
they respect the bounds set by higher priority actors. If lower priority actors
83+
request power values outside of the bounds set by higher priority actors, the
84+
target power will be the closest value to the preferred power that is within the
85+
bounds.
86+
87+
When there are no other actors trying to use the same PV inverters, the actor's
88+
preferred power would be set as the target power, as long as it falls within the
89+
system power bounds for the PV inverters.
90+
91+
The result of the request can be accessed using the receiver returned from the
92+
[`power_status`][frequenz.sdk.timeseries.pv_pool.PVPool.power_status]
93+
method, which also streams the bounds that an actor should comply with, based on
94+
its priority.
95+
96+
Args:
97+
power: The power to propose for the PV inverters in the pool. If `None`,
98+
this proposal will not have any effect on the target power, unless
99+
bounds are specified. If both are `None`, it is equivalent to not
100+
having a proposal or withdrawing a previous one.
101+
request_timeout: The timeout for the request.
102+
bounds: The power bounds for the proposal. These bounds will apply to
103+
actors with a lower priority, and can be overridden by bounds from
104+
actors with a higher priority. If None, the power bounds will be set to
105+
the maximum power of the batteries in the pool. This is currently and
106+
experimental feature.
107+
108+
Raises:
109+
PVPoolError: If a charge power for PV inverters is requested.
110+
"""
111+
if power is not None and power > Power.zero():
112+
raise PVPoolError("Charge powers for PV inverters is not supported.")
113+
await self._pv_pool_ref.power_manager_requests_sender.send(
114+
_power_managing.Proposal(
115+
source_id=self._source_id,
116+
preferred_power=power,
117+
bounds=bounds,
118+
component_ids=self._pv_pool_ref.component_ids,
119+
priority=self._priority,
120+
creation_time=asyncio.get_running_loop().time(),
121+
request_timeout=request_timeout,
122+
)
123+
)
124+
125+
@property
126+
def component_ids(self) -> abc.Set[int]:
127+
"""Return component IDs of all PV inverters managed by this PVPool.
128+
129+
Returns:
130+
Set of managed component IDs.
131+
"""
132+
return self._pv_pool_ref.component_ids
133+
57134
@property
58135
def power_status(self) -> ReceiverFetcher[PVPoolReport]:
59136
"""Get a receiver to receive new power status reports when they change.

0 commit comments

Comments
 (0)