@@ -72,6 +72,13 @@ func (s *MinerScheduler) RunMPCOptimize(ctx context.Context) error {
7272 return nil
7373 }
7474
75+ // Step 4.1: Add battery temperature to all decisions
76+ // Use current temperature as baseline (future temperature forecasting could be added later)
77+ batteryTemp := plantInfo .ESSAvgCellTemperature
78+ for i := range decisions {
79+ decisions [i ].BatteryAvgCellTemp = batteryTemp
80+ }
81+
7582 // Step 5: Save optimization results to memory
7683 s .mu .Lock ()
7784 s .mpcDecisions = decisions
@@ -216,14 +223,15 @@ func (s *MinerScheduler) buildMPCForecast(ctx context.Context, config *Config, p
216223 loadForecast := s .estimateLoadForecast (importPrice * 1000.0 , config .PriceLimit / 1000 , solar , config )
217224
218225 timeSlots = append (timeSlots , mpc.TimeSlot {
219- Hour : i , // Now represents time slot index, not hour
220- Timestamp : futureTime .Unix (),
221- ImportPrice : importPrice ,
222- ExportPrice : exportPrice ,
223- SolarForecast : solar ,
224- LoadForecast : loadForecast ,
225- CloudCoverage : weather .CloudCoverage ,
226- WeatherSymbol : weather .WeatherSymbol ,
226+ Hour : i , // Now represents time slot index, not hour
227+ Timestamp : futureTime .Unix (),
228+ ImportPrice : importPrice ,
229+ ExportPrice : exportPrice ,
230+ SolarForecast : solar ,
231+ LoadForecast : loadForecast ,
232+ CloudCoverage : weather .CloudCoverage ,
233+ WeatherSymbol : weather .WeatherSymbol ,
234+ AirTemperature : weather .AirTemperature ,
227235 })
228236 }
229237
@@ -232,8 +240,9 @@ func (s *MinerScheduler) buildMPCForecast(ctx context.Context, config *Config, p
232240
233241// WeatherData represents weather information for a specific hour
234242type WeatherData struct {
235- CloudCoverage float64 // % cloud coverage (0-100)
236- WeatherSymbol string // weather condition symbol
243+ CloudCoverage float64 // % cloud coverage (0-100)
244+ WeatherSymbol string // weather condition symbol
245+ AirTemperature float64 // °C air temperature
237246}
238247
239248// getSolarForecast gets solar power forecast from weather data
@@ -254,11 +263,12 @@ func (s *MinerScheduler) getSolarForecast(config *Config, now time.Time, weather
254263
255264 for i := range 36 {
256265 futureTime := now .Add (time .Duration (i ) * time .Hour )
257- solarPower , cloudCoverage , weatherSymbol := s .estimateSolarPowerFromWeather (weatherForecast , futureTime , config .MaxSolarPower , currentPVPower )
266+ solarPower , cloudCoverage , weatherSymbol , airTemp := s .estimateSolarPowerFromWeather (weatherForecast , futureTime , config .MaxSolarPower , currentPVPower )
258267 solarForecast [i ] = solarPower
259268 weatherData [i ] = WeatherData {
260- CloudCoverage : cloudCoverage ,
261- WeatherSymbol : weatherSymbol ,
269+ CloudCoverage : cloudCoverage ,
270+ WeatherSymbol : weatherSymbol ,
271+ AirTemperature : airTemp ,
262272 }
263273 }
264274 solarForecast [0 ] = currentPVPower
@@ -293,12 +303,13 @@ func (s *MinerScheduler) getOrFetchWeatherForecast(config *Config) (*meteo.METJS
293303}
294304
295305// estimateSolarPowerFromWeather estimates solar power output from weather data
296- func (s * MinerScheduler ) estimateSolarPowerFromWeather (forecast * meteo.METJSONForecast , targetTime time.Time , peakPower float64 , currentPVPower float64 ) (float64 , float64 , string ) {
306+ func (s * MinerScheduler ) estimateSolarPowerFromWeather (forecast * meteo.METJSONForecast , targetTime time.Time , peakPower float64 , currentPVPower float64 ) (float64 , float64 , string , float64 ) {
297307 cloudCoverage := 0.0
298308 weatherSymbol := ""
309+ airTemperature := 0.0
299310
300311 if forecast .Properties == nil || len (forecast .Properties .Timeseries ) == 0 {
301- return 0 , cloudCoverage , weatherSymbol
312+ return 0 , cloudCoverage , weatherSymbol , airTemperature
302313 }
303314
304315 // Find closest time step
@@ -317,7 +328,7 @@ func (s *MinerScheduler) estimateSolarPowerFromWeather(forecast *meteo.METJSONFo
317328 }
318329
319330 if closestStep == nil || closestStep .Data == nil || closestStep .Data .Instant == nil || closestStep .Data .Instant .Details == nil {
320- return 0 , cloudCoverage , weatherSymbol
331+ return 0 , cloudCoverage , weatherSymbol , airTemperature
321332 }
322333
323334 details := closestStep .Data .Instant .Details
@@ -332,6 +343,11 @@ func (s *MinerScheduler) estimateSolarPowerFromWeather(forecast *meteo.METJSONFo
332343 weatherSymbol = string (* symbol )
333344 }
334345
346+ // Get air temperature
347+ if details .AirTemperature != nil {
348+ airTemperature = * details .AirTemperature
349+ }
350+
335351 // Get location from config
336352 config := s .GetConfig ()
337353 lat := config .Latitude
@@ -344,7 +360,7 @@ func (s *MinerScheduler) estimateSolarPowerFromWeather(forecast *meteo.METJSONFo
344360
345361 // Check if we're between sunrise and sunset
346362 if targetTime .Before (sunrise ) || targetTime .After (sunset ) {
347- return 0 , cloudCoverage , weatherSymbol // No sun available
363+ return 0 , cloudCoverage , weatherSymbol , airTemperature // No sun available
348364 }
349365
350366 // Get solar position to calculate altitude angle
@@ -356,14 +372,14 @@ func (s *MinerScheduler) estimateSolarPowerFromWeather(forecast *meteo.METJSONFo
356372 // Use sine of altitude as a factor (0 at horizon, 1 at zenith)
357373 solarAngleFactor := math .Sin (altitude )
358374 if solarAngleFactor < 0 {
359- return 0 , cloudCoverage , weatherSymbol
375+ return 0 , cloudCoverage , weatherSymbol , airTemperature
360376 }
361377
362378 // Check for snow conditions - PV panels covered by snow produce zero power
363379 if symbol := closestStep .GetSymbolCode (); symbol != nil {
364380 if symbol .HasSnow () {
365381 s .logger .Printf ("Snow detected in weather forecast at %s, setting solar power to zero" , targetTime .Format (time .RFC3339 ))
366- return 0 , cloudCoverage , weatherSymbol
382+ return 0 , cloudCoverage , weatherSymbol , airTemperature
367383 }
368384 }
369385
@@ -373,7 +389,7 @@ func (s *MinerScheduler) estimateSolarPowerFromWeather(forecast *meteo.METJSONFo
373389 if currentPVPower < 0.1 && expectedPower > 1.0 && time .Until (targetTime ).Hours () < 1 {
374390 // Current power is essentially zero but we expect power - likely snow covered
375391 s .logger .Printf ("Current PV power is zero (%.2f kW) but forecast expects %.2f kW - panels may be snow covered" , currentPVPower , expectedPower )
376- return 0 , cloudCoverage , weatherSymbol
392+ return 0 , cloudCoverage , weatherSymbol , airTemperature
377393 }
378394
379395 // Cloud factor (0-1, where 1 = no clouds)
@@ -386,7 +402,7 @@ func (s *MinerScheduler) estimateSolarPowerFromWeather(forecast *meteo.METJSONFo
386402 // Estimate solar power
387403 solarPower := peakPower * solarAngleFactor * cloudFactor
388404
389- return solarPower , cloudCoverage , weatherSymbol
405+ return solarPower , cloudCoverage , weatherSymbol , airTemperature
390406}
391407
392408// estimateLoadForecast estimates power load based on price and available power
0 commit comments