From c895202c1ca005d05a53c46117535b7f8f2a99ac Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 11 Nov 2024 12:34:36 +0100 Subject: [PATCH 1/4] Add the `set_reactive_power` method Signed-off-by: Sahas Subramanian --- src/frequenz/client/microgrid/_client.py | 36 +++++++++++++++++++- tests/test_client.py | 43 ++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/src/frequenz/client/microgrid/_client.py b/src/frequenz/client/microgrid/_client.py index 71775380..e84a95c3 100644 --- a/src/frequenz/client/microgrid/_client.py +++ b/src/frequenz/client/microgrid/_client.py @@ -441,7 +441,41 @@ async def set_power(self, component_id: int, power_w: float) -> None: grpc_error=grpc_error, ) from grpc_error - async def set_bounds( + async def set_reactive_power( # noqa: DOC502 (raises ApiClientError indirectly) + self, component_id: int, reactive_power_var: float + ) -> None: + """Send request to the Microgrid to set reactive power for component. + + Negative values are for inductive (lagging) power , and positive values are for + capacitive (leading) power. + + Args: + component_id: id of the component to set power. + reactive_power_var: reactive power to set for the component. + + Raises: + ApiClientError: If the are any errors communicating with the Microgrid API, + most likely a subclass of + [GrpcError][frequenz.client.microgrid.GrpcError]. + """ + try: + await cast( + Awaitable[Empty], + self.api.SetPowerReactive( + microgrid_pb2.SetPowerReactiveParam( + component_id=component_id, power=reactive_power_var + ), + timeout=int(DEFAULT_GRPC_CALL_TIMEOUT), + ), + ) + except grpc.aio.AioRpcError as grpc_error: + raise ApiClientError.from_grpc_error( + server_url=self._server_url, + operation="SetPowerReactive", + grpc_error=grpc_error, + ) from grpc_error + + async def set_bounds( # noqa: DOC503 (raises ApiClientError indirectly) self, component_id: int, lower: float, diff --git a/tests/test_client.py b/tests/test_client.py index ff0c308e..5293d8dd 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -42,6 +42,7 @@ def __init__(self, *, retry_strategy: retry.Strategy | None = None) -> None: mock_stub.ListComponents = mock.AsyncMock("ListComponents") mock_stub.ListConnections = mock.AsyncMock("ListConnections") mock_stub.SetPowerActive = mock.AsyncMock("SetPowerActive") + mock_stub.SetPowerReactive = mock.AsyncMock("SetPowerReactive") mock_stub.AddInclusionBounds = mock.AsyncMock("AddInclusionBounds") mock_stub.StreamComponentData = mock.Mock("StreamComponentData") super().__init__("grpc://mock_host:1234", retry_strategy=retry_strategy) @@ -607,6 +608,48 @@ async def test_set_power_grpc_error() -> None: await client.set_power(component_id=83, power_w=100.0) +@pytest.mark.parametrize( + "reactive_power_var", + [0, 0.0, 12, -75, 0.1, -0.0001, 134.0], +) +async def test_set_reactive_power_ok( + reactive_power_var: float, meter83: microgrid_pb2.Component +) -> None: + """Test if charge is able to charge component.""" + client = _TestClient() + client.mock_stub.ListComponents.return_value = microgrid_pb2.ComponentList( + components=[meter83] + ) + + await client.set_reactive_power( + component_id=83, reactive_power_var=reactive_power_var + ) + client.mock_stub.SetPowerReactive.assert_called_once() + call_args = client.mock_stub.SetPowerReactive.call_args[0] + assert call_args[0] == microgrid_pb2.SetPowerReactiveParam( + component_id=83, power=reactive_power_var + ) + + +async def test_set_reactive_power_grpc_error() -> None: + """Test set_power() raises ApiClientError when the gRPC call fails.""" + client = _TestClient() + client.mock_stub.SetPowerReactive.side_effect = grpc.aio.AioRpcError( + mock.MagicMock(name="mock_status"), + mock.MagicMock(name="mock_initial_metadata"), + mock.MagicMock(name="mock_trailing_metadata"), + "fake grpc details", + "fake grpc debug_error_string", + ) + with pytest.raises( + ApiClientError, + match=r"Failed calling 'SetPowerReactive' on 'grpc://mock_host:1234': .* " + r">: fake grpc details " + r"\(fake grpc debug_error_string\)", + ): + await client.set_reactive_power(component_id=83, reactive_power_var=100.0) + + @pytest.mark.parametrize( "bounds", [ From 29d6b3283ac9d8b73d1fc7b0d4fd4c4d25148c70 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 11 Nov 2024 14:35:53 +0100 Subject: [PATCH 2/4] Add type stub dependencies for grpc and protobuf Signed-off-by: Sahas Subramanian --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 2e75cf3e..09f31d61 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,9 @@ dev-mkdocs = [ ] dev-mypy = [ "mypy == 1.11.2", + "grpc-stubs == 1.53.0.5", "types-Markdown == 3.7.0.20240822", + "types-protobuf == 5.28.3.20241030", # For checking the noxfile, docs/ script, and tests "frequenz-client-microgrid[dev-mkdocs,dev-noxfile,dev-pytest]", ] From c94c310c29c4c5de4995ffd1cfe54d470ca10bbb Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 11 Nov 2024 12:38:44 +0100 Subject: [PATCH 3/4] Update release notes Signed-off-by: Sahas Subramanian --- RELEASE_NOTES.md | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 80e5f0cb..05bf9552 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,17 +1,5 @@ # Frequenz Microgrid API Client Release Notes -## Summary - - - -## Upgrading - - - ## New Features - - -## Bug Fixes - - +- The client now supports setting reactive power for components through the new `set_reactive_power` method. From 8f6b7fb6681c413f287b09b6fa19fd5dbb9e74e0 Mon Sep 17 00:00:00 2001 From: Leandro Lucarella Date: Tue, 19 Nov 2024 13:06:52 +0100 Subject: [PATCH 4/4] Prepare release notes for the v0.6.0 release Signed-off-by: Leandro Lucarella --- RELEASE_NOTES.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index d46ff52b..290cb6a6 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,9 +1,5 @@ # Frequenz Microgrid API Client Release Notes -## Summary - - - ## Upgrading - `ApiClient`: @@ -18,7 +14,3 @@ ## New Features - The client now inherits from `frequenz.client.base.BaseApiClient`, so it provides a few new features, like `disconnect()`ing or using it as a context manager. Please refer to the [`BaseApiClient` documentation](https://frequenz-floss.github.io/frequenz-client-base-python/latest/reference/frequenz/client/base/client/#frequenz.client.base.client.BaseApiClient) for more information on these features. - -## Bug Fixes - -