Skip to content

Commit c9ad6f6

Browse files
committed
Add ComponentStateSample
Signed-off-by: Leandro Lucarella <[email protected]>
1 parent 7442b55 commit c9ad6f6

File tree

5 files changed

+482
-2
lines changed

5 files changed

+482
-2
lines changed

src/frequenz/client/microgrid/component/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
UnspecifiedComponent,
5151
)
5252
from ._relay import Relay
53+
from ._state_sample import ComponentErrorCode, ComponentStateCode, ComponentStateSample
5354
from ._status import ComponentStatus
5455
from ._types import (
5556
ComponentTypes,
@@ -69,6 +70,9 @@
6970
"Component",
7071
"ComponentCategory",
7172
"ComponentConnection",
73+
"ComponentErrorCode",
74+
"ComponentStateCode",
75+
"ComponentStateSample",
7276
"ComponentStatus",
7377
"ComponentTypes",
7478
"Converter",
Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
# License: MIT
2+
# Copyright © 2025 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Definition of component states."""
5+
6+
import enum
7+
from dataclasses import dataclass
8+
from datetime import datetime
9+
10+
from frequenz.api.common.v1.microgrid.components import components_pb2
11+
12+
13+
@enum.unique
14+
class ComponentStateCode(enum.Enum):
15+
"""The various states that a component can be in."""
16+
17+
UNSPECIFIED = components_pb2.COMPONENT_STATE_CODE_UNSPECIFIED
18+
"""The state is unspecified (this should not be normally used)."""
19+
20+
UNKNOWN = components_pb2.COMPONENT_STATE_CODE_UNKNOWN
21+
"""The component is in an unknown or undefined condition.
22+
23+
This is used when the state can be retrieved from the component but it doesn't match
24+
any known state.
25+
"""
26+
27+
UNAVAILABLE = components_pb2.COMPONENT_STATE_CODE_UNAVAILABLE
28+
"""The component is temporarily unavailable for operation."""
29+
30+
SWITCHING_OFF = components_pb2.COMPONENT_STATE_CODE_SWITCHING_OFF
31+
"""The component is in the process of switching off."""
32+
33+
OFF = components_pb2.COMPONENT_STATE_CODE_OFF
34+
"""The component has successfully switched off."""
35+
36+
SWITCHING_ON = components_pb2.COMPONENT_STATE_CODE_SWITCHING_ON
37+
"""The component is in the process of switching on."""
38+
39+
STANDBY = components_pb2.COMPONENT_STATE_CODE_STANDBY
40+
"""The component is in standby mode and not immediately ready for operation."""
41+
42+
READY = components_pb2.COMPONENT_STATE_CODE_READY
43+
"""The component is fully operational and ready for use."""
44+
45+
CHARGING = components_pb2.COMPONENT_STATE_CODE_CHARGING
46+
"""The component is actively consuming energy."""
47+
48+
DISCHARGING = components_pb2.COMPONENT_STATE_CODE_DISCHARGING
49+
"""The component is actively producing or releasing energy."""
50+
51+
ERROR = components_pb2.COMPONENT_STATE_CODE_ERROR
52+
"""The component is in an error state and may need attention."""
53+
54+
EV_CHARGING_CABLE_UNPLUGGED = (
55+
components_pb2.COMPONENT_STATE_CODE_EV_CHARGING_CABLE_UNPLUGGED
56+
)
57+
"""The EV charging cable is unplugged from the charging station."""
58+
59+
EV_CHARGING_CABLE_PLUGGED_AT_STATION = (
60+
components_pb2.COMPONENT_STATE_CODE_EV_CHARGING_CABLE_PLUGGED_AT_STATION
61+
)
62+
"""The EV charging cable is plugged into the charging station."""
63+
64+
EV_CHARGING_CABLE_PLUGGED_AT_EV = (
65+
components_pb2.COMPONENT_STATE_CODE_EV_CHARGING_CABLE_PLUGGED_AT_EV
66+
)
67+
"""The EV charging cable is plugged into the vehicle."""
68+
69+
EV_CHARGING_CABLE_LOCKED_AT_STATION = (
70+
components_pb2.COMPONENT_STATE_CODE_EV_CHARGING_CABLE_LOCKED_AT_STATION
71+
)
72+
"""The EV charging cable is locked at the charging station end."""
73+
74+
EV_CHARGING_CABLE_LOCKED_AT_EV = (
75+
components_pb2.COMPONENT_STATE_CODE_EV_CHARGING_CABLE_LOCKED_AT_EV
76+
)
77+
"""The EV charging cable is locked at the vehicle end."""
78+
79+
RELAY_OPEN = components_pb2.COMPONENT_STATE_CODE_RELAY_OPEN
80+
"""The relay is in an open state, meaning no current can flow through."""
81+
82+
RELAY_CLOSED = components_pb2.COMPONENT_STATE_CODE_RELAY_CLOSED
83+
"""The relay is in a closed state, allowing current to flow."""
84+
85+
PRECHARGER_OPEN = components_pb2.COMPONENT_STATE_CODE_PRECHARGER_OPEN
86+
"""The precharger circuit is open, meaning it's not currently active."""
87+
88+
PRECHARGER_PRECHARGING = components_pb2.COMPONENT_STATE_CODE_PRECHARGER_PRECHARGING
89+
"""The precharger is in a precharging state, preparing the main circuit for activation."""
90+
91+
PRECHARGER_CLOSED = components_pb2.COMPONENT_STATE_CODE_PRECHARGER_CLOSED
92+
"""The precharger circuit is closed, allowing full current to flow to the main circuit."""
93+
94+
95+
@enum.unique
96+
class ComponentErrorCode(enum.Enum):
97+
"""The various errors that a component can report."""
98+
99+
UNSPECIFIED = components_pb2.COMPONENT_ERROR_CODE_UNSPECIFIED
100+
"""The error is unspecified (this should not be normally used)."""
101+
102+
UNKNOWN = components_pb2.COMPONENT_ERROR_CODE_UNKNOWN
103+
"""The component is reporting an unknown or undefined error.
104+
105+
This is used when the error can be retrieved from the component but it doesn't match
106+
any known error.
107+
"""
108+
109+
SWITCH_ON_FAULT = components_pb2.COMPONENT_ERROR_CODE_SWITCH_ON_FAULT
110+
"""The component could not be switched on."""
111+
112+
UNDERVOLTAGE = components_pb2.COMPONENT_ERROR_CODE_UNDERVOLTAGE
113+
"""The component is operating under the minimum rated voltage."""
114+
115+
OVERVOLTAGE = components_pb2.COMPONENT_ERROR_CODE_OVERVOLTAGE
116+
"""The component is operating over the maximum rated voltage."""
117+
118+
OVERCURRENT = components_pb2.COMPONENT_ERROR_CODE_OVERCURRENT
119+
"""The component is drawing more current than the maximum rated value."""
120+
121+
OVERCURRENT_CHARGING = components_pb2.COMPONENT_ERROR_CODE_OVERCURRENT_CHARGING
122+
"""The component's consumption current is over the maximum rated value during charging."""
123+
124+
OVERCURRENT_DISCHARGING = (
125+
components_pb2.COMPONENT_ERROR_CODE_OVERCURRENT_DISCHARGING
126+
)
127+
"""The component's production current is over the maximum rated value during discharging."""
128+
129+
OVERTEMPERATURE = components_pb2.COMPONENT_ERROR_CODE_OVERTEMPERATURE
130+
"""The component is operating over the maximum rated temperature."""
131+
132+
UNDERTEMPERATURE = components_pb2.COMPONENT_ERROR_CODE_UNDERTEMPERATURE
133+
"""The component is operating under the minimum rated temperature."""
134+
135+
HIGH_HUMIDITY = components_pb2.COMPONENT_ERROR_CODE_HIGH_HUMIDITY
136+
"""The component is exposed to high humidity levels over the maximum rated value."""
137+
138+
FUSE_ERROR = components_pb2.COMPONENT_ERROR_CODE_FUSE_ERROR
139+
"""The component's fuse has blown."""
140+
141+
PRECHARGE_ERROR = components_pb2.COMPONENT_ERROR_CODE_PRECHARGE_ERROR
142+
"""The component's precharge unit has failed."""
143+
144+
PLAUSIBILITY_ERROR = components_pb2.COMPONENT_ERROR_CODE_PLAUSIBILITY_ERROR
145+
"""Plausibility issues within the system involving this component."""
146+
147+
UNDERVOLTAGE_SHUTDOWN = components_pb2.COMPONENT_ERROR_CODE_UNDERVOLTAGE_SHUTDOWN
148+
"""System shutdown due to undervoltage involving this component."""
149+
150+
EV_UNEXPECTED_PILOT_FAILURE = (
151+
components_pb2.COMPONENT_ERROR_CODE_EV_UNEXPECTED_PILOT_FAILURE
152+
)
153+
"""Unexpected pilot failure in an electric vehicle component."""
154+
155+
FAULT_CURRENT = components_pb2.COMPONENT_ERROR_CODE_FAULT_CURRENT
156+
"""Fault current detected in the component."""
157+
158+
SHORT_CIRCUIT = components_pb2.COMPONENT_ERROR_CODE_SHORT_CIRCUIT
159+
"""Short circuit detected in the component."""
160+
161+
CONFIG_ERROR = components_pb2.COMPONENT_ERROR_CODE_CONFIG_ERROR
162+
"""Configuration error related to the component."""
163+
164+
ILLEGAL_COMPONENT_STATE_CODE_REQUESTED = (
165+
components_pb2.COMPONENT_ERROR_CODE_ILLEGAL_COMPONENT_STATE_CODE_REQUESTED
166+
)
167+
"""Illegal state requested for the component."""
168+
169+
HARDWARE_INACCESSIBLE = components_pb2.COMPONENT_ERROR_CODE_HARDWARE_INACCESSIBLE
170+
"""Hardware of the component is inaccessible."""
171+
172+
INTERNAL = components_pb2.COMPONENT_ERROR_CODE_INTERNAL
173+
"""Internal error within the component."""
174+
175+
UNAUTHORIZED = components_pb2.COMPONENT_ERROR_CODE_UNAUTHORIZED
176+
"""The component is unauthorized to perform the last requested action."""
177+
178+
EV_CHARGING_CABLE_UNPLUGGED_FROM_STATION = (
179+
components_pb2.COMPONENT_ERROR_CODE_EV_CHARGING_CABLE_UNPLUGGED_FROM_STATION
180+
)
181+
"""EV cable was abruptly unplugged from the charging station."""
182+
183+
EV_CHARGING_CABLE_UNPLUGGED_FROM_EV = (
184+
components_pb2.COMPONENT_ERROR_CODE_EV_CHARGING_CABLE_UNPLUGGED_FROM_EV
185+
)
186+
"""EV cable was abruptly unplugged from the vehicle."""
187+
188+
EV_CHARGING_CABLE_LOCK_FAILED = (
189+
components_pb2.COMPONENT_ERROR_CODE_EV_CHARGING_CABLE_LOCK_FAILED
190+
)
191+
"""EV cable lock failure."""
192+
193+
EV_CHARGING_CABLE_INVALID = (
194+
components_pb2.COMPONENT_ERROR_CODE_EV_CHARGING_CABLE_INVALID
195+
)
196+
"""Invalid EV cable."""
197+
198+
EV_CONSUMER_INCOMPATIBLE = (
199+
components_pb2.COMPONENT_ERROR_CODE_EV_CONSUMER_INCOMPATIBLE
200+
)
201+
"""Incompatible EV plug."""
202+
203+
BATTERY_IMBALANCE = components_pb2.COMPONENT_ERROR_CODE_BATTERY_IMBALANCE
204+
"""Battery system imbalance."""
205+
206+
BATTERY_LOW_SOH = components_pb2.COMPONENT_ERROR_CODE_BATTERY_LOW_SOH
207+
"""Low state of health (SOH) detected in the battery."""
208+
209+
BATTERY_BLOCK_ERROR = components_pb2.COMPONENT_ERROR_CODE_BATTERY_BLOCK_ERROR
210+
"""Battery block error."""
211+
212+
BATTERY_CONTROLLER_ERROR = (
213+
components_pb2.COMPONENT_ERROR_CODE_BATTERY_CONTROLLER_ERROR
214+
)
215+
"""Battery controller error."""
216+
217+
BATTERY_RELAY_ERROR = components_pb2.COMPONENT_ERROR_CODE_BATTERY_RELAY_ERROR
218+
"""Battery relay error."""
219+
220+
BATTERY_CALIBRATION_NEEDED = (
221+
components_pb2.COMPONENT_ERROR_CODE_BATTERY_CALIBRATION_NEEDED
222+
)
223+
"""Battery calibration is needed."""
224+
225+
RELAY_CYCLE_LIMIT_REACHED = (
226+
components_pb2.COMPONENT_ERROR_CODE_RELAY_CYCLE_LIMIT_REACHED
227+
)
228+
"""Relays have been cycled for the maximum number of times."""
229+
230+
231+
@dataclass(frozen=True, kw_only=True)
232+
class ComponentStateSample:
233+
"""A collection of the state, warnings, and errors for a component at a specific time."""
234+
235+
sampled_at: datetime
236+
"""The time at which this state was sampled."""
237+
238+
states: frozenset[ComponentStateCode | int]
239+
"""The set of states of the component.
240+
241+
If the reported state is not known by the client (it could happen when using an
242+
older version of the client with a newer version of the server), it will be
243+
represented as an `int` and **not** the
244+
[`ComponentStateCode.UNKNOWN`][frequenz.client.microgrid.component.ComponentStateCode.UNKNOWN]
245+
value (this value is used only when the state is not known by the server).
246+
"""
247+
248+
warnings: frozenset[ComponentErrorCode | int]
249+
"""The set of warnings for the component."""
250+
251+
errors: frozenset[ComponentErrorCode | int]
252+
"""The set of errors for the component.
253+
254+
This set will only contain errors if the component is in an error state.
255+
"""
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# License: MIT
2+
# Copyright © 2025 Frequenz Energy-as-a-Service GmbH
3+
4+
"""Loading of MetricSample and AggregatedMetricValue objects from protobuf messages."""
5+
6+
from functools import partial
7+
8+
from frequenz.api.common.v1.microgrid.components import components_pb2
9+
from frequenz.client.base import conversion
10+
11+
from .._util import enum_from_proto
12+
from ._state_sample import ComponentErrorCode, ComponentStateCode, ComponentStateSample
13+
14+
_state_from_proto = partial(enum_from_proto, enum_type=ComponentStateCode)
15+
_error_from_proto = partial(enum_from_proto, enum_type=ComponentErrorCode)
16+
17+
18+
def component_state_sample_from_proto(
19+
message: components_pb2.ComponentState,
20+
) -> ComponentStateSample:
21+
"""Convert a protobuf message to a `ComponentStateSample` object.
22+
23+
Args:
24+
message: The protobuf message to convert.
25+
26+
Returns:
27+
The resulting `ComponentStateSample` object.
28+
"""
29+
return ComponentStateSample(
30+
sampled_at=conversion.to_datetime(message.sampled_at),
31+
states=frozenset(map(_state_from_proto, message.states)),
32+
warnings=frozenset(map(_error_from_proto, message.warnings)),
33+
errors=frozenset(map(_error_from_proto, message.errors)),
34+
)

src/frequenz/client/microgrid/metrics/_sample_proto.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
from frequenz.client.base import conversion
1010

1111
from .._util import enum_from_proto
12-
from ..metrics._bounds import Bounds
13-
from ..metrics._metric import Metric
12+
from ._bounds import Bounds
1413
from ._bounds_proto import bounds_from_proto
14+
from ._metric import Metric
1515
from ._sample import AggregatedMetricValue, MetricSample
1616

1717

0 commit comments

Comments
 (0)