diff --git a/src/frequenz/sdk/actor/power_distributing/_component_managers/_ev_charger_manager/_ev_charger_manager.py b/src/frequenz/sdk/actor/power_distributing/_component_managers/_ev_charger_manager/_ev_charger_manager.py index d7e95637e..9fa1c75a0 100644 --- a/src/frequenz/sdk/actor/power_distributing/_component_managers/_ev_charger_manager/_ev_charger_manager.py +++ b/src/frequenz/sdk/actor/power_distributing/_component_managers/_ev_charger_manager/_ev_charger_manager.py @@ -10,7 +10,6 @@ import grpc from frequenz.channels import Broadcast, Sender, merge, select, selected_from -from frequenz.channels.timer import SkipMissedAndDrift, Timer from frequenz.client.microgrid import ApiClient, ComponentCategory, EVChargerData from typing_extensions import override @@ -20,8 +19,6 @@ from ....._internal._math import is_close_to_zero from .....timeseries import Power, Sample3Phase, Voltage from ..._component_pool_status_tracker import ComponentPoolStatusTracker - -# from .._component_pool_status_tracker import ComponentPoolStatusTracker from ..._component_status import ComponentPoolStatus, EVChargerStatusTracker from ...request import Request from ...result import PartialFailure, Result, Success @@ -32,7 +29,6 @@ _logger = logging.getLogger(__name__) _DEFAULT_API_REQUEST_TIMEOUT = timedelta(seconds=5.0) -_TGT_POWER_RESEND_INTERVAL = timedelta(seconds=5.0) class EVChargerManager(ComponentManager): @@ -79,7 +75,9 @@ def component_ids(self) -> collections.abc.Set[int]: @override async def start(self) -> None: """Start the ev charger data manager.""" - self._task = asyncio.create_task(self._run_forever()) + # Need to start a task only if there are EV chargers in the component graph. + if self._ev_charger_ids: + self._task = asyncio.create_task(self._run_forever()) @override async def distribute_power(self, request: Request) -> None: @@ -88,7 +86,8 @@ async def distribute_power(self, request: Request) -> None: Args: request: Request to get the distribution for. """ - await self._target_power_tx.send(request) + if self._ev_charger_ids: + await self._target_power_tx.send(request) @override async def stop(self) -> None: @@ -222,10 +221,8 @@ async def _run(self) -> None: # pylint: disable=too-many-locals ) target_power_rx = self._target_power_channel.new_receiver() api_request_timeout = _DEFAULT_API_REQUEST_TIMEOUT - resend_timer = Timer(_TGT_POWER_RESEND_INTERVAL, SkipMissedAndDrift()) latest_target_powers: dict[int, Power] = {} - async for selected in select(ev_charger_data_rx, target_power_rx, resend_timer): - resending = False + async for selected in select(ev_charger_data_rx, target_power_rx): target_power_changes = {} now = datetime.now(tz=timezone.utc) @@ -276,21 +273,14 @@ async def _run(self) -> None: # pylint: disable=too-many-locals diff_power = allocated_power - self._target_power target_power_changes = self._deallocate_unused_power(diff_power) - elif selected_from(selected, resend_timer): - target_power_changes = latest_target_powers - resending = True - if target_power_changes: _logger.debug("Setting power to EV chargers: %s", target_power_changes) else: continue - if not resending: - for component_id, power in target_power_changes.items(): - self._evc_states.get(component_id).update_last_allocation( - power, now - ) + for component_id, power in target_power_changes.items(): + self._evc_states.get(component_id).update_last_allocation(power, now) - latest_target_powers.update(target_power_changes) + latest_target_powers.update(target_power_changes) result = await self._set_api_power( api, target_power_changes, api_request_timeout ) diff --git a/src/frequenz/sdk/timeseries/ev_charger_pool/_ev_charger_pool.py b/src/frequenz/sdk/timeseries/ev_charger_pool/_ev_charger_pool.py index f909af661..3758f66a2 100644 --- a/src/frequenz/sdk/timeseries/ev_charger_pool/_ev_charger_pool.py +++ b/src/frequenz/sdk/timeseries/ev_charger_pool/_ev_charger_pool.py @@ -115,7 +115,14 @@ async def propose_power( actors with a higher priority. If None, the power bounds will be set to the maximum power of the batteries in the pool. This is currently and experimental feature. + + Raises: + EVChargerPoolError: If a discharge power for EV chargers is requested. """ + if power is not None and power < Power.zero(): + raise EVChargerPoolError( + "Discharging from EV chargers is currently not supported." + ) await self._ev_charger_pool.power_manager_requests_sender.send( _power_managing.Proposal( source_id=self._source_id, diff --git a/tests/timeseries/_ev_charger_pool/test_ev_charger_pool_control_methods.py b/tests/timeseries/_ev_charger_pool/test_ev_charger_pool_control_methods.py index 63059c57d..2d9613147 100644 --- a/tests/timeseries/_ev_charger_pool/test_ev_charger_pool_control_methods.py +++ b/tests/timeseries/_ev_charger_pool/test_ev_charger_pool_control_methods.py @@ -163,8 +163,6 @@ async def _init_ev_chargers(self, mocks: _Mocks) -> None: 0.05, ) - await asyncio.sleep(1) - def _assert_report( # pylint: disable=too-many-arguments self, report: EVChargerPoolReport,