diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index cb3b98a..7ff3499 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,17 +2,15 @@ ## Summary - +This release introduces the `v1alpha8` module to support a new API version. ## Upgrading - The `typing-extensions` dependency minimum version was bumped to 4.6 to support Python 3.12. -- Changed the dependency reference for frequenz-api-common -- Renamed the old components module since it is replaced by electrical_components -- Updated tests to use the new electrical_components enums ## New Features +- Provide access to new API using new `v1alpha8` module. ## Bug Fixes diff --git a/pyproject.toml b/pyproject.toml index 45b187b..e33ba05 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ classifiers = [ requires-python = ">= 3.11, < 4" dependencies = [ "typing-extensions >= 4.6.0, < 5", - "frequenz-api-common @ git+https://github.com/frequenz-floss/frequenz-api-common.git@2e89add6a16d42b23612f0f791a499919f3738ed", + "frequenz-api-common >= 0.8.0, < 9", ] dynamic = ["version"] diff --git a/src/frequenz/client/common/microgrid/components/__init__.py b/src/frequenz/client/common/microgrid/components/__init__.py new file mode 100644 index 0000000..013c26a --- /dev/null +++ b/src/frequenz/client/common/microgrid/components/__init__.py @@ -0,0 +1,354 @@ +# License: MIT +# Copyright © 2022 Frequenz Energy-as-a-Service GmbH + +"""Defines the components that can be used in a microgrid.""" +from __future__ import annotations + +from enum import Enum + +# pylint: disable=no-name-in-module +from frequenz.api.common.v1.microgrid.components.components_pb2 import ( + ComponentCategory as PBComponentCategory, +) +from frequenz.api.common.v1.microgrid.components.components_pb2 import ( + ComponentErrorCode as PBComponentErrorCode, +) +from frequenz.api.common.v1.microgrid.components.components_pb2 import ( + ComponentStateCode as PBComponentStateCode, +) + +# pylint: enable=no-name-in-module + + +class ComponentCategory(Enum): + """Possible types of microgrid component.""" + + UNSPECIFIED = PBComponentCategory.COMPONENT_CATEGORY_UNSPECIFIED + """An unknown component category. + + Useful for error handling, and marking unknown components in + a list of components with otherwise known categories. + """ + + GRID = PBComponentCategory.COMPONENT_CATEGORY_GRID + """The point where the local microgrid is connected to the grid.""" + + METER = PBComponentCategory.COMPONENT_CATEGORY_METER + """A meter, for measuring electrical metrics, e.g., current, voltage, etc.""" + + INVERTER = PBComponentCategory.COMPONENT_CATEGORY_INVERTER + """An electricity generator, with batteries or solar energy.""" + + BATTERY = PBComponentCategory.COMPONENT_CATEGORY_BATTERY + """A storage system for electrical energy, used by inverters.""" + + EV_CHARGER = PBComponentCategory.COMPONENT_CATEGORY_EV_CHARGER + """A station for charging electrical vehicles.""" + + CHP = PBComponentCategory.COMPONENT_CATEGORY_CHP + """A heat and power combustion plant (CHP stands for combined heat and power).""" + + @classmethod + def from_proto( + cls, component_category: PBComponentCategory.ValueType + ) -> ComponentCategory: + """Convert a protobuf ComponentCategory message to ComponentCategory enum. + + Args: + component_category: protobuf enum to convert + + Returns: + Enum value corresponding to the protobuf message. + """ + if not any(t.value == component_category for t in ComponentCategory): + return ComponentCategory.UNSPECIFIED + return cls(component_category) + + def to_proto(self) -> PBComponentCategory.ValueType: + """Convert a ComponentCategory enum to protobuf ComponentCategory message. + + Returns: + Enum value corresponding to the protobuf message. + """ + return self.value + + +class ComponentStateCode(Enum): + """All possible states of a microgrid component.""" + + UNSPECIFIED = PBComponentStateCode.COMPONENT_STATE_CODE_UNSPECIFIED + """Default value when the component state is not explicitly set.""" + + UNKNOWN = PBComponentStateCode.COMPONENT_STATE_CODE_UNKNOWN + """State when the component is in an unknown or undefined condition. + + This is used when the sender is unable to classify the component into any + other state. + """ + SWITCHING_OFF = PBComponentStateCode.COMPONENT_STATE_CODE_SWITCHING_OFF + """State when the component is in the process of switching off.""" + + OFF = PBComponentStateCode.COMPONENT_STATE_CODE_OFF + """State when the component has successfully switched off.""" + + SWITCHING_ON = PBComponentStateCode.COMPONENT_STATE_CODE_SWITCHING_ON + """State when the component is in the process of switching on from an off state.""" + + STANDBY = PBComponentStateCode.COMPONENT_STATE_CODE_STANDBY + """State when the component is in standby mode, and not immediately ready for operation.""" + + READY = PBComponentStateCode.COMPONENT_STATE_CODE_READY + """State when the component is fully operational and ready for use.""" + + CHARGING = PBComponentStateCode.COMPONENT_STATE_CODE_CHARGING + """State when the component is actively consuming energy.""" + + DISCHARGING = PBComponentStateCode.COMPONENT_STATE_CODE_DISCHARGING + """State when the component is actively producing or releasing energy.""" + + ERROR = PBComponentStateCode.COMPONENT_STATE_CODE_ERROR + """State when the component is in an error state and may need attention.""" + + EV_CHARGING_CABLE_UNPLUGGED = ( + PBComponentStateCode.COMPONENT_STATE_CODE_EV_CHARGING_CABLE_UNPLUGGED + ) + """The Electric Vehicle (EV) charging cable is unplugged from the charging station.""" + + EV_CHARGING_CABLE_PLUGGED_AT_STATION = ( + PBComponentStateCode.COMPONENT_STATE_CODE_EV_CHARGING_CABLE_PLUGGED_AT_STATION + ) + """The EV charging cable is plugged into the charging station.""" + + EV_CHARGING_CABLE_PLUGGED_AT_EV = ( + PBComponentStateCode.COMPONENT_STATE_CODE_EV_CHARGING_CABLE_PLUGGED_AT_EV + ) + """The EV charging cable is plugged into the vehicle.""" + + EV_CHARGING_CABLE_LOCKED_AT_STATION = ( + PBComponentStateCode.COMPONENT_STATE_CODE_EV_CHARGING_CABLE_LOCKED_AT_STATION + ) + """The EV charging cable is locked at the charging station end, indicating + readiness for charging.""" + + EV_CHARGING_CABLE_LOCKED_AT_EV = ( + PBComponentStateCode.COMPONENT_STATE_CODE_EV_CHARGING_CABLE_LOCKED_AT_EV + ) + """The EV charging cable is locked at the vehicle end, indicating that charging is active.""" + + RELAY_OPEN = PBComponentStateCode.COMPONENT_STATE_CODE_RELAY_OPEN + """The relay is in an open state, meaning no current can flow through.""" + + RELAY_CLOSED = PBComponentStateCode.COMPONENT_STATE_CODE_RELAY_CLOSED + """The relay is in a closed state, allowing current to flow.""" + + PRECHARGER_OPEN = PBComponentStateCode.COMPONENT_STATE_CODE_PRECHARGER_OPEN + """The precharger circuit is open, meaning it's not currently active.""" + + PRECHARGER_PRECHARGING = ( + PBComponentStateCode.COMPONENT_STATE_CODE_PRECHARGER_PRECHARGING + ) + """The precharger is in a precharging state, preparing the main circuit for activation.""" + + PRECHARGER_CLOSED = PBComponentStateCode.COMPONENT_STATE_CODE_PRECHARGER_CLOSED + """The precharger circuit is closed, allowing full current to flow to the main circuit.""" + + @classmethod + def from_proto( + cls, component_state: PBComponentStateCode.ValueType + ) -> ComponentStateCode: + """Convert a protobuf ComponentStateCode message to ComponentStateCode enum. + + Args: + component_state: protobuf enum to convert + + Returns: + Enum value corresponding to the protobuf message. + """ + if not any(c.value == component_state for c in ComponentStateCode): + return ComponentStateCode.UNSPECIFIED + return cls(component_state) + + def to_proto(self) -> PBComponentStateCode.ValueType: + """Convert a ComponentStateCode enum to protobuf ComponentStateCode message. + + Returns: + Enum value corresponding to the protobuf message. + """ + return self.value + + +class ComponentErrorCode(Enum): + """All possible errors that can occur across all microgrid component categories.""" + + UNSPECIFIED = PBComponentErrorCode.COMPONENT_ERROR_CODE_UNSPECIFIED + """Default value. No specific error is specified.""" + + UNKNOWN = PBComponentErrorCode.COMPONENT_ERROR_CODE_UNKNOWN + """The component is reporting an unknown or an undefined error, and the sender + cannot parse the component error to any of the variants below.""" + + SWITCH_ON_FAULT = PBComponentErrorCode.COMPONENT_ERROR_CODE_SWITCH_ON_FAULT + """Error indicating that the component could not be switched on.""" + + UNDERVOLTAGE = PBComponentErrorCode.COMPONENT_ERROR_CODE_UNDERVOLTAGE + """Error indicating that the component is operating under the minimum rated + voltage.""" + + OVERVOLTAGE = PBComponentErrorCode.COMPONENT_ERROR_CODE_OVERVOLTAGE + """Error indicating that the component is operating over the maximum rated + voltage.""" + + OVERCURRENT = PBComponentErrorCode.COMPONENT_ERROR_CODE_OVERCURRENT + """Error indicating that the component is drawing more current than the + maximum rated value.""" + + OVERCURRENT_CHARGING = ( + PBComponentErrorCode.COMPONENT_ERROR_CODE_OVERCURRENT_CHARGING + ) + """Error indicating that the component's consumption current is over the + maximum rated value during charging.""" + + OVERCURRENT_DISCHARGING = ( + PBComponentErrorCode.COMPONENT_ERROR_CODE_OVERCURRENT_DISCHARGING + ) + """Error indicating that the component's production current is over the + maximum rated value during discharging.""" + + OVERTEMPERATURE = PBComponentErrorCode.COMPONENT_ERROR_CODE_OVERTEMPERATURE + """Error indicating that the component is operating over the maximum rated + temperature.""" + + UNDERTEMPERATURE = PBComponentErrorCode.COMPONENT_ERROR_CODE_UNDERTEMPERATURE + """Error indicating that the component is operating under the minimum rated + temperature.""" + + HIGH_HUMIDITY = PBComponentErrorCode.COMPONENT_ERROR_CODE_HIGH_HUMIDITY + """Error indicating that the component is exposed to high humidity levels over + the maximum rated value.""" + + FUSE_ERROR = PBComponentErrorCode.COMPONENT_ERROR_CODE_FUSE_ERROR + """Error indicating that the component's fuse has blown.""" + + PRECHARGE_ERROR = PBComponentErrorCode.COMPONENT_ERROR_CODE_PRECHARGE_ERROR + """Error indicating that the component's precharge unit has failed.""" + + PLAUSIBILITY_ERROR = PBComponentErrorCode.COMPONENT_ERROR_CODE_PLAUSIBILITY_ERROR + """Error indicating plausibility issues within the system involving this + component.""" + + UNDERVOLTAGE_SHUTDOWN = ( + PBComponentErrorCode.COMPONENT_ERROR_CODE_UNDERVOLTAGE_SHUTDOWN + ) + """Error indicating system shutdown due to undervoltage involving this + component.""" + + EV_UNEXPECTED_PILOT_FAILURE = ( + PBComponentErrorCode.COMPONENT_ERROR_CODE_EV_UNEXPECTED_PILOT_FAILURE + ) + """Error indicating unexpected pilot failure in an electric vehicle (EV) + component.""" + + FAULT_CURRENT = PBComponentErrorCode.COMPONENT_ERROR_CODE_FAULT_CURRENT + """Error indicating fault current detected in the component.""" + + SHORT_CIRCUIT = PBComponentErrorCode.COMPONENT_ERROR_CODE_SHORT_CIRCUIT + """Error indicating a short circuit detected in the component.""" + + CONFIG_ERROR = PBComponentErrorCode.COMPONENT_ERROR_CODE_CONFIG_ERROR + """Error indicating a configuration error related to the component.""" + + ILLEGAL_COMPONENT_STATE_CODE_REQUESTED = ( + PBComponentErrorCode.COMPONENT_ERROR_CODE_ILLEGAL_COMPONENT_STATE_CODE_REQUESTED + ) + """Error indicating an illegal state requested for the component.""" + + HARDWARE_INACCESSIBLE = ( + PBComponentErrorCode.COMPONENT_ERROR_CODE_HARDWARE_INACCESSIBLE + ) + """Error indicating that the hardware of the component is inaccessible.""" + + INTERNAL = PBComponentErrorCode.COMPONENT_ERROR_CODE_INTERNAL + """Error indicating an internal error within the component.""" + + UNAUTHORIZED = PBComponentErrorCode.COMPONENT_ERROR_CODE_UNAUTHORIZED + """Error indicating that the component is unauthorized to perform the + last requested action.""" + + EV_CHARGING_CABLE_UNPLUGGED_FROM_STATION = ( + PBComponentErrorCode.COMPONENT_ERROR_CODE_EV_CHARGING_CABLE_UNPLUGGED_FROM_STATION + ) + """Error indicating electric vehicle (EV) cable was abruptly unplugged from + the charging station.""" + + EV_CHARGING_CABLE_UNPLUGGED_FROM_EV = ( + PBComponentErrorCode.COMPONENT_ERROR_CODE_EV_CHARGING_CABLE_UNPLUGGED_FROM_EV + ) + """Error indicating electric vehicle (EV) cable was abruptly unplugged from + the vehicle.""" + + EV_CHARGING_CABLE_LOCK_FAILED = ( + PBComponentErrorCode.COMPONENT_ERROR_CODE_EV_CHARGING_CABLE_LOCK_FAILED + ) + """Error indicating electric vehicle (EV) cable lock failure.""" + + EV_CHARGING_CABLE_INVALID = ( + PBComponentErrorCode.COMPONENT_ERROR_CODE_EV_CHARGING_CABLE_INVALID + ) + """Error indicating an invalid electric vehicle (EV) cable.""" + + EV_CONSUMER_INCOMPATIBLE = ( + PBComponentErrorCode.COMPONENT_ERROR_CODE_EV_CONSUMER_INCOMPATIBLE + ) + """Error indicating an incompatible electric vehicle (EV) plug.""" + + BATTERY_IMBALANCE = PBComponentErrorCode.COMPONENT_ERROR_CODE_BATTERY_IMBALANCE + """Error indicating a battery system imbalance.""" + + BATTERY_LOW_SOH = PBComponentErrorCode.COMPONENT_ERROR_CODE_BATTERY_LOW_SOH + """Error indicating a low state of health (SOH) detected in the battery.""" + + BATTERY_BLOCK_ERROR = PBComponentErrorCode.COMPONENT_ERROR_CODE_BATTERY_BLOCK_ERROR + """Error indicating a battery block error.""" + + BATTERY_CONTROLLER_ERROR = ( + PBComponentErrorCode.COMPONENT_ERROR_CODE_BATTERY_CONTROLLER_ERROR + ) + """Error indicating a battery controller error.""" + + BATTERY_RELAY_ERROR = PBComponentErrorCode.COMPONENT_ERROR_CODE_BATTERY_RELAY_ERROR + """Error indicating a battery relay error.""" + + BATTERY_CALIBRATION_NEEDED = ( + PBComponentErrorCode.COMPONENT_ERROR_CODE_BATTERY_CALIBRATION_NEEDED + ) + """Error indicating that battery calibration is needed.""" + + RELAY_CYCLE_LIMIT_REACHED = ( + PBComponentErrorCode.COMPONENT_ERROR_CODE_RELAY_CYCLE_LIMIT_REACHED + ) + """Error indicating that the relays have been cycled for the maximum number of + times.""" + + @classmethod + def from_proto( + cls, component_error_code: PBComponentErrorCode.ValueType + ) -> ComponentErrorCode: + """Convert a protobuf ComponentErrorCode message to ComponentErrorCode enum. + + Args: + component_error_code: protobuf enum to convert + + Returns: + Enum value corresponding to the protobuf message. + """ + if not any(c.value == component_error_code for c in ComponentErrorCode): + return ComponentErrorCode.UNSPECIFIED + return cls(component_error_code) + + def to_proto(self) -> PBComponentErrorCode.ValueType: + """Convert a ComponentErrorCode enum to protobuf ComponentErrorCode message. + + Returns: + Enum value corresponding to the protobuf message. + """ + return self.value diff --git a/src/frequenz/client/common/v1alpha8/__init__.py b/src/frequenz/client/common/v1alpha8/__init__.py new file mode 100644 index 0000000..adc4de1 --- /dev/null +++ b/src/frequenz/client/common/v1alpha8/__init__.py @@ -0,0 +1,4 @@ +# License: MIT +# Copyright © 2025 Frequenz Energy-as-a-Service GmbH + +"""Types introduced or modified in the v1alpha8 version of the common api.""" diff --git a/src/frequenz/client/common/v1alpha8/metric/__init__.py b/src/frequenz/client/common/v1alpha8/metric/__init__.py new file mode 100644 index 0000000..7634473 --- /dev/null +++ b/src/frequenz/client/common/v1alpha8/metric/__init__.py @@ -0,0 +1,159 @@ +# License: MIT +# Copyright © 2024 Frequenz Energy-as-a-Service GmbH + +"""Module to define the metrics used with the common client.""" + +from enum import Enum +from typing import Self + +from frequenz.api.common.v1alpha8.metrics.metrics_pb2 import Metric as PBMetric + + +class Metric(Enum): + """List of supported metrics. + + AC energy metrics information: + * This energy metric is reported directly from the component, and not a + result of aggregations in our systems. If a component does not have this + metric, this field cannot be populated. + * Components that provide energy metrics reset this metric from time to + time. This behaviour is specific to each component model. E.g., some + components reset it on UTC 00:00:00. + * This energy metric does not specify the timestamp since when the energy + was being accumulated, and therefore can be inconsistent. + """ + + # Default value + UNSPECIFIED = PBMetric.METRIC_UNSPECIFIED + + # DC electricity metrics + DC_VOLTAGE = PBMetric.METRIC_DC_VOLTAGE + DC_CURRENT = PBMetric.METRIC_DC_CURRENT + DC_POWER = PBMetric.METRIC_DC_POWER + + # General AC electricity metrics + AC_FREQUENCY = PBMetric.METRIC_AC_FREQUENCY + AC_VOLTAGE = PBMetric.METRIC_AC_VOLTAGE + AC_VOLTAGE_PHASE_1_N = PBMetric.METRIC_AC_VOLTAGE_PHASE_1_N + AC_VOLTAGE_PHASE_2_N = PBMetric.METRIC_AC_VOLTAGE_PHASE_2_N + AC_VOLTAGE_PHASE_3_N = PBMetric.METRIC_AC_VOLTAGE_PHASE_3_N + AC_VOLTAGE_PHASE_1_PHASE_2 = PBMetric.METRIC_AC_VOLTAGE_PHASE_1_PHASE_2 + AC_VOLTAGE_PHASE_2_PHASE_3 = PBMetric.METRIC_AC_VOLTAGE_PHASE_2_PHASE_3 + AC_VOLTAGE_PHASE_3_PHASE_1 = PBMetric.METRIC_AC_VOLTAGE_PHASE_3_PHASE_1 + AC_CURRENT = PBMetric.METRIC_AC_CURRENT + AC_CURRENT_PHASE_1 = PBMetric.METRIC_AC_CURRENT_PHASE_1 + AC_CURRENT_PHASE_2 = PBMetric.METRIC_AC_CURRENT_PHASE_2 + AC_CURRENT_PHASE_3 = PBMetric.METRIC_AC_CURRENT_PHASE_3 + + # AC power metrics + AC_POWER_APPARENT = PBMetric.METRIC_AC_POWER_APPARENT + AC_POWER_APPARENT_PHASE_1 = PBMetric.METRIC_AC_POWER_APPARENT_PHASE_1 + AC_POWER_APPARENT_PHASE_2 = PBMetric.METRIC_AC_POWER_APPARENT_PHASE_2 + AC_POWER_APPARENT_PHASE_3 = PBMetric.METRIC_AC_POWER_APPARENT_PHASE_3 + AC_POWER_ACTIVE = PBMetric.METRIC_AC_POWER_ACTIVE + AC_POWER_ACTIVE_PHASE_1 = PBMetric.METRIC_AC_POWER_ACTIVE_PHASE_1 + AC_POWER_ACTIVE_PHASE_2 = PBMetric.METRIC_AC_POWER_ACTIVE_PHASE_2 + AC_POWER_ACTIVE_PHASE_3 = PBMetric.METRIC_AC_POWER_ACTIVE_PHASE_3 + AC_POWER_REACTIVE = PBMetric.METRIC_AC_POWER_REACTIVE + AC_POWER_REACTIVE_PHASE_1 = PBMetric.METRIC_AC_POWER_REACTIVE_PHASE_1 + AC_POWER_REACTIVE_PHASE_2 = PBMetric.METRIC_AC_POWER_REACTIVE_PHASE_2 + AC_POWER_REACTIVE_PHASE_3 = PBMetric.METRIC_AC_POWER_REACTIVE_PHASE_3 + + # AC power factor + AC_POWER_FACTOR = PBMetric.METRIC_AC_POWER_FACTOR + AC_POWER_FACTOR_PHASE_1 = PBMetric.METRIC_AC_POWER_FACTOR_PHASE_1 + AC_POWER_FACTOR_PHASE_2 = PBMetric.METRIC_AC_POWER_FACTOR_PHASE_2 + AC_POWER_FACTOR_PHASE_3 = PBMetric.METRIC_AC_POWER_FACTOR_PHASE_3 + + # AC energy metrics - Please be careful when using and check Enum docs + AC_ENERGY_APPARENT = PBMetric.METRIC_AC_ENERGY_APPARENT + AC_ENERGY_APPARENT_PHASE_1 = PBMetric.METRIC_AC_ENERGY_APPARENT_PHASE_1 + AC_ENERGY_APPARENT_PHASE_2 = PBMetric.METRIC_AC_ENERGY_APPARENT_PHASE_2 + AC_ENERGY_APPARENT_PHASE_3 = PBMetric.METRIC_AC_ENERGY_APPARENT_PHASE_3 + AC_ENERGY_ACTIVE = PBMetric.METRIC_AC_ENERGY_ACTIVE + AC_ENERGY_ACTIVE_PHASE_1 = PBMetric.METRIC_AC_ENERGY_ACTIVE_PHASE_1 + AC_ENERGY_ACTIVE_PHASE_2 = PBMetric.METRIC_AC_ENERGY_ACTIVE_PHASE_2 + AC_ENERGY_ACTIVE_PHASE_3 = PBMetric.METRIC_AC_ENERGY_ACTIVE_PHASE_3 + AC_ENERGY_ACTIVE_CONSUMED = PBMetric.METRIC_AC_ENERGY_ACTIVE_CONSUMED + AC_ENERGY_ACTIVE_CONSUMED_PHASE_1 = ( + PBMetric.METRIC_AC_ENERGY_ACTIVE_CONSUMED_PHASE_1 + ) + AC_ENERGY_ACTIVE_CONSUMED_PHASE_2 = ( + PBMetric.METRIC_AC_ENERGY_ACTIVE_CONSUMED_PHASE_2 + ) + AC_ENERGY_ACTIVE_CONSUMED_PHASE_3 = ( + PBMetric.METRIC_AC_ENERGY_ACTIVE_CONSUMED_PHASE_3 + ) + AC_ENERGY_ACTIVE_DELIVERED = PBMetric.METRIC_AC_ENERGY_ACTIVE_DELIVERED + AC_ENERGY_ACTIVE_DELIVERED_PHASE_1 = ( + PBMetric.METRIC_AC_ENERGY_ACTIVE_DELIVERED_PHASE_1 + ) + AC_ENERGY_ACTIVE_DELIVERED_PHASE_2 = ( + PBMetric.METRIC_AC_ENERGY_ACTIVE_DELIVERED_PHASE_2 + ) + AC_ENERGY_ACTIVE_DELIVERED_PHASE_3 = ( + PBMetric.METRIC_AC_ENERGY_ACTIVE_DELIVERED_PHASE_3 + ) + AC_ENERGY_REACTIVE = PBMetric.METRIC_AC_ENERGY_REACTIVE + AC_ENERGY_REACTIVE_PHASE_1 = PBMetric.METRIC_AC_ENERGY_REACTIVE_PHASE_1 + AC_ENERGY_REACTIVE_PHASE_2 = PBMetric.METRIC_AC_ENERGY_REACTIVE_PHASE_2 + AC_ENERGY_REACTIVE_PHASE_3 = PBMetric.METRIC_AC_ENERGY_REACTIVE_PHASE_3 + + # AC harmonics + AC_TOTAL_HARMONIC_DISTORTION_CURRENT = ( + PBMetric.METRIC_AC_TOTAL_HARMONIC_DISTORTION_CURRENT + ) + AC_TOTAL_HARMONIC_DISTORTION_CURRENT_PHASE_1 = ( + PBMetric.METRIC_AC_TOTAL_HARMONIC_DISTORTION_CURRENT_PHASE_1 + ) + AC_TOTAL_HARMONIC_DISTORTION_CURRENT_PHASE_2 = ( + PBMetric.METRIC_AC_TOTAL_HARMONIC_DISTORTION_CURRENT_PHASE_2 + ) + AC_TOTAL_HARMONIC_DISTORTION_CURRENT_PHASE_3 = ( + PBMetric.METRIC_AC_TOTAL_HARMONIC_DISTORTION_CURRENT_PHASE_3 + ) + + # General BMS metrics + BATTERY_CAPACITY = PBMetric.METRIC_BATTERY_CAPACITY + BATTERY_SOC_PCT = PBMetric.METRIC_BATTERY_SOC_PCT + BATTERY_TEMPERATURE = PBMetric.METRIC_BATTERY_TEMPERATURE + + # General inverter metrics + INVERTER_TEMPERATURE = PBMetric.METRIC_INVERTER_TEMPERATURE + INVERTER_TEMPERATURE_CABINET = PBMetric.METRIC_INVERTER_TEMPERATURE_CABINET + INVERTER_TEMPERATURE_HEATSINK = PBMetric.METRIC_INVERTER_TEMPERATURE_HEATSINK + INVERTER_TEMPERATURE_TRANSFORMER = PBMetric.METRIC_INVERTER_TEMPERATURE_TRANSFORMER + + # EV charging station metrics + EV_CHARGER_TEMPERATURE = PBMetric.METRIC_EV_CHARGER_TEMPERATURE + + # General sensor metrics + SENSOR_WIND_SPEED = PBMetric.METRIC_SENSOR_WIND_SPEED + SENSOR_WIND_DIRECTION = PBMetric.METRIC_SENSOR_WIND_DIRECTION + SENSOR_TEMPERATURE = PBMetric.METRIC_SENSOR_TEMPERATURE + SENSOR_RELATIVE_HUMIDITY = PBMetric.METRIC_SENSOR_RELATIVE_HUMIDITY + SENSOR_DEW_POINT = PBMetric.METRIC_SENSOR_DEW_POINT + SENSOR_AIR_PRESSURE = PBMetric.METRIC_SENSOR_AIR_PRESSURE + SENSOR_IRRADIANCE = PBMetric.METRIC_SENSOR_IRRADIANCE + + @classmethod + def from_proto(cls, metric: PBMetric.ValueType) -> Self: + """Convert a protobuf Metric value to Metric enum. + + Args: + metric: Metric to convert. + Returns: + Enum value corresponding to the protobuf message. + """ + if not any(m.value == metric for m in cls): + return cls(Metric.UNSPECIFIED) + + return cls(metric) + + def to_proto(self) -> PBMetric.ValueType: + """Convert a Metric object to protobuf Metric. + + Returns: + Protobuf message corresponding to the Metric object. + """ + return self.value diff --git a/src/frequenz/client/common/v1alpha8/microgrid/__init__.py b/src/frequenz/client/common/v1alpha8/microgrid/__init__.py new file mode 100644 index 0000000..d003a01 --- /dev/null +++ b/src/frequenz/client/common/v1alpha8/microgrid/__init__.py @@ -0,0 +1,4 @@ +# License: MIT +# Copyright © 2025 Frequenz Energy-as-a-Service GmbH + +"""Frequenz microgrid definition.""" diff --git a/src/frequenz/client/common/microgrid/electrical_components/__init__.py b/src/frequenz/client/common/v1alpha8/microgrid/electrical_components/__init__.py similarity index 95% rename from src/frequenz/client/common/microgrid/electrical_components/__init__.py rename to src/frequenz/client/common/v1alpha8/microgrid/electrical_components/__init__.py index 4110a0d..3d667d5 100644 --- a/src/frequenz/client/common/microgrid/electrical_components/__init__.py +++ b/src/frequenz/client/common/v1alpha8/microgrid/electrical_components/__init__.py @@ -7,13 +7,13 @@ from enum import Enum # pylint: disable=no-name-in-module -from frequenz.api.common.v1.microgrid.electrical_components.electrical_components_pb2 import ( +from frequenz.api.common.v1alpha8.microgrid.electrical_components.electrical_components_pb2 import ( ElectricalComponentCategory as PBElectricalComponentCategory, ) -from frequenz.api.common.v1.microgrid.electrical_components.electrical_components_pb2 import ( +from frequenz.api.common.v1alpha8.microgrid.electrical_components.electrical_components_pb2 import ( ElectricalComponentDiagnosticCode as PBElectricalComponentDiagnosticCode, ) -from frequenz.api.common.v1.microgrid.electrical_components.electrical_components_pb2 import ( +from frequenz.api.common.v1alpha8.microgrid.electrical_components.electrical_components_pb2 import ( ElectricalComponentStateCode as PBElectricalComponentStateCode, ) @@ -32,7 +32,9 @@ class ElectricalComponentCategory(Enum): a list of components with otherwise known categories. """ - GRID = PBElectricalComponentCategory.ELECTRICAL_COMPONENT_CATEGORY_GRID + GRID_CONNECTION_POINT = ( + PBElectricalComponentCategory.ELECTRICAL_COMPONENT_CATEGORY_GRID_CONNECTION_POINT + ) """The point where the local microgrid is connected to the grid.""" METER = PBElectricalComponentCategory.ELECTRICAL_COMPONENT_CATEGORY_METER @@ -63,17 +65,14 @@ class ElectricalComponentCategory(Enum): CHP = PBElectricalComponentCategory.ELECTRICAL_COMPONENT_CATEGORY_CHP """A heat and power combustion plant (CHP stands for combined heat and power).""" - RELAY = PBElectricalComponentCategory.ELECTRICAL_COMPONENT_CATEGORY_RELAY + BREAKER = PBElectricalComponentCategory.ELECTRICAL_COMPONENT_CATEGORY_BREAKER """A relay, used for switching electrical circuits on and off.""" PRECHARGER = PBElectricalComponentCategory.ELECTRICAL_COMPONENT_CATEGORY_PRECHARGER """A precharger, used for preparing electrical circuits for switching on.""" - FUSE = PBElectricalComponentCategory.ELECTRICAL_COMPONENT_CATEGORY_FUSE - """A fuse, used for protecting electrical circuits from overcurrent.""" - - TRANSFORMER = ( - PBElectricalComponentCategory.ELECTRICAL_COMPONENT_CATEGORY_VOLTAGE_TRANSFORMER + POWER_TRANSFORMER = ( + PBElectricalComponentCategory.ELECTRICAL_COMPONENT_CATEGORY_POWER_TRANSFORMER ) """A transformer, used for changing the voltage of electrical circuits.""" @@ -314,12 +313,6 @@ class ElectricalComponentDiagnosticCode(Enum): """Error indicating plausibility issues within the system involving this component.""" - UNDERVOLTAGE_SHUTDOWN = ( - PBElectricalComponentDiagnosticCode.ELECTRICAL_COMPONENT_DIAGNOSTIC_CODE_UNDERVOLTAGE_SHUTDOWN # noqa: E501 - ) - """Error indicating system shutdown due to undervoltage involving this - component.""" - EV_UNEXPECTED_PILOT_FAILURE = ( PBElectricalComponentDiagnosticCode.ELECTRICAL_COMPONENT_DIAGNOSTIC_CODE_EV_UNEXPECTED_PILOT_FAILURE # noqa: E501 ) diff --git a/src/frequenz/client/common/v1alpha8/pagination/__init__.py b/src/frequenz/client/common/v1alpha8/pagination/__init__.py new file mode 100644 index 0000000..6034449 --- /dev/null +++ b/src/frequenz/client/common/v1alpha8/pagination/__init__.py @@ -0,0 +1,91 @@ +# License: MIT +# Copyright © 2024 Frequenz Energy-as-a-Service GmbH + +"""Module to define the pagination used with the common client.""" + +from __future__ import annotations # required for constructor type hinting + +from dataclasses import dataclass +from typing import Self + +# pylint: disable=no-name-in-module +from frequenz.api.common.v1alpha8.pagination.pagination_info_pb2 import ( + PaginationInfo as PBPaginationInfo, +) +from frequenz.api.common.v1alpha8.pagination.pagination_params_pb2 import ( + PaginationParams as PBPaginationParams, +) + +# pylint: enable=no-name-in-module + + +@dataclass(frozen=True, kw_only=True) +class PaginationParams: + """Parameters for paginating list requests.""" + + page_size: int + """The maximum number of results to be returned per request.""" + + page_token: str + """The token identifying a specific page of the list results.""" + + @classmethod + def from_proto(cls, pagination_params: PBPaginationParams) -> Self: + """Convert a protobuf PaginationParams to python PaginationParams object. + + Args: + pagination_params: Params to convert. + Returns: + Params object corresponding to the protobuf message. + """ + return cls( + page_size=pagination_params.page_size, + page_token=pagination_params.page_token, + ) + + def to_proto(self) -> PBPaginationParams: + """Convert a Params object to protobuf PaginationParams. + + Returns: + Protobuf message corresponding to the Params object. + """ + return PBPaginationParams( + page_size=self.page_size, + page_token=self.page_token, + ) + + +@dataclass(frozen=True, kw_only=True) +class PaginationInfo: + """Information about the pagination of a list request.""" + + total_items: int + """The total number of items that match the request.""" + + next_page_token: str | None = None + """The token identifying the next page of results.""" + + @classmethod + def from_proto(cls, pagination_info: PBPaginationInfo) -> Self: + """Convert a protobuf PBPaginationInfo to Info object. + + Args: + pagination_info: Info to convert. + Returns: + Info object corresponding to the protobuf message. + """ + return cls( + total_items=pagination_info.total_items, + next_page_token=pagination_info.next_page_token, + ) + + def to_proto(self) -> PBPaginationInfo: + """Convert a Info object to protobuf PBPaginationInfo. + + Returns: + Protobuf message corresponding to the Info object. + """ + return PBPaginationInfo( + total_items=self.total_items, + next_page_token=self.next_page_token, + ) diff --git a/tests/test_client_common.py b/tests/test_client_common.py index b932bf0..e9d865d 100644 --- a/tests/test_client_common.py +++ b/tests/test_client_common.py @@ -3,31 +3,26 @@ """Tests for the frequenz.client.common package.""" -from frequenz.client.common.microgrid.electrical_components import ( - ElectricalComponentCategory, - ElectricalComponentDiagnosticCode, - ElectricalComponentStateCode, +from frequenz.client.common.microgrid.components import ( + ComponentCategory, + ComponentErrorCode, + ComponentStateCode, ) def test_components() -> None: """Test the components.""" - for category in ElectricalComponentCategory: - assert ElectricalComponentCategory.from_proto(category.to_proto()) == category + for category in ComponentCategory: + assert ComponentCategory.from_proto(category.to_proto()) == category def test_component_state_code() -> None: """Test the component state code.""" - for state_code in ElectricalComponentStateCode: - assert ( - ElectricalComponentStateCode.from_proto(state_code.to_proto()) == state_code - ) + for state_code in ComponentStateCode: + assert ComponentStateCode.from_proto(state_code.to_proto()) == state_code def test_component_error_code() -> None: - """Test the component diagnostic code.""" - for diagnostic_code in ElectricalComponentDiagnosticCode: - assert ( - ElectricalComponentDiagnosticCode.from_proto(diagnostic_code.to_proto()) - == diagnostic_code - ) + """Test the component error code.""" + for error_code in ComponentErrorCode: + assert ComponentErrorCode.from_proto(error_code.to_proto()) == error_code diff --git a/tests/test_client_common_v1alpha8.py b/tests/test_client_common_v1alpha8.py new file mode 100644 index 0000000..cb9538a --- /dev/null +++ b/tests/test_client_common_v1alpha8.py @@ -0,0 +1,33 @@ +# License: MIT +# Copyright © 2023 Frequenz Energy-as-a-Service GmbH + +"""Tests for the frequenz.client.common package.""" + +from frequenz.client.common.v1alpha8.microgrid.electrical_components import ( + ElectricalComponentCategory, + ElectricalComponentDiagnosticCode, + ElectricalComponentStateCode, +) + + +def test_components() -> None: + """Test the components.""" + for category in ElectricalComponentCategory: + assert ElectricalComponentCategory.from_proto(category.to_proto()) == category + + +def test_component_state_code() -> None: + """Test the component state code.""" + for state_code in ElectricalComponentStateCode: + assert ( + ElectricalComponentStateCode.from_proto(state_code.to_proto()) == state_code + ) + + +def test_component_error_code() -> None: + """Test the component diagnostic code.""" + for diagnostic_code in ElectricalComponentDiagnosticCode: + assert ( + ElectricalComponentDiagnosticCode.from_proto(diagnostic_code.to_proto()) + == diagnostic_code + )