@@ -1348,7 +1348,7 @@ def fetch_sensor_data(self):
13481348 self .cost_today_sofar , self .carbon_today_sofar = self .today_cost (self .import_today , self .export_today , self .car_charging_energy , self .load_minutes )
13491349
13501350 # Fetch PV forecast if enabled, today must be enabled, other days are optional
1351- self .pv_forecast_minute , self .pv_forecast_minute10 = self .fetch_pv_forecast ()
1351+ self .pv_forecast_minute , self .pv_forecast_minute10 = self .fetch_pv_forecast_from_entities ()
13521352
13531353 # Apply modal filter to historical data
13541354 if self .load_minutes and not self .load_forecast_only :
@@ -2297,3 +2297,70 @@ def load_car_energy(self, now_utc):
22972297 else :
22982298 self .log ("Car charging hold {} threshold {}" .format (self .car_charging_hold , self .car_charging_threshold * 60.0 ))
22992299 return self .car_charging_energy
2300+
2301+ def fetch_pv_forecast_from_entities (self ):
2302+ """
2303+ Fetch PV forecast data from configured entities following component architecture
2304+ """
2305+ pv_forecast_minute = {}
2306+ pv_forecast_minute10 = {}
2307+ found_data = False
2308+
2309+ # Read from configured entities - this is the proper way
2310+ forecast_config_keys = ["pv_forecast_today" , "pv_forecast_tomorrow" , "pv_forecast_d3" , "pv_forecast_d4" ]
2311+
2312+ for config_key in forecast_config_keys :
2313+ configured_entity = self .get_arg (config_key , None )
2314+ if configured_entity :
2315+ # Try with forecast attribute first (newer format)
2316+ data = self .get_state_wrapper (entity_id = configured_entity , attribute = "forecast" )
2317+ if not data :
2318+ # Try with detailedForecast attribute for older Solcast HA integrations
2319+ data = self .get_state_wrapper (entity_id = configured_entity , attribute = "detailedForecast" )
2320+
2321+ if data :
2322+ found_data = True
2323+ self .log ("Found PV forecast data in configured entity {} for {}" .format (configured_entity , config_key ))
2324+ self ._process_forecast_data_to_minutes (data , pv_forecast_minute , pv_forecast_minute10 )
2325+
2326+ if not found_data :
2327+ self .log ("Warn: No PV forecast data found in configured entities. Check pv_forecast_today, pv_forecast_tomorrow etc. in apps.yaml" )
2328+
2329+ return pv_forecast_minute , pv_forecast_minute10
2330+
2331+ def _process_forecast_data_to_minutes (self , forecast_data , pv_forecast_minute , pv_forecast_minute10 ):
2332+ """
2333+ Process forecast data from entity into minute-level dictionaries
2334+ """
2335+ if not forecast_data :
2336+ return
2337+
2338+ for entry in forecast_data :
2339+ if "period_start" not in entry :
2340+ continue
2341+
2342+ try :
2343+ # Parse period start time
2344+ period_start_str = entry .get ("period_start" , "" )
2345+ if isinstance (period_start_str , str ):
2346+ period_start = str2time (period_start_str )
2347+ else :
2348+ continue
2349+
2350+ # Calculate minutes from midnight UTC
2351+ minutes = int ((period_start - self .midnight_utc ).total_seconds () / 60 )
2352+
2353+ # Get estimates
2354+ pv_estimate = entry .get ("pv_estimate" , 0 )
2355+ pv_estimate10 = entry .get ("pv_estimate10" , pv_estimate * 0.1 ) # Default to 10% if not present
2356+
2357+ # Spread 30-minute forecast over individual minutes
2358+ for minute_offset in range (30 ):
2359+ minute = minutes + minute_offset
2360+ if minute >= 0 : # Only positive minutes
2361+ pv_forecast_minute [minute ] = pv_forecast_minute .get (minute , 0 ) + pv_estimate / 30
2362+ pv_forecast_minute10 [minute ] = pv_forecast_minute10 .get (minute , 0 ) + pv_estimate10 / 30
2363+
2364+ except (ValueError , TypeError ) as e :
2365+ self .log ("Warn: Error processing forecast entry {}: {}" .format (entry , e ))
2366+ continue
0 commit comments