Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 1 addition & 8 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
# Frequenz Microgrid API Client Release Notes

## Summary

<!-- Here goes a general summary of what this release is about -->

## Upgrading

- `ApiClient`:
Expand All @@ -18,7 +14,4 @@
## 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

<!-- Here goes notable bug fixes that are worth a special mention or explanation -->
- The client now supports setting reactive power for components through the new `set_reactive_power` method.
28 changes: 28 additions & 0 deletions src/frequenz/client/microgrid/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,34 @@ async def set_power( # noqa: DOC502 (raises ApiClientError indirectly)
method_name="SetPowerActive",
)

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].
"""
await client.call_stub_method(
self,
lambda: self.stub.SetPowerReactive(
microgrid_pb2.SetPowerReactiveParam(
component_id=component_id, power=reactive_power_var
),
timeout=int(DEFAULT_GRPC_CALL_TIMEOUT),
),
method_name="SetPowerReactive",
)

async def set_bounds( # noqa: DOC503 (raises ApiClientError indirectly)
self,
component_id: int,
Expand Down
43 changes: 43 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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"<status=<MagicMock name='mock_status\.name' id='.*'>>: 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",
[
Expand Down