Skip to content

Commit

Permalink
Add reactive power for inverters, meters and EV chargers (#30)
Browse files Browse the repository at this point in the history
Fixes #28.
  • Loading branch information
llucax authored Mar 28, 2024
2 parents 56b1884 + f0e9d25 commit d8d50ff
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 34 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@

## Introduction

Microgrid API client for Python

TODO(cookiecutter): Improve the README file
This project provides a Python client for the [Frequenz Microgrid
API](https://frequenz-floss.github.io/frequenz-api-microgrid/) that is more
idiomatic to use in Python than the automatically-generated gRPC client
provided by `protoc`.

## Supported Platforms

Expand Down
14 changes: 1 addition & 13 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,5 @@
# Frequenz Microgrid API Client Release Notes

## Summary

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

## Upgrading

<!-- Here goes notes on how to upgrade from previous versions, including deprecations and what they should be replaced with -->

## New Features

<!-- Here goes the main new features and examples or instructions on how to use them -->

## Bug Fixes

<!-- Here goes notable bug fixes that are worth a special mention or explanation -->
- The `reactive_power` is exposed for meters, inverters and EV chargers.
10 changes: 10 additions & 0 deletions src/frequenz/client/microgrid/_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,16 @@ class ComponentMetricId(Enum):
ACTIVE_POWER_PHASE_3 = "active_power_phase_3"
"""Active power in phase 3."""

REACTIVE_POWER = "reactive_power"
"""Reactive power."""

REACTIVE_POWER_PHASE_1 = "reactive_power_phase_1"
"""Reactive power in phase 1."""
REACTIVE_POWER_PHASE_2 = "reactive_power_phase_2"
"""Reactive power in phase 2."""
REACTIVE_POWER_PHASE_3 = "reactive_power_phase_3"
"""Reactive power in phase 3."""

CURRENT_PHASE_1 = "current_phase_1"
"""Current in phase 1."""
CURRENT_PHASE_2 = "current_phase_2"
Expand Down
138 changes: 120 additions & 18 deletions src/frequenz/client/microgrid/_component_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,18 +70,46 @@ class MeterData(ComponentData):
"""A wrapper class for holding meter data."""

active_power: float
"""The 3-phase active power, in Watts, represented in the passive sign convention.
+ve current means consumption, away from the grid.
-ve current means supply into the grid.
"""The total active 3-phase AC power, in Watts (W).
Represented in the passive sign convention.
* Positive means consumption from the grid.
* Negative means supply into the grid.
"""

active_power_per_phase: tuple[float, float, float]
"""The AC active power for phase/line 1,2 and 3 respectively."""
"""The per-phase AC active power for phase 1, 2, and 3 respectively, in Watt (W).
Represented in the passive sign convention.
* Positive means consumption from the grid.
* Negative means supply into the grid.
"""

reactive_power: float
"""The total reactive 3-phase AC power, in Volt-Ampere Reactive (VAr).
* Positive power means capacitive (current leading w.r.t. voltage).
* Negative power means inductive (current lagging w.r.t. voltage).
"""

reactive_power_per_phase: tuple[float, float, float]
"""The per-phase AC reactive power, in Volt-Ampere Reactive (VAr).
The provided values are for phase 1, 2, and 3 respectively.
* Positive power means capacitive (current leading w.r.t. voltage).
* Negative power means inductive (current lagging w.r.t. voltage).
"""

current_per_phase: tuple[float, float, float]
"""AC current in Amperes (A) for phase/line 1,2 and 3 respectively.
+ve current means consumption, away from the grid.
-ve current means supply into the grid.
Represented in the passive sign convention.
* Positive means consumption from the grid.
* Negative means supply into the grid.
"""

voltage_per_phase: tuple[float, float, float]
Expand Down Expand Up @@ -111,6 +139,12 @@ def from_proto(cls, raw: PbComponentData) -> Self:
raw.meter.data.ac.phase_2.power_active.value,
raw.meter.data.ac.phase_3.power_active.value,
),
reactive_power=raw.meter.data.ac.power_reactive.value,
reactive_power_per_phase=(
raw.meter.data.ac.phase_1.power_reactive.value,
raw.meter.data.ac.phase_2.power_reactive.value,
raw.meter.data.ac.phase_3.power_reactive.value,
),
current_per_phase=(
raw.meter.data.ac.phase_1.current.value,
raw.meter.data.ac.phase_2.current.value,
Expand Down Expand Up @@ -241,18 +275,46 @@ class InverterData(ComponentData): # pylint: disable=too-many-instance-attribut
"""A wrapper class for holding inverter data."""

active_power: float
"""The 3-phase active power, in Watts, represented in the passive sign convention.
+ve current means consumption, away from the grid.
-ve current means supply into the grid.
"""The total active 3-phase AC power, in Watts (W).
Represented in the passive sign convention.
* Positive means consumption from the grid.
* Negative means supply into the grid.
"""

active_power_per_phase: tuple[float, float, float]
"""The AC active power for phase/line 1, 2 and 3 respectively."""
"""The per-phase AC active power for phase 1, 2, and 3 respectively, in Watt (W).
Represented in the passive sign convention.
* Positive means consumption from the grid.
* Negative means supply into the grid.
"""

reactive_power: float
"""The total reactive 3-phase AC power, in Volt-Ampere Reactive (VAr).
* Positive power means capacitive (current leading w.r.t. voltage).
* Negative power means inductive (current lagging w.r.t. voltage).
"""

reactive_power_per_phase: tuple[float, float, float]
"""The per-phase AC reactive power, in Volt-Ampere Reactive (VAr).
The provided values are for phase 1, 2, and 3 respectively.
* Positive power means capacitive (current leading w.r.t. voltage).
* Negative power means inductive (current lagging w.r.t. voltage).
"""

current_per_phase: tuple[float, float, float]
"""AC current in Amperes (A) for phase/line 1, 2 and 3 respectively.
+ve current means consumption, away from the grid.
-ve current means supply into the grid.
Represented in the passive sign convention.
* Positive means consumption from the grid.
* Negative means supply into the grid.
"""

voltage_per_phase: tuple[float, float, float]
Expand Down Expand Up @@ -335,6 +397,12 @@ def from_proto(cls, raw: PbComponentData) -> Self:
raw.inverter.data.ac.phase_2.power_active.value,
raw.inverter.data.ac.phase_3.power_active.value,
),
reactive_power=raw.inverter.data.ac.power_reactive.value,
reactive_power_per_phase=(
raw.inverter.data.ac.phase_1.power_reactive.value,
raw.inverter.data.ac.phase_2.power_reactive.value,
raw.inverter.data.ac.phase_3.power_reactive.value,
),
current_per_phase=(
raw.inverter.data.ac.phase_1.current.value,
raw.inverter.data.ac.phase_2.current.value,
Expand Down Expand Up @@ -363,18 +431,46 @@ class EVChargerData(ComponentData): # pylint: disable=too-many-instance-attribu
"""A wrapper class for holding ev_charger data."""

active_power: float
"""The 3-phase active power, in Watts, represented in the passive sign convention.
+ve current means consumption, away from the grid.
-ve current means supply into the grid.
"""The total active 3-phase AC power, in Watts (W).
Represented in the passive sign convention.
* Positive means consumption from the grid.
* Negative means supply into the grid.
"""

active_power_per_phase: tuple[float, float, float]
"""The AC active power for phase/line 1,2 and 3 respectively."""
"""The per-phase AC active power for phase 1, 2, and 3 respectively, in Watt (W).
Represented in the passive sign convention.
* Positive means consumption from the grid.
* Negative means supply into the grid.
"""

current_per_phase: tuple[float, float, float]
"""AC current in Amperes (A) for phase/line 1,2 and 3 respectively.
+ve current means consumption, away from the grid.
-ve current means supply into the grid.
Represented in the passive sign convention.
* Positive means consumption from the grid.
* Negative means supply into the grid.
"""

reactive_power: float
"""The total reactive 3-phase AC power, in Volt-Ampere Reactive (VAr).
* Positive power means capacitive (current leading w.r.t. voltage).
* Negative power means inductive (current lagging w.r.t. voltage).
"""

reactive_power_per_phase: tuple[float, float, float]
"""The per-phase AC reactive power, in Volt-Ampere Reactive (VAr).
The provided values are for phase 1, 2, and 3 respectively.
* Positive power means capacitive (current leading w.r.t. voltage).
* Negative power means inductive (current lagging w.r.t. voltage).
"""

voltage_per_phase: tuple[float, float, float]
Expand Down Expand Up @@ -455,6 +551,12 @@ def from_proto(cls, raw: PbComponentData) -> Self:
raw.ev_charger.data.ac.phase_2.power_active.value,
raw.ev_charger.data.ac.phase_3.power_active.value,
),
reactive_power=raw.ev_charger.data.ac.power_reactive.value,
reactive_power_per_phase=(
raw.ev_charger.data.ac.phase_1.power_reactive.value,
raw.ev_charger.data.ac.phase_2.power_reactive.value,
raw.ev_charger.data.ac.phase_3.power_reactive.value,
),
current_per_phase=(
raw.ev_charger.data.ac.phase_1.current.value,
raw.ev_charger.data.ac.phase_2.current.value,
Expand Down
10 changes: 10 additions & 0 deletions tests/test_component_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,20 +52,28 @@ def test_inverter_data() -> None:
system_exclusion_bounds=Bounds(lower=-501.0, upper=501.0),
system_inclusion_bounds=Bounds(lower=-51_000.0, upper=51_000.0),
),
power_reactive=Metric(
value=200.3,
system_exclusion_bounds=Bounds(lower=-502.0, upper=502.0),
system_inclusion_bounds=Bounds(lower=-52_000.0, upper=52_000.0),
),
phase_1=AC.ACPhase(
current=Metric(value=12.3),
voltage=Metric(value=229.8),
power_active=Metric(value=33.1),
power_reactive=Metric(value=10.1),
),
phase_2=AC.ACPhase(
current=Metric(value=23.4),
voltage=Metric(value=230.0),
power_active=Metric(value=33.3),
power_reactive=Metric(value=10.2),
),
phase_3=AC.ACPhase(
current=Metric(value=34.5),
voltage=Metric(value=230.2),
power_active=Metric(value=33.8),
power_reactive=Metric(value=10.3),
),
),
),
Expand All @@ -84,6 +92,8 @@ def test_inverter_data() -> None:
assert inv_data.frequency == pytest.approx(50.1)
assert inv_data.active_power == pytest.approx(100.2)
assert inv_data.active_power_per_phase == pytest.approx((33.1, 33.3, 33.8))
assert inv_data.reactive_power == pytest.approx(200.3)
assert inv_data.reactive_power_per_phase == pytest.approx((10.1, 10.2, 10.3))
assert inv_data.current_per_phase == pytest.approx((12.3, 23.4, 34.5))
assert inv_data.voltage_per_phase == pytest.approx((229.8, 230.0, 230.2))
assert inv_data.active_power_inclusion_lower_bound == pytest.approx(-51_000.0)
Expand Down

0 comments on commit d8d50ff

Please sign in to comment.