Skip to content

Commit 3c9ba53

Browse files
committed
Add a propose_power method to EVChargerPool
Signed-off-by: Sahas Subramanian <[email protected]>
1 parent d75b37a commit 3c9ba53

File tree

3 files changed

+70
-1
lines changed

3 files changed

+70
-1
lines changed

src/frequenz/sdk/microgrid/_data_pipeline.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,9 @@ def ev_charger_pool(
202202
status_receiver=self._ev_power_wrapper.status_channel.new_receiver(
203203
maxsize=1
204204
),
205+
power_manager_requests_sender=(
206+
self._ev_power_wrapper.proposal_channel.new_sender()
207+
),
205208
power_manager_bounds_subs_sender=(
206209
self._ev_power_wrapper.bounds_subscription_channel.new_sender()
207210
),

src/frequenz/sdk/timeseries/ev_charger_pool/_ev_charger_pool.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
import typing
99
import uuid
1010
from collections import abc
11+
from datetime import timedelta
1112

1213
from ..._internal._channels import ReceiverFetcher
1314
from ...actor import _power_managing
15+
from ...timeseries import Bounds
1416
from .._base_types import SystemBounds
1517
from .._quantities import Current, Power
1618
from ..formula_engine import FormulaEngine, FormulaEngine3Phase
@@ -66,6 +68,66 @@ def __init__( # pylint: disable=too-many-arguments
6668
self._source_id = unique_id if name is None else f"{name}-{unique_id}"
6769
self._priority = priority
6870

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

src/frequenz/sdk/timeseries/ev_charger_pool/_ev_charger_pool_reference_store.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from frequenz.channels import Broadcast, Receiver, Sender
1212

1313
from ...actor import ChannelRegistry, ComponentMetricRequest
14-
from ...actor._power_managing import ReportRequest
14+
from ...actor._power_managing._base_classes import Proposal, ReportRequest
1515
from ...actor.power_distributing import ComponentPoolStatus
1616
from ...microgrid import connection_manager
1717
from ...microgrid.component import ComponentCategory
@@ -38,6 +38,7 @@ def __init__( # pylint: disable=too-many-arguments
3838
channel_registry: ChannelRegistry,
3939
resampler_subscription_sender: Sender[ComponentMetricRequest],
4040
status_receiver: Receiver[ComponentPoolStatus],
41+
power_manager_requests_sender: Sender[Proposal],
4142
power_manager_bounds_subs_sender: Sender[ReportRequest],
4243
component_ids: abc.Set[int] | None = None,
4344
):
@@ -50,6 +51,8 @@ def __init__( # pylint: disable=too-many-arguments
5051
resampling actor.
5152
status_receiver: A receiver that streams the status of the EV Chargers in
5253
the pool.
54+
power_manager_requests_sender: A Channel sender for sending power
55+
requests to the power managing actor.
5356
power_manager_bounds_subs_sender: A Channel sender for sending power bounds
5457
subscription requests to the power managing actor.
5558
component_ids: An optional list of component_ids belonging to this pool. If
@@ -59,6 +62,7 @@ def __init__( # pylint: disable=too-many-arguments
5962
self.channel_registry = channel_registry
6063
self.resampler_subscription_sender = resampler_subscription_sender
6164
self.status_receiver = status_receiver
65+
self.power_manager_requests_sender = power_manager_requests_sender
6266
self.power_manager_bounds_subs_sender = power_manager_bounds_subs_sender
6367

6468
if component_ids is not None:

0 commit comments

Comments
 (0)