[PROJEKT] Zaawansowana Optymalizacja PV (Cz. 1): Estymacja ukrytego potencjału mocy

Cześć wszystkim! :sun:

Chciałbym podzielić się z Wami rozwiązaniem, nad którym ostatnio pracowałem w ramach optymalizacji autokonsumpcji. Jeśli temat Was zainteresuje, w przyszłości postaram się opisać kolejne kroki i automatyzacje budowane na tych danych.

:magnifying_glass_tilted_left: Problem: Skąd wiedzieć ile mocy idzie w niebo?

Większość z nas opiera automatyzacje na aktualnej produkcji. Jednak gdy magazyn energii jest pełny (SoC = 100%) lub eksport do sieci jest ograniczony (lub wyłączony​:trophy:) inwerter celowo schodzi z punktu mocy maksymalnej (MPPT).

Wtedy produkcja na wykresie spada, a napięcie na stringach rośnie w stronę napięcia jałowego (Voc). Standardowy sensr pokaże np. 200W poboru własnego mimo że słońce praży i moglibyście właśnie zasilać klimę za darmo. Bez drogieo pyranometru (czujnika nasłonecznienia) trudno ocenić ten potencjał.

:brain: Rozwiązanie: Dynamiczny Model Matematyczny (Beta)

Przygotowałem algorytm, który analizuje parametry elektryczne stringów i warunki otoczenia, by oszacować realną dostępną nadwyżkę.

Co bierze pod uwagę kod?

  1. Fizykę ogniwa i temperaturę: Moc paneli spada wraz z ich nagrzewaniem. Kod stosuje korektę o współczynnik temperaturowy rozróżniając montaż na gruncie (lepszy przewiew) od dachu.
  2. Korektę Air Mass: Uwzględnia elewację słońca – przy niskim kącie atmosfera silniej tłumi promieniowanie co kod przelicza na realny współczynnik AM.
  3. Modelowanie krzywej I-V: Wykorzystuje uproszczony model kwadratowy, by na podstawie wzrostu napięcia względem optymalnego Vmp oszacować, ile mocy inwerter aktualnie marnuje.

:white_check_mark: Wymagania wstępne

Zanim przejdziesz do implementacji upewnij się, że Twój system integruje następujące dane:

  • Sensory napięcia (V) i mocy (W) DC osobno dla każdego stringu (pobierane z inwertera).
  • Pozycja słońca (np. elewacja słońca z integracji Sunlight Visualizer lub natywnej integracji Sun).
  • Dane pogodowe (temperatura zewnętrzna oraz zachmurzenie w %).
  • Stan naładowania baterii (SoC) – jeśli posiadaszmagazyn energii.

:satellite_antenna: Źródła danych i sensory

W moim kodzie korzystam z dwóch świetnych integracji do pobierania danych środowiskowych:

Oczywiście nic nie stoi na przeszkodzie abyście użyli własnych sensorów (np. z lokalnej stacji pogodowej Zigbee/WiFi czy integracji Weather) o ile dostarczają one temperatury i nasłonecznienia/zachmurzenia.


:hammer_and_wrench: Implementacja: Home Assistant (Jinja2)

W tym miejscu należy wkleić kod sensora typu template. Możecie go dodać bezpośrednio w pliku configuration.yaml, a czy przez UI moża dodać, szczerze nie wiem. Jest opcja dodanie przez UI (Pomocnicy → Template → Sensor) choć przy tak złożonej logice configuration.yaml pozostaje czytelniejszy.

Wskazówka: Wartości Vmp i Voc wpisujemy dla pojedynczego modułu – kod sam przemnoży to przez liczbę paneli. To ułatwia sprawę bo te dane najłatwiej spisać prosto z naklejki na plecach panelu lub z karty katalogowej.

