Skip to content

Commit 9c44f4a

Browse files
Add pv optimization tutorial
Signed-off-by: Matthias Wende <matthias.wende@frequenz.com>
1 parent b71335f commit 9c44f4a

File tree

1 file changed

+117
-0
lines changed

1 file changed

+117
-0
lines changed

docs/tutorials/pv_optimization.md

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

0 commit comments

Comments
 (0)