1313
1414from frequenz .channels import Receiver , Sender
1515
16+ from frequenz .sdk .actor .power_distributing .result import Result
17+
1618from ..._internal ._asyncio import cancel_and_await
1719from ...actor import ChannelRegistry , ComponentMetricRequest
20+ from ...actor .power_distributing import Request
1821from ...actor .power_distributing ._battery_pool_status import BatteryStatus
1922from ...microgrid import connection_manager
2023from ...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