Skip to content

Commit

Permalink
givenergy (#165)
Browse files Browse the repository at this point in the history
* givenergy

* run fc update

* latest inv

* refactor

* use serial number from api
  • Loading branch information
aryanbhosale authored Jul 29, 2024
1 parent 63a50e5 commit b8dafdd
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 16 deletions.
5 changes: 3 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# User needs to add their Enphase API details

ENPHASE_SYSTEM_ID = 'user_enphase_system_id'
ENPHASE_CLIENT_ID = 'user_enphase_client_id'
ENPHASE_CLIENT_SECRET = 'user_enphase_client_secret'
Expand All @@ -8,12 +7,14 @@ ENPHASE_API_KEY = 'user_enphase_api_key'
AUTHORIZATION_URL = 'https://api.enphaseenergy.com/oauth/authorize?response_type=code&client_id=ENPHASE_CLIENT_ID'

# User needs to add their Solis Cloud API details

SOLIS_CLOUD_API_KEY = 'user_solis_account_key'
SOLIS_CLOUD_API_KEY_SECRET = 'user_solis_user_key'
SOLIS_CLOUD_API_URL = 'https://www.soliscloud.com'
SOLIS_CLOUD_API_PORT = '13333'

# User needs to add their GivEnergy API details
GIVENERGY_API_KEY = 'user_givenergy_api_key'

# This section is for OpenMeteo setup

# Docker is used to fetch and store OpenMeteo's open data, targeting temperature_2m, precipitation,
Expand Down
33 changes: 23 additions & 10 deletions dashboards/dashboard_2/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from quartz_solar_forecast.data import get_nwp, process_pv_data
from quartz_solar_forecast.inverters.enphase import process_enphase_data
from quartz_solar_forecast.inverters.solis import SolisData, get_solis_data
from quartz_solar_forecast.inverters.givenergy import get_givenergy_data

# Load environment variables
load_dotenv()
Expand Down Expand Up @@ -146,14 +147,17 @@ def make_pv_data(
ts: pd.Timestamp,
access_token: str = None,
enphase_system_id: str = None,
solis_data: pd.DataFrame = None
solis_data: pd.DataFrame = None,
givenergy_data: pd.DataFrame = None
) -> xr.Dataset:
live_generation_kw = None

if site.inverter_type == "enphase" and access_token and enphase_system_id:
live_generation_kw = get_enphase_data(enphase_system_id, access_token)
elif site.inverter_type == "solis" and solis_data is not None:
live_generation_kw = solis_data
elif site.inverter_type == "givenergy" and givenergy_data is not None:
live_generation_kw = givenergy_data

da = process_pv_data(live_generation_kw, ts, site)
return da
Expand All @@ -165,7 +169,8 @@ def predict_ocf(
nwp_source: str = "icon",
access_token: str = None,
enphase_system_id: str = None,
solis_data: pd.DataFrame = None
solis_data: pd.DataFrame = None,
givenergy_data: pd.DataFrame = None
):
if ts is None:
ts = pd.Timestamp.now().round("15min")
Expand All @@ -174,7 +179,8 @@ def predict_ocf(

nwp_xr = get_nwp(site=site, ts=ts, nwp_source=nwp_source)
pv_xr = make_pv_data(
site=site, ts=ts, access_token=access_token, enphase_system_id=enphase_system_id, solis_data=solis_data
site=site, ts=ts, access_token=access_token, enphase_system_id=enphase_system_id,
solis_data=solis_data, givenergy_data=givenergy_data
)

pred_df = forecast_v1_tilt_orientation(nwp_source, nwp_xr, pv_xr, ts, model=model)
Expand All @@ -187,10 +193,11 @@ def run_forecast(
nwp_source: str = "icon",
access_token: str = None,
enphase_system_id: str = None,
solis_data: pd.DataFrame = None
solis_data: pd.DataFrame = None,
givenergy_data: pd.DataFrame = None
) -> pd.DataFrame:
if model == "gb":
return predict_ocf(site, None, ts, nwp_source, access_token, enphase_system_id, solis_data)
return predict_ocf(site, None, ts, nwp_source, access_token, enphase_system_id, solis_data, givenergy_data)
elif model == "xgb":
return predict_tryolabs(site, ts)
else:
Expand All @@ -200,7 +207,8 @@ def fetch_data_and_run_forecast(
site: PVSite,
access_token: str = None,
enphase_system_id: str = None,
solis_data: pd.DataFrame = None
solis_data: pd.DataFrame = None,
givenergy_data: pd.DataFrame = None
):
with st.spinner("Running forecast..."):
try:
Expand All @@ -216,7 +224,8 @@ def fetch_data_and_run_forecast(
ts=ts,
access_token=access_token,
enphase_system_id=enphase_system_id,
solis_data=solis_data
solis_data=solis_data,
givenergy_data=givenergy_data
)

# Create a site without inverter for comparison
Expand Down Expand Up @@ -255,11 +264,12 @@ def fetch_data_and_run_forecast(
longitude = st.sidebar.number_input("Longitude", min_value=-180.0, max_value=180.0, value=-1.25, step=0.01)
capacity_kwp = st.sidebar.number_input("Capacity (kWp)", min_value=0.1, value=1.25, step=0.01)

inverter_type = st.sidebar.selectbox("Select Inverter", ["No Inverter", "Enphase", "Solis"])
inverter_type = st.sidebar.selectbox("Select Inverter", ["No Inverter", "Enphase", "Solis", "GivEnergy"])

access_token = None
enphase_system_id = None
solis_data = None
givenergy_data = None

if inverter_type == "Enphase":
if "access_token" not in st.session_state:
Expand All @@ -268,8 +278,6 @@ def fetch_data_and_run_forecast(
access_token, enphase_system_id = st.session_state["access_token"], os.getenv(
"ENPHASE_SYSTEM_ID"
)
elif inverter_type == "Solis":
solis_data = SolisData()

if st.sidebar.button("Run Forecast"):
if inverter_type == "Enphase" and (access_token is None or enphase_system_id is None):
Expand All @@ -295,6 +303,11 @@ def fetch_data_and_run_forecast(
predictions_df, ts = fetch_data_and_run_forecast(
site, solis_data=solis_df
)
elif inverter_type == "GivEnergy":
givenergy_df = get_givenergy_data()
predictions_df, ts = fetch_data_and_run_forecast(
site, givenergy_data=givenergy_df
)
else:
predictions_df, ts = fetch_data_and_run_forecast(site)

Expand Down
4 changes: 2 additions & 2 deletions examples/inverter_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ def main(save_outputs: bool = False):
timestamp_str = datetime.fromtimestamp(timestamp, tz=timezone.utc).strftime('%Y-%m-%d %H:%M:%S')
ts = pd.to_datetime(timestamp_str)

# make input data with live enphase or solis data
site_live = PVSite(latitude=51.75, longitude=-1.25, capacity_kwp=1.25, inverter_type="solis") # inverter_type="enphase" or "solis"
# make input data with live enphase, solis, or givenergy data
site_live = PVSite(latitude=51.75, longitude=-1.25, capacity_kwp=1.25, inverter_type="givenergy") # inverter_type="enphase", "solis", or "givenergy"

# make input data with nan data
site_no_live = PVSite(latitude=51.75, longitude=-1.25, capacity_kwp=1.25)
Expand Down
6 changes: 6 additions & 0 deletions quartz_solar_forecast/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from quartz_solar_forecast.pydantic_models import PVSite
from quartz_solar_forecast.inverters.enphase import get_enphase_data
from quartz_solar_forecast.inverters.solis import get_solis_data
from quartz_solar_forecast.inverters.givenergy import get_givenergy_data

ssl._create_default_https_context = ssl._create_unverified_context

Expand Down Expand Up @@ -200,6 +201,11 @@ def make_pv_data(site: PVSite, ts: pd.Timestamp) -> xr.Dataset:
live_generation_kw = asyncio.run(get_solis_data())
if live_generation_kw is None:
print("Error: Failed to retrieve Solis inverter data.")
elif site.inverter_type == 'givenergy':
try:
live_generation_kw = get_givenergy_data()
except Exception as e:
print(f"Error retrieving GivEnergy data: {str(e)}")
else:
# If no inverter type is specified or not recognized, set live_generation_kw to None
live_generation_kw = None
Expand Down
79 changes: 79 additions & 0 deletions quartz_solar_forecast/inverters/givenergy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import os
import requests
import pandas as pd
from datetime import datetime
from dotenv import load_dotenv

# Load environment variables
load_dotenv()

def get_inverter_serial_number():
"""
Fetch the inverter serial number from the GivEnergy communication device API.
:return: Inverter serial number as a string
"""
api_key = os.getenv('GIVENERGY_API_KEY')

if not api_key:
raise ValueError("GIVENERGY_API_KEY not set in environment variables")

url = 'https://api.givenergy.cloud/v1/communication-device'

headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}

response = requests.get(url, headers=headers)

if response.status_code != 200:
raise Exception(f"Communication device API request failed with status code {response.status_code}")

data = response.json()['data']
if not data:
raise ValueError("No communication devices found")

inverter_serial_number = data[0]['inverter']['serial']
return inverter_serial_number

def get_givenergy_data():
"""
Fetch the latest data from the GivEnergy API and return a DataFrame.
:return: DataFrame with timestamp and power_kw columns
"""
api_key = os.getenv('GIVENERGY_API_KEY')

if not api_key:
raise ValueError("GIVENERGY_API_KEY not set in environment variables")

inverter_serial_number = get_inverter_serial_number()

url = f'https://api.givenergy.cloud/v1/inverter/{inverter_serial_number}/system-data/latest'

headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json',
'Accept': 'application/json'
}

response = requests.get(url, headers=headers)

if response.status_code != 200:
raise Exception(f"System data API request failed with status code {response.status_code}")

data = response.json()['data']

# Process the data
timestamp = datetime.strptime(data['time'], "%Y-%m-%dT%H:%M:%SZ")
power_kw = data['solar']['power'] / 1000 # Convert W to kW

# Create DataFrame
df = pd.DataFrame({
'timestamp': [timestamp],
'power_kw': [power_kw]
})
print(df)
return df
2 changes: 1 addition & 1 deletion quartz_solar_forecast/pydantic_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ class PVSite(BaseModel):
inverter_type: str = Field(
default=None,
description="The type of inverter used",
json_schema_extra=["enphase", "solis", None],
json_schema_extra=["enphase", "solis", "givenergy", None],
)
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ gdown==5.1.0
xgboost==2.0.3
plotly
typer
streamlit
streamlit
async_timeout

0 comments on commit b8dafdd

Please sign in to comment.