Skip to content

Commit 1f43f35

Browse files
Add pv optimization tutorial
Signed-off-by: Matthias Wende <[email protected]>
1 parent b71335f commit 1f43f35

File tree

1 file changed

+125
-0
lines changed

1 file changed

+125
-0
lines changed

docs/tutorials/pv_optimization.md

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Optimizing PV production
2+
3+
## Introduction
4+
5+
Before we start it's assumed that you have finished the first [tutorial](./getting_started.md)
6+
7+
In this tutorial you will write an application that optimizes the energy produced from a
8+
[PV](intro/glossary/#pv) system with Battery for self consumption.
9+
In order to do so you need to measure the power that flows through the
10+
[grid connection point](../../intro/glossary/#grid) to determine excess power.
11+
12+
## Measure the excess power
13+
14+
When using the term excess power what we actually mean is the consumer excess power, that is the power that
15+
flows from the PV system into the grid.
16+
17+
!!! note
18+
19+
We are using the [passive sign convention](../../intro/glossary/#passive-sign-convention) and thus power
20+
flowing from PV is negative and consumed power is positive.
21+
22+
We want to measure the excess power. In order to do so you can use the SDK's data pipeline and especially
23+
the pre defined
24+
[consumer](../../reference/frequenz/sdk/timeseries/logical_meter/#frequenz.sdk.timeseries.logical_meter.LogicalMeter.consumer_power)
25+
and
26+
[producer power](../../reference/frequenz/sdk/timeseries/logical_meter/#frequenz.sdk.timeseries.logical_meter.LogicalMeter.producer_power)
27+
formulas.
28+
29+
```python
30+
async def run() -> None:
31+
... # (1)!
32+
33+
# negative means feed-in power due to the negative sign convention
34+
consumer_excess_power_engine = (
35+
microgrid.logical_meter().producer_power
36+
+ microgrid.logical_meter().consumer_power
37+
).build("excess_power") # (2)!
38+
cons_excess_power_recv = consumer_excess_power_engine.new_receiver() # (3)!
39+
```
40+
41+
1. The initialization code as explained in the Getting Started tutorial.
42+
2. Construct the consumer excess power by summing up consumer and producer power each of which having
43+
opposite signs due to the sign convention. This returns a formula engine.
44+
3. Request a receiver from the formula engine which will be used to consume the stream.
45+
46+
## Control the Battery
47+
48+
Now, with the constructed excess power data stream use a
49+
[battery pool](../../reference/frequenz/sdk/timeseries/battery_pool/) to implement control logic.
50+
51+
```python
52+
...
53+
54+
battery_pool = microgrid.battery_pool() # (1)!
55+
56+
async for cons_excess_power in cons_excess_power_recv: # (2)!
57+
cons_excess_power = cons_excess_power.value # (3)!
58+
if cons_excess_power is None: # (4)!
59+
continue
60+
if cons_excess_power <= Power.zero(): # (5)!
61+
await battery_pool.charge(-cons_excess_power)
62+
elif cons_excess_power > Power.zero():
63+
await battery_pool.discharge(cons_excess_power)
64+
```
65+
66+
1. Get an instance of the battery pool.
67+
2. Iterate asynchronously over the constructed consumer excess power stream.
68+
3. Get the `Quantity` from the received `Sample`.
69+
4. Do nothing if we didn't receive new data.
70+
5. Charge the battery if there is excess power and discharge otherwise.
71+
72+
And that's all you need to know to write the simple application for storing PV excess power in a battery.
73+
74+
## Full example
75+
76+
Here is a copy & paste friendly version
77+
78+
```python
79+
import asyncio
80+
81+
from datetime import timedelta
82+
from frequenz.sdk import microgrid
83+
from frequenz.sdk.actor import ResamplerConfig
84+
from frequenz.sdk.timeseries import Power
85+
86+
async def run() -> None:
87+
# This points to the default Frequenz microgrid sandbox
88+
microgrid_host = "microgrid.sandbox.api.frequenz.io"
89+
microgrid_port = 62060
90+
91+
# Initialize the microgrid
92+
await microgrid.initialize(
93+
microgrid_host,
94+
microgrid_port,
95+
ResamplerConfig(resampling_period=timedelta(seconds=1)),
96+
)
97+
98+
# negative means feed-in power due to the negative sign convention
99+
consumer_excess_power_engine = (
100+
microgrid.logical_meter().producer_power
101+
+ microgrid.logical_meter().consumer_power
102+
).build("excess_power") # (2)!
103+
cons_excess_power_recv = consumer_excess_power_engine.new_receiver() # (3)!
104+
105+
battery_pool = microgrid.battery_pool() # (1)!
106+
107+
async for cons_excess_power in cons_excess_power_recv: # (2)!
108+
cons_excess_power = cons_excess_power.value # (3)!
109+
if cons_excess_power is None: # (4)!
110+
continue
111+
if cons_excess_power <= Power.zero(): # (5)!
112+
await battery_pool.charge(-cons_excess_power)
113+
elif cons_excess_power > Power.zero():
114+
await battery_pool.discharge(discharge_power)
115+
116+
def main() -> None:
117+
asyncio.run(run())
118+
119+
if __name__ == "__main__":
120+
main()
121+
```
122+
123+
## Further reading
124+
125+
To create more advanced applications it is suggested to read the [actors](../../intro/actors/) documentation.

0 commit comments

Comments
 (0)