template:
  - sensor:
      - name: "Dostępna Nadwyżka PV (Estymacja)"
        unique_id: pv_potential_universal_v1
        unit_of_measurement: "W"
        device_class: power
        state: >
          {# --- 1. WEJŚCIA (Podmień na swoje encje lub stację pogodową) --- #}
          {% set s_elev = states('sensor.sun_solar_elevation') | float(0) %}
          {% set s_temp = states('sensor.weather_imgw_temperature') | float(15) %}
          {% set s_clouds = states('sensor.weather_imgw_cloud_coverage') | float(0) %}
          {% set s_soc = states('sensor.deye_battery_soc') | float(100) %}
          
          {# --- 2. DANE TWOICH PANELI (Z naklejki pojedynczego panela) --- #}
          {% set mppt_strings = [
            {
              'name': 'Południe Grunt',
              'v_now': states('sensor.pv_voltage_1') | float(0),
              'p_now': states('sensor.pv_power_1') | float(0),
              'vmp_stc': 31.18,
              'voc_stc': 37.15,
              'ilosc_paneli': 12,
              'typ_montazu': 'grunt'
            },
            {
              'name': 'Zachód Dach',
              'v_now': states('sensor.pv_voltage_2') | float(0),
              'p_now': states('sensor.pv_power_2') | float(0),
              'vmp_stc': 30.50,
              'voc_stc': 37.05,
              'ilosc_paneli': 6,
              'typ_montazu': 'dach'
            }
          ] %}

          {# --- 3. LOGIKA I BUFORY --- #}
          {% set t_coeff = -0.0027 %} {# Współczynnik Pmax/°C #}
          {% set sprawnosc_inv = 0.96 %}
          {% set buffer = 150 if s_soc < 95 else 50 %}

          {% if s_elev < 2 %} 0
          {% else %}
            {% set am_factor = [0.85, (s_elev / 20)] | min if s_elev < 20 else 1.0 %}
            {% set ns = namespace(total_pot=0, current_p=0) %}
            
            {% for s in mppt_strings %}
              {# Model temperatury ogniwa: +18K dla gruntu, +28K dla dachu względem otoczenia #}
              {% set t_cell = s_temp + (18 if s.typ_montazu == 'grunt' else 28) * (1 - s_clouds/100) %}
              {% set vmp_t = s.ilosc_paneli * s.vmp_stc * (1 + t_coeff * (t_cell - 25)) %}
              {% set voc_t = s.ilosc_paneli * s.voc_stc * (1 + t_coeff * (t_cell - 25)) %}
              
              {# Estymacja ukrytej mocy na podstawie napięcia DC #}
              {% set p_pot = (s.p_now / (1 - ([(s.v_now - vmp_t) / ([1, voc_t - vmp_t]|max), 0.93]|min)**2)) if s.v_now > vmp_t + 2 else s.p_now %}
              
              {% set ns.total_pot = ns.total_pot + p_pot %}
              {% set ns.current_p = ns.current_p + s.p_now %}
            {% endfor %}

            {% set result = (ns.total_pot * sprawnosc_inv * am_factor) - ns.current_p - buffer %}
            {{ [0, result] | max | round(0) }}
          {% endif %}

:laptop: Alternatywa: JavaScript (NR / Logic)

Dla osób preferujących logikę w JS lub NodeRed przygotowałem również funkcję, którą możecie wykorzystać w swoich flowach.

function calculatePVSurplus(weather, mpptStrings) {
    if (weather.elev < 2) return 0;
    
    const tCoeff = -0.0027;
    const invEff = 0.96;
    const amFactor = weather.elev < 20 ? Math.min(0.85, weather.elev / 20) : 1.0;
    
    let totalPot = 0;
    let currentP = 0;

    mpptStrings.forEach(s => {
        const tCell = weather.temp + (s.type === 'ground' ? 18 : 28) * (1 - weather.clouds / 100);
        const vmpT = s.count * s.vmpStc * (1 + tCoeff * (tCell - 25));
        const vocT = s.count * s.vocStc * (1 + tCoeff * (tCell - 25));

        let pPot = s.pNow;
        if (s.vNow > vmpT + 2) {
            const ratio = Math.min((s.vNow - vmpT) / Math.max(1, vocT - vmpT), 0.93);
            pPot = s.pNow / (1 - Math.pow(ratio, 2));
        }
        totalPot += pPot;
        currentP += s.pNow;
    });

    const buffer = weather.soc < 95 ? 150 : 50;
    return Math.round(Math.max(0, (totalPot * invEff * amFactor) - currentP - buffer));
}

:chart_increasing: Kalibracja i precyzja danych

:warning: Ważna uwaga: Dokładność tego sensora jest bezpośrednio uzależniona od jakości danych wejściowych. Dane o zachurzeniu z IMGW czy pozycja słońca z Sunlight Visualizer są estymacjami – chmura nad stacją meteo nie zawsze oznacza chmurę nad Twoim dachem. Różnice rzędu 100-200W są całkowicie normalne i wynikają z bezwładności systemów pogodowych.

Jak sprawdzić czy to działa?

  • W słoneczny dzień, gdy magazyn jest pełny zanotuj wartość Dostępnej Nadwyżki.
  • Włącz duże obciążenie (np. zajnik 2kW).
  • Wartość sensora powinna spaść o około 2000W (blisko zera). Jeśli zostaje na dużym plusie lub spada głęboko poniżej zera skoryguj współczynniki sprawności lub bufory w kodzie.

:warning: Uwagi praktyczne

  1. Histereza: Jeśli planujecie używać tego sensora do załączania urządzeń, dodajcie w automatyzacji warunek czasu (np. nadwyżka powyżej 1000W przez przynajmniej 2 minuty). Zapobiegnie to “taktowaniu” przekaźników przy przemykających chmurach.
  2. Status Beta: Algorytm jest w fazie testów. Wyniki mogą się różnić w zależności od technologii ogniw.

Jeśli projekt się sprawdzi, w przyszłości postaram się pokazać, jak zintegrować te dane oraz postaram się zrobić trochę trudniejszy projekt :shushing_face:

Dajcie znać, jak to u Was wygląda.:sun_with_face: :battery:

3 polubienia