Skip to content

Commit 2d806b0

Browse files
committed
Add MicrogridInfo and MicrogridStatus
This will be used to retrieve the microgrid information. Signed-off-by: Leandro Lucarella <[email protected]>
1 parent 8e08ba6 commit 2d806b0

File tree

4 files changed

+484
-0
lines changed

4 files changed

+484
-0
lines changed

src/frequenz/client/microgrid/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
)
3434
from ._lifetime import Lifetime
3535
from ._location import Location
36+
from ._microgrid_info import MicrogridInfo, MicrogridStatus
3637

3738
__all__ = [
3839
"ApiClientError",
@@ -48,6 +49,8 @@
4849
"Lifetime",
4950
"Location",
5051
"MicrogridApiClient",
52+
"MicrogridInfo",
53+
"MicrogridStatus",
5154
"OperationAborted",
5255
"OperationCancelled",
5356
"OperationNotImplemented",
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# License: MIT
2+
# Copyright © 2025 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Definition of a microgrid."""
5+
6+
import datetime
7+
import enum
8+
import logging
9+
from dataclasses import dataclass
10+
from functools import cached_property
11+
12+
from ._delivery_area import DeliveryArea
13+
from ._location import Location
14+
from .id import EnterpriseId, MicrogridId
15+
16+
_logger = logging.getLogger(__name__)
17+
18+
19+
@enum.unique
20+
class MicrogridStatus(enum.Enum):
21+
"""The possible statuses for a microgrid."""
22+
23+
UNSPECIFIED = 0
24+
"""The status is unspecified. This should not be used."""
25+
26+
ACTIVE = 1
27+
"""The microgrid is active."""
28+
29+
INACTIVE = 2
30+
"""The microgrid is inactive."""
31+
32+
33+
@dataclass(frozen=True, kw_only=True)
34+
class MicrogridInfo:
35+
"""A localized grouping of electricity generation, energy storage, and loads.
36+
37+
A microgrid is a localized grouping of electricity generation, energy storage, and
38+
loads that normally operates connected to a traditional centralized grid.
39+
40+
Each microgrid has a unique identifier and is associated with an enterprise account.
41+
42+
A key feature is that it has a physical location and is situated in a delivery area.
43+
44+
Note: Key Concepts
45+
- Physical Location: Geographical coordinates specify the exact physical
46+
location of the microgrid.
47+
- Delivery Area: Each microgrid is part of a broader delivery area, which is
48+
crucial for energy trading and compliance.
49+
"""
50+
51+
id: MicrogridId
52+
"""The unique identifier of the microgrid."""
53+
54+
enterprise_id: EnterpriseId
55+
"""The unique identifier linking this microgrid to its parent enterprise account."""
56+
57+
name: str | None
58+
"""Name of the microgrid."""
59+
60+
delivery_area: DeliveryArea | None
61+
"""The delivery area where the microgrid is located, as identified by a specific code."""
62+
63+
location: Location | None
64+
"""Physical location of the microgrid, in geographical co-ordinates."""
65+
66+
status: MicrogridStatus | int
67+
"""The current status of the microgrid."""
68+
69+
create_timestamp: datetime.datetime
70+
"""The UTC timestamp indicating when the microgrid was initially created."""
71+
72+
@cached_property
73+
def is_active(self) -> bool:
74+
"""Whether the microgrid is active."""
75+
if self.status is MicrogridStatus.UNSPECIFIED:
76+
# Because this is a cached property, the warning will only be logged once.
77+
_logger.warning(
78+
"Microgrid %s has an unspecified status. Assuming it is active.", self
79+
)
80+
return self.status in (MicrogridStatus.ACTIVE, MicrogridStatus.UNSPECIFIED)
81+
82+
def __str__(self) -> str:
83+
"""Return the ID of this microgrid as a string."""
84+
name = f":{self.name}" if self.name else ""
85+
return f"{self.id}{name}"
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# License: MIT
2+
# Copyright © 2025 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Loading of MicrogridInfo objects from protobuf messages."""
5+
6+
7+
import logging
8+
9+
from frequenz.api.common.v1.microgrid import microgrid_pb2
10+
from frequenz.client.base import conversion
11+
12+
from ._delivery_area import DeliveryArea
13+
from ._delivery_area_proto import delivery_area_from_proto
14+
from ._location import Location
15+
from ._location_proto import location_from_proto
16+
from ._microgrid_info import MicrogridInfo, MicrogridStatus
17+
from ._util import enum_from_proto
18+
from .id import EnterpriseId, MicrogridId
19+
20+
_logger = logging.getLogger(__name__)
21+
22+
23+
def microgrid_info_from_proto(message: microgrid_pb2.Microgrid) -> MicrogridInfo:
24+
"""Convert a protobuf microgrid message to a microgrid object.
25+
26+
Args:
27+
message: The protobuf message to convert.
28+
29+
Returns:
30+
The resulting microgrid object.
31+
"""
32+
major_issues: list[str] = []
33+
minor_issues: list[str] = []
34+
35+
delivery_area: DeliveryArea | None = None
36+
if message.HasField("delivery_area"):
37+
delivery_area = delivery_area_from_proto(message.delivery_area)
38+
else:
39+
major_issues.append("delivery_area is missing")
40+
41+
location: Location | None = None
42+
if message.HasField("location"):
43+
location = location_from_proto(message.location)
44+
else:
45+
major_issues.append("location is missing")
46+
47+
name = message.name or None
48+
if name is None:
49+
minor_issues.append("name is empty")
50+
51+
status = enum_from_proto(message.status, MicrogridStatus)
52+
if status is MicrogridStatus.UNSPECIFIED:
53+
major_issues.append("status is unspecified")
54+
elif isinstance(status, int):
55+
major_issues.append("status is unrecognized")
56+
57+
if major_issues:
58+
_logger.warning(
59+
"Found issues in microgrid: %s | Protobuf message:\n%s",
60+
", ".join(major_issues),
61+
message,
62+
)
63+
64+
if minor_issues:
65+
_logger.debug(
66+
"Found minor issues in microgrid: %s | Protobuf message:\n%s",
67+
", ".join(minor_issues),
68+
message,
69+
)
70+
71+
return MicrogridInfo(
72+
id=MicrogridId(message.id),
73+
enterprise_id=EnterpriseId(message.enterprise_id),
74+
name=message.name or None,
75+
delivery_area=delivery_area,
76+
location=location,
77+
status=status,
78+
create_timestamp=conversion.to_datetime(message.create_timestamp),
79+
)

0 commit comments

Comments
 (0)