Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8da7645
Add a component status tracker for PV inverters
shsms Apr 3, 2024
e8b4705
Pass ComponentType as arguments to PowerManager and PowerDistributor
shsms Apr 4, 2024
ed85d8a
Add a `stop` method for `LatestValueCache`
shsms Apr 4, 2024
6be3ac4
Add a `PVManager` implementation
shsms Apr 4, 2024
1a52351
Update `PowerDistributor` to support PV inverters
shsms Apr 4, 2024
6633e9a
Implement a `SystemBoundsTracker` for PV inverters
shsms Apr 4, 2024
446187b
Implement a PV pool with a `_system_power_bounds` method
shsms Apr 4, 2024
d1021d3
Expose the `PVPool` through `microgrid.pv_pool()`
shsms Apr 4, 2024
5d62396
Support PV inverters in the `PowerManager`
shsms Apr 4, 2024
bb333e0
Implement power_status reporting from the PV pool
shsms Apr 5, 2024
f091eaf
Support sending power proposals to `PowerManager` from the `PVPool`
shsms Apr 5, 2024
141f896
Move the PV `power` formula from `logical_meter` to `PVPool`
shsms Apr 5, 2024
a039724
Add a `stop` method to `BatteryPool`
shsms Apr 5, 2024
5093900
Add tests for PVPool control methods
shsms Apr 9, 2024
79a725c
Add section for PV arrays in microgrid concepts documentation
shsms Apr 9, 2024
c647ed0
Update section for EV chargers in microgrid concepts documentation
shsms Apr 9, 2024
0411b7d
Update release notes
shsms Apr 9, 2024
09c8414
Make `component_category` and `component_type` parameters kw-only
shsms Apr 10, 2024
0f022ab
Initialize super-class as the first step
shsms Apr 10, 2024
22158fe
Fix docstring
shsms Apr 10, 2024
4f9883d
Broaden exception handling when setting power through microgrid API
shsms Apr 10, 2024
30f19c0
Remove `*Pool` constructor related docs from the class docs
shsms Apr 10, 2024
6e1400d
Fix exception handling bugs in EV and PV managers
shsms Apr 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@

- New `propose_power` and `power_status` methods have been added to the `EVChargerPool` similar to the `BatteryPool`. These method interface with the `PowerManager` and `PowerDistributor`, which currently uses a first-come-first-serve algorithm to distribute power to EVs.

- PV Power is now available from `microgrid.pv_pool().power`, and no longer from `microgrid.logical_meter().pv_power`.

## New Features

- Warning messages are logged when multiple instances of `*Pool`s are created for the same set of batteries, with the same priority values.

- A PV Pool, with `propose_power`, `power_status` and `power` methods similar to Battery and EV Pools.

## Bug Fixes

