Skip to content

Commit cd54430

Browse files
committed
Add BatteryPool methods for communicating with the PowerDistributor
1. set_power :: send a PSC power request to the power distributor. 2. charge :: send a charge power (+ve) to the power distributor. 3. discharge :: send a discharge power (+ve) to the power distributor. 4. power_distribution_results :: get a receiver to power distributor responses. (PSC = Passive Sign Convention) Signed-off-by: Sahas Subramanian <[email protected]>
1 parent 8203984 commit cd54430

File tree

1 file changed

+135
-0
lines changed

1 file changed

+135
-0
lines changed

src/frequenz/sdk/timeseries/battery_pool/battery_pool.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@
1313

1414
from frequenz.channels import Receiver, Sender
1515

16+
from frequenz.sdk.actor.power_distributing.result import Result
17+
1618
from ..._internal._asyncio import cancel_and_await
1719
from ...actor import ChannelRegistry, ComponentMetricRequest
20+
from ...actor.power_distributing import Request
1821
from ...actor.power_distributing._battery_pool_status import BatteryStatus
1922
from ...microgrid import connection_manager
2023
from ...microgrid.component import ComponentCategory
@@ -41,6 +44,7 @@ def __init__( # pylint: disable=too-many-arguments
4144
channel_registry: ChannelRegistry,
4245
resampler_subscription_sender: Sender[ComponentMetricRequest],
4346
batteries_status_receiver: Receiver[BatteryStatus],
47+
power_distributing_sender: Sender[Request],
4448
min_update_interval: timedelta,
4549
batteries_id: Set[int] | None = None,
4650
) -> None:
@@ -57,6 +61,8 @@ def __init__( # pylint: disable=too-many-arguments
5761
It should send information when any battery changed status.
5862
Battery status should include status of the inverter adjacent to this
5963
battery.
64+
power_distributing_sender: A Channel sender for sending power requests to
65+
the power distributing actor.
6066
min_update_interval: Some metrics in BatteryPool are send only when they
6167
change. For these metrics min_update_interval is the minimum time
6268
interval between the following messages.
@@ -84,15 +90,144 @@ def __init__( # pylint: disable=too-many-arguments
8490
)
8591

8692
self._min_update_interval = min_update_interval
93+
94+
self._power_distributing_sender = power_distributing_sender
8795
self._active_methods: dict[str, MetricAggregator[Any]] = {}
8896

8997
self._namespace: str = f"battery-pool-{self._batteries}-{uuid.uuid4()}"
98+
self._power_distributing_namespace: str = f"power-distributor-{self._namespace}"
99+
self._channel_registry: ChannelRegistry = channel_registry
90100
self._formula_pool: FormulaEnginePool = FormulaEnginePool(
91101
self._namespace,
92102
channel_registry,
93103
resampler_subscription_sender,
94104
)
95105

106+
async def set_power(
107+
self,
108+
power: float,
109+
*,
110+
adjust_power: bool = True,
111+
request_timeout: timedelta = timedelta(seconds=5.0),
112+
include_broken_batteries: bool = False,
113+
) -> None:
114+
"""Set the given power for the batteries in the pool.
115+
116+
Power values need to follow the Passive Sign Convention (PSC). That is, positive
117+
values indicate charge power and negative values indicate discharge power.
118+
119+
When not using the Passive Sign Convention, the `charge` and `discharge` methods
120+
might be more convenient.
121+
122+
Args:
123+
power: The power to set for the batteries in the pool.
124+
adjust_power: If True, the power will be adjusted to fit the power bounds,
125+
if necessary. If False, then power requests outside the bounds will be
126+
rejected.
127+
request_timeout: The timeout for the request.
128+
include_broken_batteries: if True, the power will be set for all batteries
129+
in the pool, including the broken ones. If False, then the power will be
130+
set only for the working batteries. This is not a guarantee that the
131+
power will be set for all working batteries, as the microgrid API may
132+
still reject the request.
133+
"""
134+
await self._power_distributing_sender.send(
135+
Request(
136+
namespace=self._power_distributing_namespace,
137+
power=power,
138+
batteries=self._batteries,
139+
adjust_power=adjust_power,
140+
request_timeout_sec=request_timeout.total_seconds(),
141+
include_broken_batteries=include_broken_batteries,
142+
)
143+
)
144+
145+
async def charge(
146+
self,
147+
power: float,
148+
*,
149+
adjust_power: bool = True,
150+
request_timeout: timedelta = timedelta(seconds=5.0),
151+
include_broken_batteries: bool = False,
152+
) -> None:
153+
"""Set the given charge power for the batteries in the pool.
154+
155+
Power values need to be positive values, indicating charge power.
156+
157+
When using the Passive Sign Convention, the `set_power` method might be more
158+
convenient.
159+
160+
Args:
161+
power: Unsigned charge power to set for the batteries in the pool.
162+
adjust_power: If True, the power will be adjusted to fit the power bounds,
163+
if necessary. If False, then power requests outside the bounds will be
164+
rejected.
165+
request_timeout: The timeout for the request.
166+
include_broken_batteries: if True, the power will be set for all batteries
167+
in the pool, including the broken ones. If False, then the power will be
168+
set only for the working batteries. This is not a guarantee that the
169+
power will be set for all working batteries, as the microgrid API may
170+
still reject the request.
171+
172+
Raises:
173+
ValueError: If the given power is negative.
174+
"""
175+
if power < 0.0:
176+
raise ValueError("Charge power must be positive.")
177+
await self.set_power(
178+
power,
179+
adjust_power=adjust_power,
180+
request_timeout=request_timeout,
181+
include_broken_batteries=include_broken_batteries,
182+
)
183+
184+
async def discharge(
185+
self,
186+
power: float,
187+
*,
188+
adjust_power: bool = True,
189+
request_timeout: timedelta = timedelta(seconds=5.0),
190+
include_broken_batteries: bool = False,
191+
) -> None:
192+
"""Set the given discharge power for the batteries in the pool.
193+
194+
Power values need to be positive values, indicating discharge power.
195+
196+
When using the Passive Sign Convention, the `set_power` method might be more
197+
convenient.
198+
199+
Args:
200+
power: Unsigned discharge power to set for the batteries in the pool.
201+
adjust_power: If True, the power will be adjusted to fit the power bounds,
202+
if necessary. If False, then power requests outside the bounds will be
203+
rejected.
204+
request_timeout: The timeout for the request.
205+
include_broken_batteries: if True, the power will be set for all batteries
206+
in the pool, including the broken ones. If False, then the power will be
207+
set only for the working batteries. This is not a guarantee that the
208+
power will be set for all working batteries, as the microgrid API may
209+
still reject the request.
210+
211+
Raises:
212+
ValueError: If the given power is negative.
213+
"""
214+
if power < 0.0:
215+
raise ValueError("Discharge power must be positive.")
216+
await self.set_power(
217+
-power,
218+
adjust_power=adjust_power,
219+
request_timeout=request_timeout,
220+
include_broken_batteries=include_broken_batteries,
221+
)
222+
223+
def power_distribution_results(self) -> Receiver[Result]:
224+
"""Return a receiver for the power distribution results.
225+
226+
Returns:
227+
A receiver for the power distribution results.
228+
"""
229+
return self._channel_registry.new_receiver(self._power_distributing_namespace)
230+
96231
@property
97232
def battery_ids(self) -> Set[int]:
98233
"""Return ids of the batteries in the pool.

0 commit comments

Comments
 (0)