Simulatore Pannello Fotovoltaico
This commit is contained in:
243
pvsim/panel.py
Normal file
243
pvsim/panel.py
Normal file
@@ -0,0 +1,243 @@
|
||||
```python
|
||||
"""
|
||||
panel.py
|
||||
|
||||
Modello del singolo pannello fotovoltaico.
|
||||
|
||||
Ogni pannello riceve dal simulatore:
|
||||
|
||||
- Irradianza [W/m²]
|
||||
- Temperatura ambiente [°C]
|
||||
- Timestamp
|
||||
|
||||
e restituisce le principali grandezze elettriche.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from typing import Dict
|
||||
import random
|
||||
import math
|
||||
|
||||
|
||||
@dataclass
|
||||
class PVPanel:
|
||||
|
||||
panel_id: str
|
||||
|
||||
nominal_power: float = 450.0 # W
|
||||
area: float = 2.1 # m²
|
||||
|
||||
vmp: float = 41.5
|
||||
imp: float = 10.85
|
||||
|
||||
voc: float = 49.8
|
||||
isc: float = 11.35
|
||||
|
||||
temp_coeff: float = -0.0035 # -0.35 %/°C
|
||||
|
||||
nominal_temp: float = 25.0
|
||||
|
||||
annual_degradation: float = 0.005
|
||||
|
||||
sensor_noise: float = 0.01
|
||||
|
||||
# stato pannello
|
||||
enabled: bool = True
|
||||
soiling_factor: float = 1.0
|
||||
degradation: float = 1.0
|
||||
|
||||
total_energy_Wh: float = 0.0
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def module_temperature(
|
||||
self,
|
||||
ambient_temperature: float,
|
||||
irradiance: float
|
||||
) -> float:
|
||||
"""
|
||||
Stima della temperatura del modulo.
|
||||
|
||||
Formula semplificata:
|
||||
Tmodule = Tamb + irradiance * 0.03
|
||||
"""
|
||||
|
||||
return ambient_temperature + irradiance * 0.03
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def degradation_factor(
|
||||
self,
|
||||
years: float
|
||||
) -> float:
|
||||
|
||||
return (1.0 - self.annual_degradation) ** years
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def compute_power(
|
||||
self,
|
||||
irradiance: float,
|
||||
ambient_temperature: float,
|
||||
years_from_start: float
|
||||
) -> float:
|
||||
|
||||
if not self.enabled:
|
||||
return 0.0
|
||||
|
||||
module_temp = self.module_temperature(
|
||||
ambient_temperature,
|
||||
irradiance
|
||||
)
|
||||
|
||||
temp_factor = (
|
||||
1 +
|
||||
self.temp_coeff *
|
||||
(module_temp - self.nominal_temp)
|
||||
)
|
||||
|
||||
degr = self.degradation_factor(years_from_start)
|
||||
|
||||
power = (
|
||||
self.nominal_power *
|
||||
(irradiance / 1000.0) *
|
||||
temp_factor *
|
||||
degr *
|
||||
self.soiling_factor
|
||||
)
|
||||
|
||||
power = max(power, 0.0)
|
||||
|
||||
noise = random.gauss(1.0, self.sensor_noise)
|
||||
|
||||
return power * noise
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def compute_voltage(
|
||||
self,
|
||||
power: float
|
||||
) -> float:
|
||||
|
||||
if power <= 0:
|
||||
return 0.0
|
||||
|
||||
voltage = self.vmp * (
|
||||
0.98 + 0.04 * random.random()
|
||||
)
|
||||
|
||||
return voltage
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def compute_current(
|
||||
self,
|
||||
power: float,
|
||||
voltage: float
|
||||
) -> float:
|
||||
|
||||
if voltage <= 0:
|
||||
return 0.0
|
||||
|
||||
return power / voltage
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def update(
|
||||
self,
|
||||
timestamp: datetime,
|
||||
irradiance: float,
|
||||
ambient_temperature: float,
|
||||
years_from_start: float,
|
||||
timestep_minutes: float
|
||||
) -> Dict:
|
||||
|
||||
power = self.compute_power(
|
||||
irradiance,
|
||||
ambient_temperature,
|
||||
years_from_start
|
||||
)
|
||||
|
||||
voltage = self.compute_voltage(power)
|
||||
|
||||
current = self.compute_current(
|
||||
power,
|
||||
voltage
|
||||
)
|
||||
|
||||
module_temp = self.module_temperature(
|
||||
ambient_temperature,
|
||||
irradiance
|
||||
)
|
||||
|
||||
energy = power * timestep_minutes / 60.0
|
||||
|
||||
self.total_energy_Wh += energy
|
||||
|
||||
return {
|
||||
|
||||
"timestamp": timestamp,
|
||||
|
||||
"panel_id": self.panel_id,
|
||||
|
||||
"irradiance_Wm2": irradiance,
|
||||
|
||||
"ambient_temperature_C": ambient_temperature,
|
||||
|
||||
"module_temperature_C": module_temp,
|
||||
|
||||
"voltage_V": voltage,
|
||||
|
||||
"current_A": current,
|
||||
|
||||
"power_W": power,
|
||||
|
||||
"energy_Wh": energy,
|
||||
|
||||
"total_energy_Wh": self.total_energy_Wh,
|
||||
|
||||
"enabled": self.enabled,
|
||||
|
||||
"soiling_factor": self.soiling_factor,
|
||||
|
||||
"degradation_factor":
|
||||
self.degradation_factor(years_from_start)
|
||||
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def fail(self):
|
||||
|
||||
self.enabled = False
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def repair(self):
|
||||
|
||||
self.enabled = True
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def soil(self, loss=0.9):
|
||||
|
||||
self.soiling_factor = loss
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def clean(self):
|
||||
|
||||
self.soiling_factor = 1.0
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def __repr__(self):
|
||||
|
||||
return (
|
||||
f"PVPanel("
|
||||
f"{self.panel_id}, "
|
||||
f"{self.nominal_power}W)"
|
||||
)
|
||||
Reference in New Issue
Block a user