- A bug was fixed where the grid fuse was not created properly and would end up with a `max_current` with type `float` instead of `Current`.
Expand Down
1 change: 1 addition & 0 deletions benchmarks/power_distribution/power_distributor.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ async def run_test( # pylint: disable=too-many-locals
power_result_channel = Broadcast[Result](name="power-result")
async with PowerDistributingActor(
component_category=ComponentCategory.BATTERY,
component_type=None,
requests_receiver=power_request_channel.new_receiver(),
results_sender=power_result_channel.new_sender(),
component_pool_status_sender=battery_status_channel.new_sender(),
Expand Down
6 changes: 6 additions & 0 deletions src/frequenz/sdk/_internal/_channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

from frequenz.channels import Receiver

from ._asyncio import cancel_and_await

T_co = typing.TypeVar("T_co", covariant=True)


Expand Down Expand Up @@ -72,3 +74,7 @@ def has_value(self) -> bool:
async def _run(self) -> None:
async for value in self._receiver:
self._latest_value = value

async def stop(self) -> None:
"""Stop the cache."""
await cancel_and_await(self._task)
24 changes: 20 additions & 4 deletions src/frequenz/sdk/actor/_power_managing/_power_managing_actor.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from frequenz.channels import Receiver, Sender, select, selected_from
from frequenz.channels.timer import SkipMissedAndDrift, Timer
from frequenz.client.microgrid import ComponentCategory
from frequenz.client.microgrid import ComponentCategory, ComponentType, InverterType
from typing_extensions import override

from ...timeseries._base_types import SystemBounds
Expand All @@ -32,28 +32,37 @@ class PowerManagingActor(Actor):

def __init__( # pylint: disable=too-many-arguments
self,
component_category: ComponentCategory,
proposals_receiver: Receiver[Proposal],
bounds_subscription_receiver: Receiver[ReportRequest],
power_distributing_requests_sender: Sender[power_distributing.Request],
power_distributing_results_receiver: Receiver[power_distributing.Result],
channel_registry: ChannelRegistry,
*,
component_category: ComponentCategory,
component_type: ComponentType | None = None,
# arguments to actors need to serializable, so we pass an enum for the algorithm
# instead of an instance of the algorithm.
algorithm: Algorithm = Algorithm.MATRYOSHKA,
):
"""Create a new instance of the power manager.

Args:
component_category: The category of the component this power manager
instance is going to support.
proposals_receiver: The receiver for proposals.
bounds_subscription_receiver: The receiver for bounds subscriptions.
power_distributing_requests_sender: The sender for power distribution
requests.
power_distributing_results_receiver: The receiver for power distribution
results.
channel_registry: The channel registry.
component_category: The category of the component this power manager
instance is going to support.
component_type: The type of the component of the given category that this
actor is responsible for. This is used only when the component category
is not enough to uniquely identify the component. For example, when the
category is `ComponentCategory.INVERTER`, the type is needed to identify
the inverter as a solar inverter or a battery inverter. This can be
`None` when the component category is enough to uniquely identify the
component.
algorithm: The power management algorithm to use.

Raises:
Expand All @@ -65,6 +74,7 @@ def __init__( # pylint: disable=too-many-arguments
)

self._component_category = component_category
self._component_type = component_type
self._bounds_subscription_receiver = bounds_subscription_receiver
self._power_distributing_requests_sender = power_distributing_requests_sender
self._power_distributing_results_receiver = power_distributing_results_receiver
Expand Down Expand Up @@ -141,6 +151,12 @@ def _add_bounds_tracker(self, component_ids: frozenset[int]) -> None:
elif self._component_category is ComponentCategory.EV_CHARGER:
ev_charger_pool = microgrid.ev_charger_pool(component_ids)
bounds_receiver = ev_charger_pool._system_power_bounds.new_receiver()
elif (
self._component_category is ComponentCategory.INVERTER
and self._component_type is InverterType.SOLAR
):
pv_pool = microgrid.pv_pool(component_ids)
bounds_receiver = pv_pool._system_power_bounds.new_receiver()
# pylint: enable=protected-access
else:
err = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
from ._battery_manager import BatteryManager
from ._component_manager import ComponentManager
from ._ev_charger_manager import EVChargerManager
from ._pv_inverter_manager import PVManager

__all__ = [
"BatteryManager",
"ComponentManager",
"EVChargerManager",
"PVManager",
]
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,10 @@ async def start(self) -> None:
@override
async def stop(self) -> None:
"""Stop the battery data manager."""
for bat_cache in self._battery_caches.values():
await bat_cache.stop()
for inv_cache in self._inverter_caches.values():
await inv_cache.stop()
await self._component_pool_status_tracker.stop()

@override
Expand Down Expand Up @@ -668,6 +672,13 @@ def _parse_result(
battery_ids,
request_timeout.total_seconds(),
)
except Exception: # pylint: disable=broad-except
failed_power += distribution[inverter_id]
failed_batteries = failed_batteries.union(battery_ids)
_logger.exception(
"Unknown error while setting power to batteries: %s",
battery_ids,
)

return failed_power, failed_batteries

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ async def distribute_power(self, request: Request) -> None:
@override
async def stop(self) -> None:
"""Stop the ev charger manager."""
await self._voltage_cache.stop()
await self._component_pool_status_tracker.stop()

def _get_ev_charger_ids(self) -> collections.abc.Set[int]:
Expand Down Expand Up @@ -330,16 +331,21 @@ async def _set_api_power(
succeeded_components.add(component_id)

match task.exception():
case asyncio.CancelledError:
case asyncio.CancelledError():
_logger.warning(
"Timeout while setting power to EV charger %s", component_id
)
case grpc.aio.AioRpcError as err:
case grpc.aio.AioRpcError() as err:
_logger.warning(
"Error while setting power to EV charger %s: %s",
component_id,
err,
)
case Exception():
_logger.exception(
"Unknown error while setting power to EV charger: %s",
component_id,
)
if failed_components:
return PartialFailure(
failed_components=failed_components,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# License: MIT
# Copyright © 2024 Frequenz Energy-as-a-Service GmbH

"""Manage PV inverters for the power distributor."""

from ._pv_inverter_manager import PVManager

__all__ = ["PVManager"]
Loading