-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add pv optimization tutorial #700
base: v1.x.x
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,125 @@ | ||||||
# Optimizing PV production | ||||||
|
||||||
## Introduction | ||||||
|
||||||
Before we start it's assumed that you have finished the first [tutorial](./getting_started.md) | ||||||
|
||||||
In this tutorial you will write an application that optimizes the energy produced from a | ||||||
[PV](intro/glossary/#pv) system with Battery for self consumption. | ||||||
In order to do so you need to measure the power that flows through the | ||||||
[grid connection point](../../intro/glossary/#grid) to determine excess power. | ||||||
|
||||||
## Measure the excess power | ||||||
|
||||||
When using the term excess power what we actually mean is the consumer excess power, that is the power that | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. quotes would be good around the term here |
||||||
flows from the PV system into the grid. | ||||||
|
||||||
!!! note | ||||||
|
||||||
We are using the [passive sign convention](../../intro/glossary/#passive-sign-convention) and thus power | ||||||
flowing from PV is negative and consumed power is positive. | ||||||
|
||||||
We want to measure the excess power. In order to do so you can use the SDK's data pipeline and especially | ||||||
the pre defined | ||||||
[consumer](../../reference/frequenz/sdk/timeseries/logical_meter/#frequenz.sdk.timeseries.logical_meter.LogicalMeter.consumer_power) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be
Suggested change
|
||||||
and | ||||||
[producer power](../../reference/frequenz/sdk/timeseries/logical_meter/#frequenz.sdk.timeseries.logical_meter.LogicalMeter.producer_power) | ||||||
formulas. | ||||||
|
||||||
```python | ||||||
async def run() -> None: | ||||||
... # (1)! | ||||||
|
||||||
# negative means feed-in power due to the negative sign convention | ||||||
consumer_excess_power_engine = ( | ||||||
microgrid.logical_meter().producer_power | ||||||
+ microgrid.logical_meter().consumer_power | ||||||
).build("excess_power") # (2)! | ||||||
cons_excess_power_recv = consumer_excess_power_engine.new_receiver() # (3)! | ||||||
``` | ||||||
|
||||||
1. The initialization code as explained in the Getting Started tutorial. | ||||||
2. Construct the consumer excess power by summing up consumer and producer power each of which having | ||||||
opposite signs due to the sign convention. This returns a formula engine. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be nice to link to I think all classes that are returned should be public, because otherwise the user doesn't know what they have to offer, even when looking at the API docs, for example: (no link in the return type, so the user don't know what can be done with it) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah this is silly |
||||||
3. Request a receiver from the formula engine which will be used to consume the stream. | ||||||
|
||||||
## Control the Battery | ||||||
|
||||||
Now, with the constructed excess power data stream use a | ||||||
[battery pool](../../reference/frequenz/sdk/timeseries/battery_pool/) to implement control logic. | ||||||
|
||||||
```python | ||||||
... | ||||||
|
||||||
battery_pool = microgrid.battery_pool() # (1)! | ||||||
|
||||||
async for cons_excess_power in cons_excess_power_recv: # (2)! | ||||||
cons_excess_power = cons_excess_power.value # (3)! | ||||||
if cons_excess_power is None: # (4)! | ||||||
continue | ||||||
if cons_excess_power <= Power.zero(): # (5)! | ||||||
await battery_pool.charge(-cons_excess_power) | ||||||
elif cons_excess_power > Power.zero(): | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick: the condition here is redundant, it can just be a simple You could also use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh yes, the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should ideally enable examples testing for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe it isn't hard, but it will probably need some reading up on how exactly do do it, maybe half a days work? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it might be worth it now that we have more examples in I would also like to resurrect the idea about just extracting examples to separate files and run all the tools as normal on them. Another advantage of this is we need to run But the later is probably a fair amount of extra work, so we can also do it as a second step and first only focus on getting There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||
await battery_pool.discharge(cons_excess_power) | ||||||
``` | ||||||
|
||||||
1. Get an instance of the battery pool. | ||||||
2. Iterate asynchronously over the constructed consumer excess power stream. | ||||||
3. Get the `Quantity` from the received `Sample`. | ||||||
4. Do nothing if we didn't receive new data. | ||||||
5. Charge the battery if there is excess power and discharge otherwise. | ||||||
|
||||||
And that's all you need to know to write the simple application for storing PV excess power in a battery. | ||||||
|
||||||
## Full example | ||||||
|
||||||
Here is a copy & paste friendly version | ||||||
|
||||||
```python | ||||||
import asyncio | ||||||
|
||||||
from datetime import timedelta | ||||||
from frequenz.sdk import microgrid | ||||||
from frequenz.sdk.actor import ResamplerConfig | ||||||
from frequenz.sdk.timeseries import Power | ||||||
|
||||||
async def run() -> None: | ||||||
# This points to the default Frequenz microgrid sandbox | ||||||
microgrid_host = "microgrid.sandbox.api.frequenz.io" | ||||||
microgrid_port = 62060 | ||||||
|
||||||
# Initialize the microgrid | ||||||
await microgrid.initialize( | ||||||
microgrid_host, | ||||||
microgrid_port, | ||||||
ResamplerConfig(resampling_period=timedelta(seconds=1)), | ||||||
) | ||||||
|
||||||
# negative means feed-in power due to the negative sign convention | ||||||
consumer_excess_power_engine = ( | ||||||
microgrid.logical_meter().producer_power | ||||||
+ microgrid.logical_meter().consumer_power | ||||||
).build("excess_power") # (2)! | ||||||
cons_excess_power_recv = consumer_excess_power_engine.new_receiver() # (3)! | ||||||
|
||||||
battery_pool = microgrid.battery_pool() # (1)! | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do the numbered comments make sense when there are no points below the block? does it reuse the numbered comments from previous examples? |
||||||
|
||||||
async for cons_excess_power in cons_excess_power_recv: # (2)! | ||||||
cons_excess_power = cons_excess_power.value # (3)! | ||||||
if cons_excess_power is None: # (4)! | ||||||
continue | ||||||
if cons_excess_power <= Power.zero(): # (5)! | ||||||
await battery_pool.charge(-cons_excess_power) | ||||||
elif cons_excess_power > Power.zero(): | ||||||
await battery_pool.discharge(discharge_power) | ||||||
|
||||||
def main() -> None: | ||||||
asyncio.run(run()) | ||||||
|
||||||
if __name__ == "__main__": | ||||||
main() | ||||||
``` | ||||||
|
||||||
## Further reading | ||||||
|
||||||
To create more advanced applications it is suggested to read the [actors](../../intro/actors/) documentation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once #722 is merged, we'll no longer have to type paths and slugs directly:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also the dir is not called "intro" anymore, it got renamed to "user-guide".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#722 is merged btw, so this is available upstream.