Skip to content

Commit 21a6584

Browse files
Add a module for grid connection points
Grid connection points are nodes in the component graph that abstract grids in a microgrid. Such nodes have their own metadata, which include their rated fuse limits. Microgrids with zero or one grid connection points are accepted. Note that a microgrid may have zero grid connection points, if it is configured as an island. This commit adds a module exposing a representation of such grid connection points. The module is named `frequenz.sdk.microgrid.grid`. The `grid` module exposes a singleton object that represents the grid connection point. It gets initialized when `microgrid.initialize()` is called. It can be obtained by calling `microgrid.grid_connection.get()`. Signed-off-by: Tiyash Basu <[email protected]>
1 parent 4160b80 commit 21a6584

File tree

4 files changed

+144
-1
lines changed

4 files changed

+144
-1
lines changed

RELEASE_NOTES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ This release replaces the `@actor` decorator with a new `Actor` class.
9292

9393
- `Actor`: This new class inherits from `BackgroundService` and it replaces the `@actor` decorator.
9494

95+
- Calling `microgrid.initialize()` now also initializes the microgrid's grid connection point as a singleton object of a newly added type `Grid`. This object can be obtained by calling `microgrid.grid.get()`. This object exposes the max current that can course through the grid connection point, which is useful for the power distribution algorithm. The max current is provided by the Microgrid API, and can be obtained by calling `microgrid.grid.get().max_current`.
96+
Note that a microgrid is allowed to have zero or one grid connection point. Microgrids configured as islands will have zero grid connection points, and microgrids configured as grid-connected will have one grid connection point.
97+
9598
## Bug Fixes
9699

97100
- Fixes a bug in the ring buffer updating the end timestamp of gaps when they are outdated.

src/frequenz/sdk/microgrid/__init__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"""
99

1010
from ..actor import ResamplerConfig
11-
from . import _data_pipeline, client, component, connection_manager
11+
from . import _data_pipeline, client, component, connection_manager, grid
1212
from ._data_pipeline import battery_pool, ev_charger_pool, logical_meter
1313
from ._graph import ComponentGraph
1414

@@ -22,6 +22,11 @@ async def initialize(host: str, port: int, resampler_config: ResamplerConfig) ->
2222
resampler_config: Configuration for the resampling actor.
2323
"""
2424
await connection_manager.initialize(host, port)
25+
26+
api_client = connection_manager.get().api_client
27+
components = await api_client.components()
28+
grid.initialize(components)
29+
2530
await _data_pipeline.initialize(resampler_config)
2631

2732

@@ -32,5 +37,6 @@ async def initialize(host: str, port: int, resampler_config: ResamplerConfig) ->
3237
"component",
3338
"battery_pool",
3439
"ev_charger_pool",
40+
"grid",
3541
"logical_meter",
3642
]

src/frequenz/sdk/microgrid/grid.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# License: MIT
2+
# Copyright © 2023 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Grid connection point.
5+
6+
This module provides the `Grid` type, which represents a grid connection point
7+
in a microgrid.
8+
"""
9+
10+
import logging
11+
from dataclasses import dataclass
12+
from typing import Iterable, Optional
13+
14+
from .component import Component
15+
from .component._component import ComponentCategory
16+
17+
18+
@dataclass(frozen=True)
19+
class Grid:
20+
"""A grid connection point.
21+
22+
Attributes:
23+
max_current: The maximum current that can course through the grid
24+
connection point, in Amperes.
25+
"""
26+
27+
max_current: float
28+
29+
30+
_GRID: Optional[Grid] = None
31+
32+
33+
def initialize(components: Iterable[Component]) -> None:
34+
"""Initialize the grid connection.
35+
36+
Args:
37+
components: The components in the microgrid.
38+
39+
Raises:
40+
RuntimeError: If there is more than 1 grid connection point in the
41+
microgrid.
42+
"""
43+
global _GRID # pylint: disable=global-statement
44+
45+
grid_connections = list(
46+
component
47+
for component in components
48+
if component.category == ComponentCategory.GRID
49+
)
50+
51+
grid_connections_count = len(grid_connections)
52+
53+
if grid_connections_count == 0:
54+
logging.info(
55+
"No grid connection found for this microgrid. This is normal for an islanded microgrid."
56+
)
57+
elif grid_connections_count > 1:
58+
raise RuntimeError(
59+
f"Expected at most one grid connection, got {len(grid_connections)}"
60+
)
61+
else:
62+
max_current = grid_connections[0].metadata.max_current # type: ignore
63+
_GRID = Grid(max_current)
64+
65+
66+
def get() -> Optional[Grid]:
67+
"""Get the grid connection.
68+
69+
Note that a microgrid configured as an island will not have a grid
70+
connection point. For such microgrids, this function will return `None`.
71+
72+
Returns:
73+
The grid connection.
74+
"""
75+
return _GRID

tests/microgrid/test_grid.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# License: MIT
2+
# Copyright © 2023 Frequenz Energy-as-a-Service GmbH
3+
4+
"""
5+
Tests for the `Grid` module.
6+
"""
7+
8+
from frequenz.sdk import microgrid
9+
from frequenz.sdk.microgrid.component import Component, ComponentCategory, GridMetadata
10+
from frequenz.sdk.microgrid.grid import Grid
11+
12+
13+
async def test_grid() -> None:
14+
"""Test the grid connection module."""
15+
16+
# The tests here need to be in this exact sequence, because the grid connection
17+
# is a singleton. Once it gets created, it stays in memory for the duration of
18+
# the tests, unless we explicitly delete it.
19+
20+
# validate that islands with no grid connection are accepted.
21+
components = [
22+
Component(2, ComponentCategory.METER),
23+
]
24+
25+
microgrid.grid.initialize(components)
26+
27+
grid = microgrid.grid.get()
28+
assert grid is None
29+
30+
# validate that the microgrid initialization fails when there are multiple
31+
# grid connection points.
32+
components = [
33+
Component(1, ComponentCategory.GRID, None, GridMetadata(123.0)),
34+
Component(2, ComponentCategory.GRID, None, GridMetadata(345.0)),
35+
Component(3, ComponentCategory.METER),
36+
]
37+
38+
try:
39+
microgrid.grid.initialize(components)
40+
assert False, "Expected microgrid.grid.initialize to raise a RuntimeError."
41+
except RuntimeError:
42+
pass
43+
44+
grid = microgrid.grid.get()
45+
assert grid is None
46+
47+
# validate that microgrids with one grid connection are accepted.
48+
components = [
49+
Component(1, ComponentCategory.GRID, None, GridMetadata(123.0)),
50+
Component(2, ComponentCategory.METER),
51+
]
52+
53+
microgrid.grid.initialize(components)
54+
55+
grid = microgrid.grid.get()
56+
assert grid == Grid(max_current=123.0)
57+
58+
max_current = grid.max_current
59+
assert max_current == 123.0

0 commit comments

Comments
 (0)