Skip to content

Commit b8dafdd

Browse files
authored
givenergy (#165)
* givenergy * run fc update * latest inv * refactor * use serial number from api
1 parent 63a50e5 commit b8dafdd

File tree

7 files changed

+116
-16
lines changed

7 files changed

+116
-16
lines changed

.env.example

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
# User needs to add their Enphase API details
2-
32
ENPHASE_SYSTEM_ID = 'user_enphase_system_id'
43
ENPHASE_CLIENT_ID = 'user_enphase_client_id'
54
ENPHASE_CLIENT_SECRET = 'user_enphase_client_secret'
@@ -8,12 +7,14 @@ ENPHASE_API_KEY = 'user_enphase_api_key'
87
AUTHORIZATION_URL = 'https://api.enphaseenergy.com/oauth/authorize?response_type=code&client_id=ENPHASE_CLIENT_ID'
98

109
# User needs to add their Solis Cloud API details
11-
1210
SOLIS_CLOUD_API_KEY = 'user_solis_account_key'
1311
SOLIS_CLOUD_API_KEY_SECRET = 'user_solis_user_key'
1412
SOLIS_CLOUD_API_URL = 'https://www.soliscloud.com'
1513
SOLIS_CLOUD_API_PORT = '13333'
1614

15+
# User needs to add their GivEnergy API details
16+
GIVENERGY_API_KEY = 'user_givenergy_api_key'
17+
1718
# This section is for OpenMeteo setup
1819

1920
# Docker is used to fetch and store OpenMeteo's open data, targeting temperature_2m, precipitation,

dashboards/dashboard_2/app.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from quartz_solar_forecast.data import get_nwp, process_pv_data
2121
from quartz_solar_forecast.inverters.enphase import process_enphase_data
2222
from quartz_solar_forecast.inverters.solis import SolisData, get_solis_data
23+
from quartz_solar_forecast.inverters.givenergy import get_givenergy_data
2324

2425
# Load environment variables
2526
load_dotenv()
@@ -146,14 +147,17 @@ def make_pv_data(
146147
ts: pd.Timestamp,
147148
access_token: str = None,
148149
enphase_system_id: str = None,
149-
solis_data: pd.DataFrame = None
150+
solis_data: pd.DataFrame = None,
151+
givenergy_data: pd.DataFrame = None
150152
) -> xr.Dataset:
151153
live_generation_kw = None
152154

153155
if site.inverter_type == "enphase" and access_token and enphase_system_id:
154156
live_generation_kw = get_enphase_data(enphase_system_id, access_token)
155157
elif site.inverter_type == "solis" and solis_data is not None:
156158
live_generation_kw = solis_data
159+
elif site.inverter_type == "givenergy" and givenergy_data is not None:
160+
live_generation_kw = givenergy_data
157161

158162
da = process_pv_data(live_generation_kw, ts, site)
159163
return da
@@ -165,7 +169,8 @@ def predict_ocf(
165169
nwp_source: str = "icon",
166170
access_token: str = None,
167171
enphase_system_id: str = None,
168-
solis_data: pd.DataFrame = None
172+
solis_data: pd.DataFrame = None,
173+
givenergy_data: pd.DataFrame = None
169174
):
170175
if ts is None:
171176
ts = pd.Timestamp.now().round("15min")
@@ -174,7 +179,8 @@ def predict_ocf(
174179

175180
nwp_xr = get_nwp(site=site, ts=ts, nwp_source=nwp_source)
176181
pv_xr = make_pv_data(
177-
site=site, ts=ts, access_token=access_token, enphase_system_id=enphase_system_id, solis_data=solis_data
182+
site=site, ts=ts, access_token=access_token, enphase_system_id=enphase_system_id,
183+
solis_data=solis_data, givenergy_data=givenergy_data
178184
)
179185

180186
pred_df = forecast_v1_tilt_orientation(nwp_source, nwp_xr, pv_xr, ts, model=model)
@@ -187,10 +193,11 @@ def run_forecast(
187193
nwp_source: str = "icon",
188194
access_token: str = None,
189195
enphase_system_id: str = None,
190-
solis_data: pd.DataFrame = None
196+
solis_data: pd.DataFrame = None,
197+
givenergy_data: pd.DataFrame = None
191198
) -> pd.DataFrame:
192199
if model == "gb":
193-
return predict_ocf(site, None, ts, nwp_source, access_token, enphase_system_id, solis_data)
200+
return predict_ocf(site, None, ts, nwp_source, access_token, enphase_system_id, solis_data, givenergy_data)
194201
elif model == "xgb":
195202
return predict_tryolabs(site, ts)
196203
else:
@@ -200,7 +207,8 @@ def fetch_data_and_run_forecast(
200207
site: PVSite,
201208
access_token: str = None,
202209
enphase_system_id: str = None,
203-
solis_data: pd.DataFrame = None
210+
solis_data: pd.DataFrame = None,
211+
givenergy_data: pd.DataFrame = None
204212
):
205213
with st.spinner("Running forecast..."):
206214
try:
@@ -216,7 +224,8 @@ def fetch_data_and_run_forecast(
216224
ts=ts,
217225
access_token=access_token,
218226
enphase_system_id=enphase_system_id,
219-
solis_data=solis_data
227+
solis_data=solis_data,
228+
givenergy_data=givenergy_data
220229
)
221230

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

258-
inverter_type = st.sidebar.selectbox("Select Inverter", ["No Inverter", "Enphase", "Solis"])
267+
inverter_type = st.sidebar.selectbox("Select Inverter", ["No Inverter", "Enphase", "Solis", "GivEnergy"])
259268

260269
access_token = None
261270
enphase_system_id = None
262271
solis_data = None
272+
givenergy_data = None
263273

264274
if inverter_type == "Enphase":
265275
if "access_token" not in st.session_state:
@@ -268,8 +278,6 @@ def fetch_data_and_run_forecast(
268278
access_token, enphase_system_id = st.session_state["access_token"], os.getenv(
269279
"ENPHASE_SYSTEM_ID"
270280
)
271-
elif inverter_type == "Solis":
272-
solis_data = SolisData()
273281

274282
if st.sidebar.button("Run Forecast"):
275283
if inverter_type == "Enphase" and (access_token is None or enphase_system_id is None):
@@ -295,6 +303,11 @@ def fetch_data_and_run_forecast(
295303
predictions_df, ts = fetch_data_and_run_forecast(
296304
site, solis_data=solis_df
297305
)
306+
elif inverter_type == "GivEnergy":
307+
givenergy_df = get_givenergy_data()
308+
predictions_df, ts = fetch_data_and_run_forecast(
309+
site, givenergy_data=givenergy_df
310+
)
298311
else:
299312
predictions_df, ts = fetch_data_and_run_forecast(site)
300313

examples/inverter_example.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ def main(save_outputs: bool = False):
1313
timestamp_str = datetime.fromtimestamp(timestamp, tz=timezone.utc).strftime('%Y-%m-%d %H:%M:%S')
1414
ts = pd.to_datetime(timestamp_str)
1515

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

1919
# make input data with nan data
2020
site_no_live = PVSite(latitude=51.75, longitude=-1.25, capacity_kwp=1.25)

quartz_solar_forecast/data.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from quartz_solar_forecast.pydantic_models import PVSite
1616
from quartz_solar_forecast.inverters.enphase import get_enphase_data
1717
from quartz_solar_forecast.inverters.solis import get_solis_data
18+
from quartz_solar_forecast.inverters.givenergy import get_givenergy_data
1819

1920
ssl._create_default_https_context = ssl._create_unverified_context
2021

@@ -200,6 +201,11 @@ def make_pv_data(site: PVSite, ts: pd.Timestamp) -> xr.Dataset:
200201
live_generation_kw = asyncio.run(get_solis_data())
201202
if live_generation_kw is None:
202203
print("Error: Failed to retrieve Solis inverter data.")
204+
elif site.inverter_type == 'givenergy':
205+
try:
206+
live_generation_kw = get_givenergy_data()
207+
except Exception as e:
208+
print(f"Error retrieving GivEnergy data: {str(e)}")
203209
else:
204210
# If no inverter type is specified or not recognized, set live_generation_kw to None
205211
live_generation_kw = None
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import os
2+
import requests
3+
import pandas as pd
4+
from datetime import datetime
5+
from dotenv import load_dotenv
6+
7+
# Load environment variables
8+
load_dotenv()
9+
10+
def get_inverter_serial_number():
11+
"""
12+
Fetch the inverter serial number from the GivEnergy communication device API.
13+
14+
:return: Inverter serial number as a string
15+
"""
16+
api_key = os.getenv('GIVENERGY_API_KEY')
17+
18+
if not api_key:
19+
raise ValueError("GIVENERGY_API_KEY not set in environment variables")
20+
21+
url = 'https://api.givenergy.cloud/v1/communication-device'
22+
23+
headers = {
24+
'Authorization': f'Bearer {api_key}',
25+
'Content-Type': 'application/json',
26+
'Accept': 'application/json'
27+
}
28+
29+
response = requests.get(url, headers=headers)
30+
31+
if response.status_code != 200:
32+
raise Exception(f"Communication device API request failed with status code {response.status_code}")
33+
34+
data = response.json()['data']
35+
if not data:
36+
raise ValueError("No communication devices found")
37+
38+
inverter_serial_number = data[0]['inverter']['serial']
39+
return inverter_serial_number
40+
41+
def get_givenergy_data():
42+
"""
43+
Fetch the latest data from the GivEnergy API and return a DataFrame.
44+
45+
:return: DataFrame with timestamp and power_kw columns
46+
"""
47+
api_key = os.getenv('GIVENERGY_API_KEY')
48+
49+
if not api_key:
50+
raise ValueError("GIVENERGY_API_KEY not set in environment variables")
51+
52+
inverter_serial_number = get_inverter_serial_number()
53+
54+
url = f'https://api.givenergy.cloud/v1/inverter/{inverter_serial_number}/system-data/latest'
55+
56+
headers = {
57+
'Authorization': f'Bearer {api_key}',
58+
'Content-Type': 'application/json',
59+
'Accept': 'application/json'
60+
}
61+
62+
response = requests.get(url, headers=headers)
63+
64+
if response.status_code != 200:
65+
raise Exception(f"System data API request failed with status code {response.status_code}")
66+
67+
data = response.json()['data']
68+
69+
# Process the data
70+
timestamp = datetime.strptime(data['time'], "%Y-%m-%dT%H:%M:%SZ")
71+
power_kw = data['solar']['power'] / 1000 # Convert W to kW
72+
73+
# Create DataFrame
74+
df = pd.DataFrame({
75+
'timestamp': [timestamp],
76+
'power_kw': [power_kw]
77+
})
78+
print(df)
79+
return df

quartz_solar_forecast/pydantic_models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@ class PVSite(BaseModel):
2222
inverter_type: str = Field(
2323
default=None,
2424
description="The type of inverter used",
25-
json_schema_extra=["enphase", "solis", None],
25+
json_schema_extra=["enphase", "solis", "givenergy", None],
2626
)

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ gdown==5.1.0
1111
xgboost==2.0.3
1212
plotly
1313
typer
14-
streamlit
14+
streamlit
15+
async_timeout

0 commit comments

Comments
 (0)