blackscreener

Holtop ERV esphome logger

Jul 23rd, 2025 (edited)
1,846
0
Never
5
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
YAML 92.58 KB | Source Code | 0 0
  1. esphome:
  2.   name: $devicename
  3.   #on_boot:
  4.   #  then:
  5.     #  - pzemac.reset_energy: pzemac_1
  6.  
  7. esp32:
  8.   board: nodemcu-32s
  9.   #board_flash_mode: dout
  10.   framework:
  11.     type: esp-idf  
  12.    
  13. debug:
  14.   update_interval: 60s
  15.  
  16. substitutions:
  17.   devicename: reku-logger
  18.   friendly_name: Rekuperator
  19.  
  20. wifi:
  21.   ssid: !secret wifi_ssid
  22.   password: !secret wifi_pass
  23.  
  24.   # Enable fallback hotspot (captive portal) in case wifi connection fails
  25.   ap:
  26.     ssid: "sweet-fallback-${devicename}"
  27.     password: cvbcbcbcvbdfbgte4
  28.  
  29. captive_portal:
  30. logger:
  31.   baud_rate: 0
  32.   level: DEBUG
  33.  
  34. # Enable Home Assistant API
  35. api:
  36.  #password: !secret api-password
  37.  
  38. ota:
  39.   - platform: esphome
  40.  
  41. # Enable Web server
  42. #web_server:
  43. # port: 80
  44. i2c:
  45.   - id: bus_a
  46.     sda: 21
  47.     scl: 22
  48.     scan: false
  49.    # frequency: 50kHz
  50.   - id: bus_b
  51.     sda: 18
  52.     scl: 19
  53.     scan: false
  54.   #  frequency: 50kHz
  55.  
  56.  
  57. uart:
  58.   - id: mod_bus #reku
  59.     tx_pin: 17
  60.     rx_pin: 16
  61.     baud_rate: 9600
  62.     stop_bits: 1
  63.     data_bits: 8
  64.     parity: NONE
  65.  
  66.   - id: mod_bus2 #pzem
  67.     tx_pin: 1
  68.     rx_pin: 3
  69.     baud_rate: 9600
  70.     stop_bits: 2  #1 dla esp8266
  71.  
  72.  
  73. time:
  74.   - platform: sntp
  75.     id: czas_systemowy
  76.  
  77. # Example configuration entry
  78. button:
  79.   - platform: restart
  80.     name: "ESP Reku Restart"
  81.  
  82. globals:
  83.   - id: potrzeba_osuszania_histereza_state
  84.     type: bool
  85.     restore_value: yes # Ważne: przywróć ostatni stan po restarcie
  86.     initial_value: 'false' # Domyślnie brak potrzeby osuszania
  87.  
  88.   - id: osuszanie_timer_koniec_timestamp
  89.     type: int
  90.     restore_value: yes # Ważne: przywróć stan po restarcie
  91.     initial_value: '0' # 0 oznacza, że timer nie jest aktywny
  92.  
  93.   - id: poprzedni_balans_przeplywu_stan
  94.     type: bool
  95.     initial_value: 'true' # Domyślnie zakładamy, że balans był włączony
  96.     restore_value: yes # Ważne: przywróć stan po restarcie
  97.  
  98.   - id: poprzedni_tryb_global
  99.     type: std::string
  100.     initial_value: '"auto"' # Domyślna wartość początkowa
  101.     restore_value: yes # Ważne! Przywróć stan po restarcie
  102.  
  103.   - id: aktywny_poziom_co2
  104.     type: int
  105.     initial_value: '0'
  106.  
  107.   - id: tryb_wysokiej_wilgotnosci_aktywny
  108.     type: bool
  109.     initial_value: 'false'
  110.  
  111.   - id: przewietrzanie_timer_koniec_timestamp
  112.     type: int
  113.     restore_value: yes
  114.     initial_value: '0'
  115.  
  116.   - id: okap_timer_koniec_timestamp
  117.     type: int
  118.     restore_value: yes # Zachowaj stan po restarcie
  119.     initial_value: '0' # 0 oznacza, ze timer nie jest aktywny
  120.  
  121.   - id: impreza_timer_koniec_timestamp
  122.     type: int
  123.     restore_value: yes
  124.     initial_value: '0' # 0 oznacza, ze timer nie jest aktywny
  125.  
  126.   - id: licznik_korekt
  127.     type: int
  128.     restore_value: no
  129.     initial_value: '0'
  130.  
  131.   - id: ostatni_timestamp_korekty # Nowa zmienna globalna
  132.     type: int
  133.     restore_value: yes # Ważne: przywróć wartość po restarcie, aby opóźnienie działało poprawnie
  134.     initial_value: '0' # Domyślnie 0, co oznacza, że pierwsza korekta może nastąpić od razu po uruchomieniu
  135.  
  136.   - id: limit_korekt_na_godzine
  137.     type: int
  138.     restore_value: no
  139.     initial_value: '10'
  140.  
  141.   - id: stary_nawiew
  142.     type: int
  143.     restore_value: no
  144.     initial_value: '0'
  145.   - id: stary_wywiew
  146.     type: int
  147.     restore_value: no
  148.     initial_value: '0'
  149.  
  150.   # Aktualny tryb z podtypem dla trybu auto
  151.   - id: tryb_auto_status
  152.     type: std::string
  153.     initial_value: '"normal"'
  154.  
  155.   # Aktualny aktywny tryb
  156.   - id: aktywny_tryb
  157.     type: std::string
  158.     initial_value: '"auto"'
  159.  
  160.   - id: ostatnie_obroty
  161.     type: int
  162.     restore_value: no
  163.     initial_value: '0'
  164.  
  165.   - id: czy_przewietrzanie
  166.     type: bool
  167.     restore_value: no
  168.     initial_value: 'false'
  169.  
  170.   - id: poprzedni_balans_przed_zimowym
  171.     type: bool
  172.     restore_value: yes
  173.     initial_value: 'false'
  174.  
  175.   - id: oryginalne_obroty_nawiew
  176.     type: int
  177.     restore_value: no
  178.     initial_value: '3'
  179.  
  180.   - id: oryginalne_obroty_wywiew
  181.     type: int
  182.     restore_value: no
  183.     initial_value: '3'
  184.  
  185.   - id: tryb_zimowy_aktywny
  186.     type: bool
  187.     restore_value: no
  188.     initial_value: 'false'
  189.  
  190. modbus:
  191.   - id: mod_bus_reventon
  192.     uart_id: mod_bus
  193.     disable_crc: False
  194.     send_wait_time: 200ms
  195.     #flow_control_pin: 4
  196.   - id: mod_bus_pzem
  197.  #   send_wait_time: 200ms
  198.     uart_id: mod_bus2
  199.  
  200. modbus_controller:
  201.   - id: reventon_reku
  202.     address: 0x2
  203.     modbus_id: mod_bus_reventon
  204.     update_interval: 15s
  205.     setup_priority: -10
  206.  
  207.   - id: pzem1
  208.     modbus_id: mod_bus_pzem
  209.     address: 0x1
  210.     #command_throttle: 0ms
  211.     setup_priority: -10
  212.     update_interval: 5s
  213.  
  214. text_sensor:
  215.   - platform: homeassistant
  216.     name: "Stan Okapu z HA - Tekst"
  217.     id: okap_stan_tekstowy_ha
  218.     entity_id: fan.fan_hood
  219.     internal: true
  220.  
  221.   - platform: debug
  222.     device:
  223.       name: "Device Info"
  224.     reset_reason:
  225.       name: "Reset Reason"
  226.  
  227.   - platform: template
  228.     name: "Aktywny tryb rekuperatora"
  229.     lambda: |-
  230.       std::string tryb = id(aktywny_tryb);
  231.       if (tryb == "auto" || tryb == "nocny") {
  232.         std::string status = id(tryb_auto_status);
  233.         return status;
  234.       }
  235.       return tryb;
  236.     update_interval: 30s
  237.  
  238.  
  239.   - platform: template
  240.     name: "Ostatni reset korekt"
  241.     id: ostatni_reset_korekt
  242.     lambda: |-
  243.       char str[20];
  244.       time_t curr_time = id(czas_systemowy).now().timestamp;
  245.       strftime(str, sizeof(str), "%H:%M", localtime(&curr_time));
  246.       return std::string(str);
  247.     update_interval: 1h
  248.  
  249.   - platform: template
  250.     name: $friendly_name Uptime
  251.     id: uptime_human
  252.     icon: mdi:clock-start
  253.  
  254.  
  255.  
  256.   - platform: modbus_controller
  257.     modbus_controller_id: reventon_reku
  258.     name: ${friendly_name} Status
  259.     id: reku_status
  260.     register_type: holding
  261.     address: 0x0012 #18
  262.     response_size: 2
  263.     raw_encode: HEXBYTES
  264.     #bitmask: 0
  265.     lambda: |-
  266.        std::string z = "";
  267.        int idx = item->offset+1;
  268.        if ((data[idx] & 0x0001) != 0) z += "Alarm pożarowy ON, ";
  269.        if ((data[idx] & 0x0002) != 0) z += "Bypass ON, ";
  270.        if ((data[idx] & 0x0004) != 0) z += "Bypass OFF, ";
  271.        if ((data[idx] & 0x0008) != 0) z += "Odladzanie, ";
  272.        if(z.length() > 0){
  273.          z.pop_back();
  274.        }
  275.        return {z};
  276.  
  277.   - platform: modbus_controller
  278.     modbus_controller_id: reventon_reku
  279.     name: ${friendly_name} Status2
  280.     id: reku_status2
  281.     register_type: holding
  282.     address: 0x0012 #18
  283.     response_size: 2
  284.     raw_encode: HEXBYTES
  285.  
  286.   - platform: modbus_controller
  287.     modbus_controller_id: reventon_reku
  288.     name: ${friendly_name} Fault Message
  289.     id: reku_fault_message
  290.     register_type: holding
  291.     address: 0x0014 #20
  292.     response_size: 2
  293.     raw_encode: HEXBYTES
  294.  
  295.   - platform: modbus_controller
  296.     modbus_controller_id: reventon_reku
  297.     name: ${friendly_name} Fault Message2
  298.     id: reku_fault_message2
  299.     register_type: holding
  300.     address: 0x0014 #20
  301.     response_size: 2
  302.     #raw_encode: HEXBYTES
  303.     lambda: |-
  304.        std::string z = "";
  305.        int idx = item->offset+1;
  306.        if ((data[idx] & 0x0001) != 0) z += "Błąd czujnika temperatury powietrza zewnętrznego (OA), ";
  307.        if ((data[idx] & 0x0002) != 0) z += "Błąd pamięci EEPROM, ";
  308.        if ((data[idx] & 0x0004) != 0) z += "Błąd czujnika temperatury powietrza wywiewanego (RA) lub SW4-3 jest w pozycji ON, ale bez podłączenia do czujnika wilgotności, ";
  309.        if ((data[idx] & 0x0008) != 0) z += "Błąd czujnika temperatury powietrza usuwanego (EA), ";
  310.        if ((data[idx] & 0x0010) != 0) z += "Błąd komunikacji, ";
  311.        if ((data[idx] & 0x0020) != 0) z += "Błąd czujnika temperatury powietrza nawiewanego (SA), ";
  312.        if ((data[idx] & 0x0040) != 0) z += "Błąd wentylatora wywiewnego, ";
  313.        if ((data[idx] & 0x0080) != 0) z += "Błąd wentylatora nawiewnego, ";
  314.    
  315.        if(z.length() > 0){
  316.          z.pop_back();
  317.        }
  318.        return {z};
  319.  
  320. script:
  321.   - id: sprawdz_ograniczenie_korekt
  322.     mode: single
  323.     parameters:
  324.       korekta_id: int
  325.     then:
  326.       - if:
  327.           condition:
  328.             lambda: return id(licznik_korekt) < id(limit_korekt_na_godzine);
  329.           then:
  330.             - lambda: 'id(licznik_korekt) += 1;'
  331.             - logger.log:
  332.                 format: "Wykonano korektę. Licznik: %d / %d"
  333.                 args:
  334.                  - id(licznik_korekt)
  335.                   - id(limit_korekt_na_godzine)
  336.             - if:
  337.                 condition:
  338.                   lambda: return korekta_id == 1;
  339.                 then:
  340.                   - script.execute: korekta_do_nadcisnienia
  341.             - if:
  342.                 condition:
  343.                   lambda: return korekta_id == 2;
  344.                 then:
  345.                   - script.execute: korekta_do_podcisnienia
  346.  
  347.             # --- DODAJ AKTUALIZACJĘ TIMESTAMPU PO WYKONANIU KOREKTY ---
  348.             - lambda: 'id(ostatni_timestamp_korekty) = id(czas_systemowy).now().timestamp;'
  349.             - logger.log: "Zaktualizowano timestamp ostatniej korekty."
  350.          
  351.           else:
  352.             - logger.log: "Limit korekt na godzinę osiągnięty – korekta zablokowana."
  353.  
  354.   - id: korekta_do_nadcisnienia # Zwiększa wywiew, aby zredukować nadciśnienie (nawiew > wywiew)
  355.     mode: restart
  356.     then:
  357.       - lambda: |-
  358.           const int max_fan_difference = 3;
  359.           int current_nawiew = id(wentylator_nawiewny).state; // Nawiew jest referencją (stała wartość)
  360.           int current_wywiew = id(wentylator_wywiewny).state;
  361.           int new_wywiew = current_wywiew + 1; // Próbujemy zwiększyć wywiew
  362.  
  363.           // Warunki:
  364.          // 1. New_wywiew nie może przekroczyć 10.
  365.           // 2. Różnica (nawiew - new_wywiew) musi być <= max_fan_difference.
  366.           //    (Czyli wywiew może być max o 3 biegi mniejszy od nawiewu)
  367.           if (new_wywiew <= 10 && (current_nawiew - new_wywiew) <= max_fan_difference) {
  368.             id(wentylator_wywiewny).make_call().set_value(new_wywiew).perform();
  369.             ESP_LOGI("Korekta_Nadcisnienia", "Zwiekszono wywiew: wywiew = %d (nawiew = %d). Roznica: %d",
  370.                      new_wywiew, current_nawiew, current_nawiew - new_wywiew);
  371.           } else {
  372.             ESP_LOGI("Korekta_Nadcisnienia", "Korekta nadcisnienia zablokowana: wywiew=%d, nawiew=%d. Roznica %d, limit %d. (Wywiew min/max lub poza roznica)",
  373.                      current_wywiew, current_nawiew, current_nawiew - current_wywiew, max_fan_difference);
  374.           }
  375.  
  376.  
  377.   - id: korekta_do_podcisnienia # Zmniejsza wywiew, aby zredukować podciśnienie (nawiew < wywiew)
  378.     mode: restart
  379.     then:
  380.       - lambda: |-
  381.           const int max_fan_difference = 3;
  382.           int current_nawiew = id(wentylator_nawiewny).state; // Nawiew jest referencją (stała wartość)
  383.           int current_wywiew = id(wentylator_wywiewny).state;
  384.           int new_wywiew = current_wywiew - 1; // Próbujemy zmniejszyć wywiew
  385.  
  386.           // Warunki:
  387.          // 1. New_wywiew nie może schodzić poniżej 0.
  388.           // 2. Różnica (new_wywiew - nawiew) musi być <= max_fan_difference.
  389.           //    (Czyli wywiew może być max o 3 biegi większy od nawiewu)
  390.           if (new_wywiew >= 0 && (new_wywiew - current_nawiew) <= max_fan_difference) {
  391.             id(wentylator_wywiewny).make_call().set_value(new_wywiew).perform();
  392.             ESP_LOGI("Korekta_Podcisnienia", "Zmniejszono wywiew: wywiew = %d (nawiew = %d). Roznica: %d",
  393.                      new_wywiew, current_nawiew, new_wywiew - current_nawiew);
  394.           } else {
  395.             ESP_LOGI("Korekta_Podcisnienia", "Korekta podcisnienia zablokowana: wywiew=%d, nawiew=%d. Roznica %d, limit %d. (Wywiew min/max lub poza roznica)",
  396.                      current_wywiew, current_nawiew, current_wywiew - current_nawiew, max_fan_difference);
  397.           }
  398.  
  399.   - id: wlacz_przewietrzanie
  400.     mode: single
  401.     then:
  402.       - logger.log: "TRYB PRZEWIETRZANIA: aktywacja"
  403.       - switch.turn_off: balans_przeplywu_switch
  404.       - lambda: |-
  405.           id(czy_przewietrzanie) = true;
  406.           id(stary_nawiew) = (int)id(wentylator_nawiewny).state;
  407.           id(stary_wywiew) = (int)id(wentylator_wywiewny).state;
  408.           id(przewietrzanie_timer_koniec_timestamp) = id(czas_systemowy).now().timestamp + (int)id(czas_przewietrzania_minuty).state * 60;
  409.       - number.set:
  410.           id: wentylator_nawiewny
  411.           value: 10
  412.       - number.set:
  413.           id: wentylator_wywiewny
  414.           value: 10
  415.       - lambda: |-
  416.           int czas = (int)id(czas_przewietrzania_minuty).state;
  417.           ESP_LOGI("przewietrzanie", "Czas przewietrzania: %d min", czas);
  418.       - delay: !lambda 'return id(czas_przewietrzania_minuty).state * 60 * 1000;'
  419.       - switch.turn_off: tryb_przewietrzania_switch
  420.  
  421.   - id: zakoncz_przewietrzanie
  422.     mode: single
  423.     then:
  424.       - logger.log: "TRYB PRZEWIETRZANIA: zakończenie – przywracanie poprzednich ustawień"
  425.       - lambda: |-
  426.           id(czy_przewietrzanie) = false;
  427.           id(przewietrzanie_timer_koniec_timestamp) = 0; // Wyzeruj timer
  428.       - number.set:
  429.           id: wentylator_nawiewny
  430.           value: !lambda 'return id(stary_nawiew);'
  431.       - number.set:
  432.           id: wentylator_wywiewny
  433.           value: !lambda 'return id(stary_wywiew);'
  434.       #- switch.turn_on: balans_przeplywu_switch
  435.       - homeassistant.event:
  436.           event: esphome.przewietrzanie_zakonczone
  437.           data:
  438.             message: "Tryb przewietrzania zakończony"
  439.             nawiew: !lambda 'return id(wentylator_nawiewny).state;'
  440.             wywiew: !lambda 'return id(wentylator_wywiewny).state;'
  441.  
  442. #   - id: set_mode
  443. #     mode: single
  444. #     parameters:
  445. #       tryb: std::string
  446. #     then:
  447. #       - lambda: |-
  448. #           id(aktywny_tryb) = tryb;
  449. #           id(tryb_select_esp).publish_state(tryb);
  450. #           ESP_LOGI("tryb", "Ustawiono tryb: %s", tryb.c_str());
  451.  
  452. #       - switch.turn_on: balans_przeplywu_switch
  453.  
  454. #       - if:
  455. #           condition:
  456. #             lambda: return tryb == "nocny";
  457. #           then:
  458. #             - switch.turn_off: balans_przeplywu_switch
  459. #             - number.set:
  460. #                 id: wentylator_nawiewny
  461. #                 value: 2
  462. #             - number.set:
  463. #                 id: wentylator_wywiewny
  464. #                 value: 2
  465.  
  466. #       - if:
  467. #           condition:
  468. #             lambda: return tryb == "intensywny";
  469. #           then:
  470. #             - number.set:
  471. #                 id: wentylator_nawiewny
  472. #                 value: 10
  473. #             - number.set:
  474. #                 id: wentylator_wywiewny
  475. #                 value: 10
  476. #             - delay: 2h
  477. #             - script.execute:
  478. #                 id: set_mode
  479. #                 tryb: "normalny"
  480.  
  481. #       - if:
  482. #           condition:
  483. #             lambda: return tryb == "gosc";
  484. #           then:
  485. #             - number.set:
  486. #                 id: wentylator_nawiewny
  487. #                 value: 10
  488. #             - number.set:
  489. #                 id: wentylator_wywiewny
  490. #                 value: 10
  491. #             - switch.turn_off: balans_przeplywu_switch
  492. #             - delay: 4h
  493. #             - homeassistant.event:
  494. #                 event: esphome.tryb_goscia
  495. #                 data:
  496. #                   message: "Tryb gość zakończony. Powrót do normalnego trybu."
  497. #             - script.execute:
  498. #                 id: set_mode
  499. #                 tryb: "normalny"
  500.  
  501. #       - if:
  502. #           condition:
  503. #             lambda: return tryb == "urlopowy";
  504. #           then:
  505. #             - switch.turn_off: balans_przeplywu_switch
  506. #             - number.set:
  507. #                 id: wentylator_nawiewnymode)$0
  508. #                 value: 1
  509. #             - number.set:
  510. #                 id: wentylator_wywiewny
  511. #                 value: 1
  512.  
  513. #       - if:
  514. #           condition:
  515. #             lambda: return tryb == "serwisowy";
  516. #           then:
  517. #             - switch.turn_off: balans_przeplywu_switch
  518. #             - switch.turn_off: tryb_przewietrzania_switch
  519.  
  520. #       - if:
  521. #           condition:
  522. #             lambda: return tryb == "auto";
  523. #           then:
  524. #             - switch.turn_on: balans_przeplywu_switch
  525.  
  526. #       - if:
  527. #           condition:
  528. #             lambda: return tryb == "normalny";
  529. #           then:
  530. #             - switch.turn_on: balans_przeplywu_switch
  531.  
  532.  
  533. #       - if:
  534. #           condition:
  535. #             lambda: return tryb == "okno";
  536. #           then:
  537. #             - switch.turn_off: balans_przeplywu_switch
  538. #             - number.set:
  539. #                 id: wentylator_nawiewny
  540. #                 value: 1
  541. #             - number.set:
  542. #                 id: wentylator_wywiewny
  543. #                 value: 3
  544. #             - lambda: id(ostatnie_obroty) = 3;
  545.  
  546.   - id: set_mode
  547.     mode: single
  548.     parameters:
  549.       mode: std::string
  550.       target_reku_speed: int
  551.     then:
  552.       - lambda: |-
  553.           std::string new_mode = mode;
  554.           std::string old_mode = id(poprzedni_tryb_global); // Użyj nowej zmiennej globalnej do pobrania poprzedniego trybu
  555.           int obroty_nawiew = 0;
  556.           int obroty_wywiew = 0;
  557.  
  558.           // --- LOGIKA PRZY WYJŚCIU Z TRYBU "OKNO" ---
  559.           // Jeśli poprzedni tryb był "okno", a nowy nie jest, przywróć balans.
  560.           if (old_mode == "okno" && new_mode != "okno") {
  561.             ESP_LOGI("Tryb", "Wychodzenie z trybu OKNO. Przywracanie balansu przepływu.");
  562.             // ZMIEŃ TĘ LINIĘ
  563.             if (id(poprzedni_balans_przeplywu_stan)) { // Sprawdź zapamiętany stan
  564.               id(balans_przeplywu_switch).turn_on(); // Włącz balans, jeśli był włączony
  565.             } else {
  566.               id(balans_przeplywu_switch).turn_off(); // Wyłącz balans, jeśli był wyłączony
  567.             }
  568.           }
  569.  
  570.           if (new_mode == "auto") {
  571.             obroty_nawiew = 3;
  572.             obroty_wywiew = 3;
  573.             ESP_LOGI("set_mode", "Tryb AUTO: Poczatkowe obroty: Nawiew=%d, Wywiew=%d. Dalsza regulacja w interval.", obroty_nawiew, obroty_wywiew);            
  574.             id(reku_on_off_switch).make_call().set_value(true).perform(); // Upewnij sie, ze glowny przelacznik jest ON
  575.             id(impreza_timer_koniec_timestamp) = 0; // Wyzeruj timer imprezy
  576.             // W trybie auto dalsze obroty sa dynamicznie ustalane przez interval: 1min
  577.          
  578.           } else if (new_mode == "nocny") {
  579.             obroty_nawiew = 2;
  580.             obroty_wywiew = 2;
  581.             ESP_LOGI("set_mode", "Tryb NOCNY: Poczatkowe obroty: Nawiew=%d, Wywiew=%d. Dalsza regulacja w interval.", obroty_nawiew, obroty_wywiew);            
  582.             id(reku_on_off_switch).make_call().set_value(true).perform();
  583.             id(impreza_timer_koniec_timestamp) = 0; // Wyzeruj timer imprezy          
  584.           }
  585.  
  586.           // --- LOGIKA PRZY WEJŚCIU W TRYB "OKNO" ---
  587.           // Jeśli nowo wybrany tryb to "okno", zapisz stan balansu i go wyłącz.
  588.           else if (new_mode == "okno") {
  589.             ESP_LOGI("Tryb", "Wchodzenie w tryb OKNO. Wyłączanie balansu przepływu.");
  590.             // Zapisz aktualny stan balansu przepływu przed wyłączeniem
  591.             // ZMIEŃ TE LINIE
  592.             if (id(balans_przeplywu_switch).state) {
  593.               id(poprzedni_balans_przeplywu_stan) = true; // Zapisz true
  594.             } else {
  595.               id(poprzedni_balans_przeplywu_stan) = false; // Zapisz false
  596.             }
  597.             obroty_nawiew = 1;
  598.             obroty_wywiew = 4;
  599.             ESP_LOGI("set_mode", "Tryb OKNO: Nawiew=%d, Wywiew=%d.", obroty_nawiew, obroty_wywiew);            
  600.             id(reku_on_off_switch).make_call().set_value(true).perform();
  601.             id(balans_przeplywu_switch).turn_off(); // Wyłącz balans przepływu
  602.             id(impreza_timer_koniec_timestamp) = 0; // Wyzeruj timer imprezy
  603.          
  604.  
  605.           //} else if (new_mode == "okno") {
  606.           //  obroty_nawiew = 1;
  607.           //  obroty_wywiew = 4;
  608.           //  ESP_LOGI("set_mode", "Tryb OKNO: Nawiew=%d, Wywiew=%d.", obroty_nawiew, obroty_wywiew);            
  609.           //  id(reku_on_off_switch).make_call().set_value(true).perform();
  610.           //  id(impreza_timer_koniec_timestamp) = 0; // Wyzeruj timer imprezy
  611.  
  612.  
  613.           } else if (new_mode == "normalny") {
  614.             obroty_nawiew = 2;
  615.             obroty_wywiew = 2;
  616.             ESP_LOGI("set_mode", "Tryb NORMALNY Nawiew=%d, Wywiew=%d.", obroty_nawiew, obroty_wywiew);            
  617.             id(reku_on_off_switch).make_call().set_value(true).perform();
  618.             id(impreza_timer_koniec_timestamp) = 0; // Wyzeruj timer imprezy
  619.  
  620.  
  621.           } else if (new_mode == "serwisowy") {
  622.             id(reku_on_off_switch).make_call().set_value(false).perform();
  623.             id(tryb_przewietrzania_switch).turn_off();
  624.             id(balans_przeplywu_switch).turn_off();
  625.             ESP_LOGI("set_mode", "Tryb SERWISOWY: Wylaczam rekuperator. Wentylatory: Nawiew=%d, Wywiew=%d.", obroty_nawiew, obroty_wywiew);
  626.             id(impreza_timer_koniec_timestamp) = 0; // Wyzeruj timer imprezy
  627.  
  628.           } else if (new_mode == "urlopowy") {
  629.             obroty_nawiew = 1;
  630.             obroty_wywiew = 1;
  631.             ESP_LOGI("set_mode", "Tryb URLOPOWY: Wentylatory wylaczone (Nawiew=%d, Wywiew=%d).", obroty_nawiew, obroty_wywiew);
  632.             id(reku_on_off_switch).make_call().set_value(true).perform();        
  633.             id(impreza_timer_koniec_timestamp) = 0; // Wyzeruj timer imprezy
  634.  
  635.           } else if (new_mode == "okap_auto") {
  636.             obroty_nawiew = target_reku_speed;
  637.             obroty_wywiew = target_reku_speed;
  638.             ESP_LOGI("set_mode", "Tryb OKAP_AUTO: Nawiew=%d, Wywiew=%d (zalezne od predkosci okapu).", obroty_nawiew, obroty_wywiew);            
  639.             id(reku_on_off_switch).make_call().set_value(true).perform();
  640.             id(impreza_timer_koniec_timestamp) = 0; // Wyzeruj timer imprezy
  641.          
  642.           } else if (new_mode == "wylaczony") {
  643.             ESP_LOGI("set_mode", "Tryb WYLACZONY: Rekuperator wylaczony (Nawiew=%d, Wywiew=%d).", obroty_nawiew, obroty_wywiew);
  644.             id(reku_on_off_switch).make_call().set_value(false).perform();
  645.             id(impreza_timer_koniec_timestamp) = 0; // Wyzeruj timer imprezy
  646.             id(tryb_przewietrzania_switch).turn_off();
  647.             id(balans_przeplywu_switch).turn_off();
  648.  
  649.           } else if (new_mode == "impreza") {
  650.             obroty_nawiew = 7;
  651.             obroty_wywiew = 7;
  652.             ESP_LOGI("set_mode", "Tryb IMPREZA: Nawiew=%d, Wywiew=%d. Aktywny na %d godzin.", obroty_nawiew, obroty_wywiew, (int)id(impreza_czas_trwania_godz).state);
  653.             id(reku_on_off_switch).make_call().set_value(true).perform();
  654.             // Ustaw timer zakonczenia trybu Impreza (teraz + zdefiniowany czas w minutach)
  655.             id(impreza_timer_koniec_timestamp) = id(czas_systemowy).now().timestamp + (int)id(impreza_czas_trwania_godz).state * 3600;
  656.  
  657.           }
  658.           else {
  659.             ESP_LOGW("set_mode", "Nieznany tryb: %s", new_mode.c_str());
  660.             return;
  661.           }
  662.  
  663.           // Wykonaj zmiany obrotow tylko jesli tryb sie zmienil LUB
  664.           // jesli to tryb 'okap_auto' i jego obroty sie zmienily (bo sa dynamiczne)
  665.           // UWAGA: Jesli reku_on_off_switch zostal wylaczony wczesniej, wentylatory i tak zostana ustawione na 0.
  666.           if (id(aktywny_tryb) != new_mode ||
  667.              (new_mode == "okap_auto" && (id(wentylator_nawiewny).state != obroty_nawiew || id(wentylator_wywiewny).state != obroty_wywiew))) {
  668.  
  669.             id(aktywny_tryb) = new_mode;
  670.             id(ostatnie_obroty) = obroty_nawiew; // Aktualizuj dla spójności
  671.  
  672.             // Aktualizacja select.tryb_select_esp ---
  673.             id(tryb_select_esp).publish_state(new_mode);
  674.  
  675.             // Logika ustawiania obrotow wentylatorow (wykonuje sie nawet jesli sa na 0)
  676.             ESP_LOGI("set_mode", "Przelaczono na tryb: '%s'. Ustawiam obroty: Nawiew=%d, Wywiew=%d.", new_mode.c_str(), obroty_nawiew, obroty_wywiew);
  677.             id(wentylator_nawiewny).make_call().set_value(obroty_nawiew).perform();
  678.             id(wentylator_wywiewny).make_call().set_value(obroty_wywiew).perform();
  679.           } else {
  680.             ESP_LOGD("set_mode", "Tryb '%s' juz jest aktywny z obrotami Nawiew=%d. Brak zmian.", new_mode.c_str(), obroty_nawiew);
  681.           }
  682.  
  683.           // --- NAJWAŻNIEJSZE: ZAWSZE AKTUALIZUJ POPRZEDNI TRYB NA KOŃCU LAMBDY ---
  684.           id(poprzedni_tryb_global) = new_mode; // Przypisz nową wartość do zmiennej globalnej poprzedniego trybu
  685.  
  686. interval:
  687.   - interval: 1h
  688.     then:
  689.       - lambda: 'id(licznik_korekt) = 0;'
  690.       - component.update: licznik_korekt_sensor
  691.       - component.update: licznik_korekt_pozostale
  692.       - component.update: ostatni_reset_korekt
  693.  
  694.   - interval: 1min
  695.     then:
  696.       - lambda: |-
  697.           // Sprawdz glowny przelacznik rekuperatora. Jesli jest wylaczony, przerwij logike sterowania.
  698.           // Tryby "serwisowy", "urlopowy", "wylaczony" obsluguja to poprzez wylaczenie reku_on_off_switch.
  699.           if (!id(reku_on_off_switch).state) {
  700.             ESP_LOGD("interval", "Rekuperator wylaczony glownym przelacznikiem. Pomijam logike sterowania.");
  701.             id(tryb_auto_status) = "reku wylaczony (switch OFF)"; // Uaktualnij status
  702.             return; // Przerwij dalsze wykonywanie lambdy
  703.           }
  704.  
  705.           // Pobierz aktualny czas systemowy
  706.           time_t current_timestamp = id(czas_systemowy).now().timestamp;
  707.  
  708.  
  709.           // LOGIKA AUTO TRYB ZIMOWY - POPRAWIONA WERSJA BEZ BLOKOWANIA
  710.           if (id(auto_tryb_zimowy).state && !id(balans_przeplywu_switch).state) {
  711.            
  712.             // ZABEZPIECZENIE: Nie działaj jeśli aktywny jest tryb okno (celowa dysproporcja)
  713.             if (id(aktywny_tryb) == "okno") {
  714.               if (id(tryb_zimowy_aktywny)) {
  715.                 // Jeśli tryb zimowy był aktywny, ale włączono tryb okno - wyłącz tryb zimowy
  716.                 id(wentylator_nawiewny).make_call().set_value(id(oryginalne_obroty_nawiew)).perform();
  717.                 id(wentylator_wywiewny).make_call().set_value(id(oryginalne_obroty_wywiew)).perform();
  718.                 id(tryb_zimowy_aktywny) = false;
  719.                 ESP_LOGI("TrybZimowy", "Wykryto tryb okno - wyłączono tryb zimowy i przywrócono obroty");
  720.               }
  721.               // NIE UŻYWAJ RETURN - kontynuuj wykonywanie pozostałej logiki interwału
  722.             }
  723.             else {
  724.               // TYLKO jeśli NIE jesteśmy w trybie okno - wykonaj logikę trybu zimowego
  725.               float temp_czerpni = id(czerpnia_temp).state;
  726.               float temp_x = id(temp_x_bypass).state;
  727.               float temp_y = id(temp_y_bypass).state;
  728.               float temp_gorna_granica = temp_x + temp_y;
  729.              
  730.               // Sprawdź czy temperatura czerpni jest poza zakresem bypassu
  731.               bool warunek_zimowy = (temp_czerpni < temp_x || temp_czerpni > temp_gorna_granica);
  732.              
  733.               if (warunek_zimowy) {
  734.                 // WARUNEK ZIMOWY AKTYWNY - ustaw różnicę na korzyść wywiewu
  735.                 int current_nawiew = id(wentylator_nawiewny).state;
  736.                 int current_wywiew = id(wentylator_wywiewny).state;
  737.                
  738.                 // ZABEZPIECZENIE: Nie modyfikuj jeśli już jest różnica na korzyść wywiewu
  739.                 if (current_wywiew > current_nawiew) {
  740.                   // Już jest dysproporcja na korzyść wywiewu - tylko oznacz jako aktywny
  741.                   if (!id(tryb_zimowy_aktywny)) {
  742.                     id(tryb_zimowy_aktywny) = true;
  743.                     ESP_LOGD("TrybZimowy", "Tryb zimowy aktywny (już jest dysproporcja): nawiew=%d, wywiew=%d",
  744.                             current_nawiew, current_wywiew);
  745.                   }
  746.                 }
  747.                 else {
  748.                   int target_wywiew = current_wywiew;
  749.                   int target_nawiew = target_wywiew - 1;
  750.                  
  751.                   // Zabezpieczenie przed ujemnymi wartościami
  752.                   if (target_nawiew < 1) target_nawiew = 1;
  753.                   if (target_wywiew < 1) target_wywiew = 1;
  754.                  
  755.                   // Zabezpieczenie przed przekroczeniem maksymalnych wartości
  756.                   if (target_nawiew > 10) target_nawiew = 10;
  757.                   if (target_wywiew > 10) target_wywiew = 10;
  758.                  
  759.                   // Zastosuj zmiany tylko jeśli są różne od aktualnych
  760.                   if (target_nawiew != current_nawiew || target_wywiew != current_wywiew) {
  761.                     id(wentylator_nawiewny).make_call().set_value(target_nawiew).perform();
  762.                     id(wentylator_wywiewny).make_call().set_value(target_wywiew).perform();
  763.                     id(tryb_zimowy_aktywny) = true;
  764.                     ESP_LOGI("TrybZimowy", "Temp czerpni %.1f°C poza zakresem [%.1f-%.1f]°C. Ustawiono nawiew=%d, wywiew=%d",
  765.                             temp_czerpni, temp_x, temp_gorna_granica, target_nawiew, target_wywiew);
  766.                   }
  767.                 }
  768.               }
  769.               else if (id(tryb_zimowy_aktywny)) {
  770.                 // WARUNEK ZIMOWY NIEAKTYWNY, ALE TRYB BYŁ AKTYWNY - przywróć oryginalne ustawienia
  771.                 id(wentylator_nawiewny).make_call().set_value(id(oryginalne_obroty_nawiew)).perform();
  772.                 id(wentylator_wywiewny).make_call().set_value(id(oryginalne_obroty_wywiew)).perform();
  773.                 id(tryb_zimowy_aktywny) = false;
  774.                 ESP_LOGI("TrybZimowy", "Temp czerpni %.1f°C wróciła do zakresu [%.1f-%.1f]°C. Przywrócono obroty: nawiew=%d, wywiew=%d",
  775.                         temp_czerpni, temp_x, temp_gorna_granica, id(oryginalne_obroty_nawiew), id(oryginalne_obroty_wywiew));
  776.               }
  777.               else {
  778.                 ESP_LOGD("TrybZimowy", "Temp czerpni %.1f°C w zakresie [%.1f-%.1f]°C - brak działania",
  779.                         temp_czerpni, temp_x, temp_gorna_granica);
  780.               }
  781.             }
  782.           }
  783.           else if (id(tryb_zimowy_aktywny)) {
  784.             // Tryb zimowy wyłączony, ale był aktywny - przywróć oryginalne ustawienia
  785.             id(wentylator_nawiewny).make_call().set_value(id(oryginalne_obroty_nawiew)).perform();
  786.             id(wentylator_wywiewny).make_call().set_value(id(oryginalne_obroty_wywiew)).perform();
  787.             id(tryb_zimowy_aktywny) = false;
  788.             ESP_LOGI("TrybZimowy", "Tryb zimowy wyłączony. Przywrócono oryginalne obroty: nawiew=%d, wywiew=%d",
  789.                     id(oryginalne_obroty_nawiew), id(oryginalne_obroty_wywiew));
  790.           }
  791.          
  792.           // DODATKOWE ZABEZPIECZENIE: Jeśli tryb zimowy jest wyłączony, ale obroty nie są oryginalne
  793.           if (!id(auto_tryb_zimowy).state && id(tryb_zimowy_aktywny)) {
  794.             // Sprawdź czy balans przypadkiem nie zmienił obrotów po wyłączeniu trybu zimowego
  795.             if (id(wentylator_nawiewny).state != id(oryginalne_obroty_nawiew) ||
  796.                 id(wentylator_wywiewny).state != id(oryginalne_obroty_wywiew)) {
  797.              
  798.               id(wentylator_nawiewny).make_call().set_value(id(oryginalne_obroty_nawiew)).perform();
  799.               id(wentylator_wywiewny).make_call().set_value(id(oryginalne_obroty_wywiew)).perform();
  800.               ESP_LOGI("TrybZimowy", "Korekta: Przywrócono oryginalne obroty po ingerencji balansu: nawiew=%d, wywiew=%d",
  801.                       id(oryginalne_obroty_nawiew), id(oryginalne_obroty_wywiew));
  802.             }
  803.             id(tryb_zimowy_aktywny) = false;
  804.           }
  805.  
  806.  
  807.  
  808.           // DALSZA LOGIKA INTERWAŁU (tryby auto, nocny, okap, etc.) BĘDZIE WYKONANA NORMALNIE
  809.  
  810.  
  811.           // --- LOGIKA DLA TRYBU OKAPU (z wykorzystaniem sensora predkosci z Home Assistant) ---
  812.           float hood_fan_speed_percent = 0.0;
  813.           if (id(okap_predkosc_ha).has_state()) { // Sprawdz, czy sensor ma juz stan
  814.               hood_fan_speed_percent = id(okap_predkosc_ha).state;
  815.           } else {
  816.               // W przypadku braku stanu sensora, traktujemy okap jako wylaczony
  817.               ESP_LOGW("okap_logic", "Sensor okap_predkosc_ha nie ma stanu! Traktuje okap jako wylaczony.");
  818.           }
  819.  
  820.           bool okap_is_on = hood_fan_speed_percent > 0.0; // Okap jest wlaczony, jesli predkosc > 0
  821.  
  822.           int reku_speed_for_okap = 0;
  823.           std::string okap_status_text = "";
  824.  
  825.           if (okap_is_on) {
  826.             // Dostosuj progi i obroty rekuperatora do swoich biezacych potrzeb
  827.             // Przyjmujemy 3 biegi okapu: niski, sredni, wysoki
  828.             if (hood_fan_speed_percent > 66.0) { // Np. powyzej 66% to Bieg 3 okapu
  829.               reku_speed_for_okap = 9; // Najwyzsze obroty rekuperatora
  830.               okap_status_text = "Okap: Bieg 3 (Wysoki)";
  831.            } else if (hood_fan_speed_percent > 33.0) { // Np. powyzej 33% to Bieg 2 okapu
  832.              reku_speed_for_okap = 7; // Srednie obroty rekuperatora
  833.              okap_status_text = "Okap: Bieg 2 (Sredni)";
  834.            } else { // Pomiedzy 0% a 33% to Bieg 1 okapu
  835.              reku_speed_for_okap = 5; // Nizsze obroty rekuperatora
  836.              okap_status_text = "Okap: Bieg 1 (Niski)";
  837.            }
  838.  
  839.            // Aktywuj tryb okap_auto i ustaw odpowiednie obroty
  840.            if (id(aktywny_tryb) != "okap_auto" ||
  841.                id(wentylator_nawiewny).state != reku_speed_for_okap) {
  842.              ESP_LOGI("tryb", "Wykryto aktywny Okap (%s) - przelaczam w tryb OKAP_AUTO. Obroty reku: %d.", okap_status_text.c_str(), reku_speed_for_okap);
  843.               id(set_mode)->execute("okap_auto", reku_speed_for_okap);
  844.             }
  845.             id(tryb_auto_status) = okap_status_text;
  846.  
  847.             // Ustaw "timer" okapu: oblicz czas zakonczenia na teraz + czas trwania z number.okap_czas_trwania_min
  848.             id(okap_timer_koniec_timestamp) = id(czas_systemowy).now().timestamp + (int)id(okap_czas_trwania_min).state * 60;
  849.             ESP_LOGI("tryb", "Timer OKAP_AUTO zresetowany na %d minut. Koniec o: %s", (int)id(okap_czas_trwania_min).state, id(czas_systemowy).now().strftime("%Y-%m-%d %H:%M:%S").c_str());
  850.  
  851.             return; // Zakoncz, bo tryb okap ma priorytet
  852.           } else { // Okap jest wylaczony (hood_fan_speed_percent jest 0)
  853.             if (id(aktywny_tryb) == "okap_auto") {
  854.                 // Jesli bylismy w trybie okap_auto, ale okap sie wylaczyl, sprawdzamy "timer"
  855.                 if (id(czas_systemowy).now().timestamp >= id(okap_timer_koniec_timestamp)) {
  856.                     ESP_LOGI("tryb", "Okap wylaczony, timer OKAP_AUTO minal - powracam do trybu AUTO.");
  857.                     id(set_mode)->execute("auto", 0);
  858.                     id(okap_timer_koniec_timestamp) = 0; // Wyzeruj timer po zakonczeniu
  859.                 } else {
  860.                     int remaining_seconds = id(okap_timer_koniec_timestamp) - id(czas_systemowy).now().timestamp;
  861.                     id(tryb_auto_status) = "Okap wylaczony (timer aktywny)";
  862.                     ESP_LOGD("tryb", "Okap wylaczony, timer OKAP_AUTO nadal aktywny. Pozostalo: %d s", remaining_seconds);
  863.                 }
  864.                 return;
  865.             }
  866.           }
  867.           // --- KONIEC LOGIKI TRYBU OKAPU ---
  868.  
  869.  
  870.           // --- LOGIKA TRYBU IMPREZA (NAJWYŻSZY PRIORYTET PO OKAPIE) ---
  871.           if (id(aktywny_tryb) == "impreza") {
  872.             if (id(czas_systemowy).now().timestamp >= id(impreza_timer_koniec_timestamp)) {
  873.                 ESP_LOGI("tryb", "Timer trybu IMPREZA minal - powracam do trybu AUTO.");
  874.                 id(set_mode)->execute("auto", 0);
  875.                 id(impreza_timer_koniec_timestamp) = 0; // Wyzeruj timer po zakonczeniu
  876.                 return; // Zakoncz, bo tryb zmieniony
  877.             } else {
  878.                 int remaining_minutes = (id(impreza_timer_koniec_timestamp) - id(czas_systemowy).now().timestamp) / 60;
  879.                 id(tryb_auto_status) = "Impreza (" + std::to_string(remaining_minutes) + " min do konca)";
  880.                 ESP_LOGD("tryb", "Tryb IMPREZA nadal aktywny. Pozostalo: %d min", remaining_minutes);
  881.                 return; // Zakoncz, bo tryb impreza ma priorytet nad auto/nocny
  882.             }
  883.           }
  884.           // --- KONIEC LOGIKI TRYBU IMPREZA ---
  885.  
  886.           // Pobierz aktualny czas
  887.           auto t = id(czas_systemowy).now();
  888.           int hour = t.hour;
  889.  
  890.           // --- NOWA LOGIKA: Wyjście z trybu nocnego ---
  891.           // Jeśli jesteśmy w trybie nocnym, a godzina nie jest już nocna (np. >= 6 rano i < 23)
  892.           if (id(aktywny_tryb) == "nocny" && (hour >= 6 && hour < 23)) {
  893.           //if (id(aktywny_tryb) == "nocny" && (hour >= 3 && hour < 5)) {
  894.               ESP_LOGI("tryb", "Koniec trybu nocnego - przechodzę w tryb AUTO.");
  895.               id(set_mode)->execute("auto", 0);
  896.               return; // Zakończ, by w tej minucie już być w trybie auto
  897.           }
  898.           // --- KONIEC NOWEJ LOGIKI ---
  899.  
  900.  
  901.           // 1. Logika otwartego okna
  902.           if (id(czy_jakies_okno_otwarte).state) {
  903.             // Jeśli okno jest otwarte i system jest włączony, ale nie jest w trybie "okno"
  904.             if (id(reku_on_off_switch).state && id(aktywny_tryb) != "okno") {
  905.               ESP_LOGI("tryb", "Okno otwarte — przełączam w tryb OKNO");
  906.               id(set_mode)->execute("okno", 0);
  907.             }
  908.             // Zawsze kończ działanie, jeśli jakiekolwiek okno jest otwarte
  909.             return;
  910.           }
  911.  
  912.           // Jeśli doszliśmy tutaj, to znaczy, że wszystkie okna są ZAMKNIĘTE.
  913.           // Sprawdź, czy trzeba wyjść z trybu "okno"
  914.           if (id(aktywny_tryb) == "okno") {
  915.             ESP_LOGI("tryb", "Wszystkie okna zamknięte — przywracam tryb AUTO");
  916.             id(set_mode)->execute("auto", 0);
  917.             return; // Zakończ, aby w następnej minucie już działać w trybie auto
  918.           }
  919.  
  920.  
  921.  
  922.           // Sprawdź warunki do wejścia w tryb nocny
  923.           //if ((hour >= 7 || hour < 10) &&
  924.           if ((hour >= 23 || hour < 6) &&
  925.               id(reku_on_off_switch).state == 1 &&
  926.               !id(czy_przewietrzanie) &&
  927.               id(aktywny_tryb) != "serwisowy" &&
  928.               id(aktywny_tryb) != "urlopowy" &&
  929.               id(aktywny_tryb) != "nocny" &&
  930.               id(aktywny_tryb) != "okno" &&
  931.               !id(dom_pusty).state) { //
  932.             ESP_LOGI("tryb", "Automatyczne przełączenie na tryb nocny");
  933.             id(set_mode)->execute("nocny", 0);
  934.             return;  // pomiń dalej logikę w tej minucie
  935.           }
  936.  
  937.           if (id(reku_on_off_switch).state != 1) {
  938.             id(tryb_auto_status) = "reku wyłączony";
  939.             return;
  940.           }
  941.           if (id(czy_przewietrzanie)) {
  942.             id(tryb_auto_status) = "przewietrzanie aktywne";
  943.             return;
  944.           }
  945.  
  946.           std::string tryb = id(aktywny_tryb);
  947.  
  948.           // Deklaracje zmiennych na tym poziomie
  949.           float hum = 0.0;
  950.           int co2 = 0;
  951.           bool pusty = id(dom_pusty).state; // Pobranie stanu 'dom_pusty' raz
  952.  
  953.           // Pobranie wartości z globalnych sensorów
  954.           if (id(max_humidity_all_sensors).has_state()) {
  955.               hum = id(max_humidity_all_sensors).state;
  956.           } else {
  957.               // Wartość domyślna, jeśli sensor max_humidity_all_sensors nie ma jeszcze stanu
  958.               // To jest tylko na wypadek braku odczytu na samym starcie
  959.               hum = 0.0;
  960.               ESP_LOGW("Wilgotnosc", "Maksymalna wilgotność nie jest dostępna!");
  961.           }
  962.  
  963.           // if (id(co2_value).has_state()) {
  964.           //    co2 = (int)id(co2_value).state;
  965.           // }
  966.           // } else {
  967.           //      co2 = 0.0; // Domyślna wartość w przypadku braku odczytu
  968.           //     ESP_LOGW("CO2", "Wartość CO2 nie jest dostępna!");
  969.           // }
  970.  
  971.  
  972.           if (id(aktywny_tryb) == "nocny") {
  973.             // Odczytaj aktualny poziom CO2 z sensora
  974.             float co2 = 0.0;
  975.             if (id(co2_value).has_state()) {
  976.                 co2 = id(co2_value).state;
  977.             } else {
  978.                 co2 = 0.0; // Domyślna wartość w przypadku braku odczytu
  979.                 ESP_LOGW("CO2", "Wartość CO2 nie jest dostępna!");
  980.             }
  981.  
  982.             // --- LOGIKA CO2 Z HISTEREZĄ "OD PROGU DO PROGU" DLA TRYBU NOCNEGO ---
  983.             int current_co2_level_nocny = id(co2_nocny_current_level_state).state;
  984.             int new_co2_level_nocny = current_co2_level_nocny;
  985.  
  986.             if (current_co2_level_nocny == 0 && co2 > id(co2_nocny_high).state) {
  987.               new_co2_level_nocny = 1; // Przejście z Normalnego na Wysoki (obroty 4)
  988.             }
  989.             if (current_co2_level_nocny == 1 && co2 < id(co2_nocny_low).state) {
  990.               new_co2_level_nocny = 0; // Przejście z Wysokiego na Normalny (obroty 2)
  991.             }
  992.  
  993.             if (new_co2_level_nocny != current_co2_level_nocny) {
  994.                 id(co2_nocny_current_level_state).publish_state(new_co2_level_nocny);
  995.                 ESP_LOGI("CO2_Nocny_Histereza", "Zmiana poziomu CO2 w nocy z %d na %d", current_co2_level_nocny, new_co2_level_nocny);
  996.             }
  997.  
  998.             int obroty_co2_nocny = 2; // Domyślne obroty w nocy
  999.             std::string status_co2_nocny = "niski CO2";
  1000.  
  1001.             if (new_co2_level_nocny == 1) {
  1002.               obroty_co2_nocny = 4;
  1003.               status_co2_nocny = "wysoki CO2";
  1004.             } else { // new_co2_level_nocny == 0
  1005.               obroty_co2_nocny = 2;
  1006.               status_co2_nocny = "niski CO2";
  1007.             }
  1008.             // --- KONIEC LOGIKI CO2 Z HISTEREZĄ "OD PROGU DO PROGU" DLA TRYBU NOCNEGO ---
  1009.  
  1010.  
  1011.             // --- NOWA LOGIKA WILGOTNOŚCI NOCNEJ Z HISTEREZĄ "OD PROGU DO PROGU" ---
  1012.             float hum = 0.0;
  1013.             if (id(max_humidity_all_sensors).has_state()) {
  1014.                 hum = id(max_humidity_all_sensors).state;
  1015.             } else {
  1016.                 hum = 0.0;
  1017.                 ESP_LOGW("Wilgotnosc", "Maksymalna wilgotność nie jest dostępna!");
  1018.             }
  1019.  
  1020.             // Odczytaj aktualny zapamiętany poziom wilgotności (0 lub 1)
  1021.             int current_hum_level_nocny = id(hum_nocna_current_level_state).state;
  1022.             int new_hum_level_nocny = current_hum_level_nocny;
  1023.  
  1024.             // Logika przejść w GÓRĘ
  1025.             if (current_hum_level_nocny == 0 && hum > id(wilgotnosc_nocna_high).state) {
  1026.               new_hum_level_nocny = 1; // Przejście z Niskiej na Wysoką (obroty 4)
  1027.             }
  1028.  
  1029.             // Logika przejść w DÓŁ
  1030.             if (current_hum_level_nocny == 1 && hum < id(wilgotnosc_nocna_low).state) {
  1031.               new_hum_level_nocny = 0; // Przejście z Wysokiej na Niską (obroty 2)
  1032.             }
  1033.  
  1034.             // Zaktualizuj zapamiętany poziom wilgotności, jeśli nastąpiła zmiana
  1035.             if (new_hum_level_nocny != current_hum_level_nocny) {
  1036.                 id(hum_nocna_current_level_state).publish_state(new_hum_level_nocny);
  1037.                 ESP_LOGI("Wilgotnosc_Nocna_Histereza", "Zmiana poziomu wilgotności w nocy z %d na %d", current_hum_level_nocny, new_hum_level_nocny);
  1038.             }
  1039.  
  1040.             int obroty_hum_nocny = 2; // Domyślne obroty dla wilgotności w nocy
  1041.             std::string status_hum_nocny = "wilgotność stabilna";
  1042.  
  1043.             // Ustalenie obrotów na podstawie aktualnego zapamiętanego poziomu wilgotności
  1044.             if (new_hum_level_nocny == 1) {
  1045.               obroty_hum_nocny = 4;
  1046.               status_hum_nocny = "wysoka wilgotność";
  1047.             } else { // new_hum_level_nocny == 0
  1048.               obroty_hum_nocny = 2;
  1049.               status_hum_nocny = "niska wilgotność";
  1050.             }
  1051.             // --- KONIEC LOGIKI WILGOTNOŚCI NOCNEJ Z HISTEREZĄ "OD PROGU DO PROGU" ---
  1052.  
  1053.  
  1054.             // Wybór wyższych obrotów (porównujemy obroty z histerezy CO2 i wilgotności)
  1055.             // Wilgotność może tylko zwiększyć obroty, nigdy ich nie obniży poniżej obrotów CO2.
  1056.             // Jeśli CO2 wymusza 5, wilgotność na 4 i tak nie obniży. Jeśli CO2 wymusza 2, wilgotność na 4 podniesie do 4.
  1057.             int obroty = std::max(obroty_co2_nocny, obroty_hum_nocny);
  1058.  
  1059.  
  1060.             if (obroty != id(ostatnie_obroty)) {
  1061.               ESP_LOGI("tryb", "Zmiana obrotow w trybie NOCNYM. Nowe obroty: %d", obroty);
  1062.               id(wentylator_nawiewny).make_call().set_value(obroty).perform();
  1063.               id(wentylator_wywiewny).make_call().set_value(obroty).perform();
  1064.               id(ostatnie_obroty) = obroty;
  1065.             }
  1066.  
  1067.             // Ustawianie statusu na podstawie zmiennych stanu, nie obrotów
  1068.             if (new_hum_level_nocny == 1) {
  1069.                 id(tryb_auto_status) = "Nocny - " + status_hum_nocny;
  1070.             } else if (new_co2_level_nocny == 1) {
  1071.                 id(tryb_auto_status) = "Nocny - " + status_co2_nocny;
  1072.             } else {
  1073.                 id(tryb_auto_status) = "Nocny - " + status_co2_nocny;
  1074.             }
  1075.             }
  1076.  
  1077.           if (tryb == "auto") {
  1078.             // **NOWA LOGIKA: Sprawdź najpierw, czy dom jest pusty**
  1079.             if (pusty) { // Używamy zmiennej 'pusty' zdeklarowanej i ustawionej wcześniej
  1080.               // Jeśli dom jest pusty, wymuś obroty na 1 i zakończ logikę dla trybu auto
  1081.               int obroty = 1; // Minimalne obroty dla trybu "dom pusty"
  1082.               if (obroty != id(ostatnie_obroty)) {
  1083.                 id(wentylator_nawiewny).make_call().set_value(obroty).perform();
  1084.                 id(wentylator_wywiewny).make_call().set_value(obroty).perform();
  1085.                 id(ostatnie_obroty) = obroty;
  1086.               }
  1087.               id(tryb_auto_status) = "dom pusty";
  1088.  
  1089.             } else {
  1090.               // --- NOWA LOGIKA CO2 Z HISTEREZĄ "OD PROGU DO PROGU" ---
  1091.               // Odczytaj aktualny poziom CO2 z sensora
  1092.               float co2 = 0.0;
  1093.               if (id(co2_value).has_state()) {
  1094.                   co2 = id(co2_value).state;
  1095.               } else {
  1096.                   co2 = 0.0; // Domyślna wartość w przypadku braku odczytu
  1097.                   ESP_LOGW("CO2", "Wartość CO2 nie jest dostępna!");
  1098.               }
  1099.  
  1100.               // Odczytaj aktualny zapamiętany poziom CO2 (0, 1, 2, 3)
  1101.               int current_co2_level = id(co2_auto_current_level_state).state;
  1102.               int new_co2_level = current_co2_level;
  1103.  
  1104.               // Logika przejść w GÓRĘ
  1105.               if (current_co2_level == 0 && co2 > id(co2_auto_low).state) {
  1106.                 new_co2_level = 1; // Przejście z Normalnego na Niski (obroty 4)
  1107.               } else if (current_co2_level == 1 && co2 > id(co2_auto_medium).state) {
  1108.                 new_co2_level = 2; // Przejście z Niskiego na Średni (obroty 6)
  1109.               } else if (current_co2_level == 2 && co2 > id(co2_auto_high).state) {
  1110.                 new_co2_level = 3; // Przejście ze Średniego na Wysoki (obroty 9)
  1111.               }
  1112.  
  1113.               // Logika przejść w DÓŁ
  1114.               // Ważne: Sprawdzamy w odwrotnej kolejności, aby zapewnić prawidłowe "zejście"
  1115.               if (current_co2_level == 3 && co2 < id(co2_auto_high).state) {
  1116.                 new_co2_level = 2; // Przejście z Wysokiego na Średni (obroty 6)
  1117.               } else if (current_co2_level == 2 && co2 < id(co2_auto_medium).state) {
  1118.                 new_co2_level = 1; // Przejście ze Średniego na Niski (obroty 4)
  1119.               } else if (current_co2_level == 1 && co2 < id(co2_auto_low).state) {
  1120.                 new_co2_level = 0; // Przejście z Niskiego na Normalny (obroty 3)
  1121.               }
  1122.  
  1123.               // Zaktualizuj zapamiętany poziom CO2, jeśli nastąpiła zmiana
  1124.               if (new_co2_level != current_co2_level) {
  1125.                   id(co2_auto_current_level_state).publish_state(new_co2_level);
  1126.                   ESP_LOGI("CO2_Histereza", "Zmiana poziomu CO2 z %d na %d", current_co2_level, new_co2_level);
  1127.               }
  1128.  
  1129.               int obroty_co2_hister = 3; // Domyślne obroty
  1130.               std::string status_co2_hister = "normalny CO2";
  1131.  
  1132.               // Ustalenie obrotów na podstawie aktualnego zapamiętanego poziomu CO2
  1133.               if (new_co2_level == 3) {
  1134.                 obroty_co2_hister = 9;
  1135.                 status_co2_hister = "wysoki CO2";
  1136.               } else if (new_co2_level == 2) {
  1137.                 obroty_co2_hister = 6;
  1138.                 status_co2_hister = "średni CO2";
  1139.               } else if (new_co2_level == 1) {
  1140.                 obroty_co2_hister = 4;
  1141.                 status_co2_hister = "niski CO2";
  1142.               } else { // new_co2_level == 0
  1143.                 obroty_co2_hister = 3;
  1144.                 status_co2_hister = "bardzo niski CO2";
  1145.               }
  1146.               // --- KONIEC LOGIKI CO2 Z HISTEREZĄ "OD PROGU DO PROGU" ---
  1147.  
  1148.  
  1149.  
  1150.               // --- LOGIKA TIMEOUTU OSUSZANIA (sprawdzenie, czy timer minął) ---
  1151.               // Jeśli tryb osuszania jest AKTYWNY i jego timer minął, wymuś jego wyłączenie.
  1152.               if (id(tryb_wysokiej_wilgotnosci_aktywny) && id(osuszanie_timer_koniec_timestamp) > 0 && current_timestamp >= id(osuszanie_timer_koniec_timestamp)) {
  1153.                 ESP_LOGI("Wilgotnosc", "TIMEOUT OSUSZANIA: Minal czas %d minut. Dezaktywuja tryb osuszania.", (int)id(osuszanie_czas_trwania_minuty).state);
  1154.                 id(tryb_wysokiej_wilgotnosci_aktywny) = false;
  1155.                 id(osuszanie_timer_koniec_timestamp) = 0; // Wyzeruj timer
  1156.               }
  1157.  
  1158.               // --- LOGIKA WILGOTNOŚCI Z HISTEREZĄ I INTEGRACJĄ Z TIMEREM ---
  1159.  
  1160.               // Sprawdź, czy należy WŁĄCZYĆ tryb osuszania
  1161.               // Aktywujemy osuszanie TYLKO jeśli wilgotność przekroczy wysoki próg i NIE BYŁO aktywne LUB JEŚLI BYŁO AKTYWNE I TIMER MINĄŁ (wymuszając ponowne uruchomienie timera)
  1162.               if (hum > id(wilgotnosc_wysoka).state) {
  1163.                 if (!id(tryb_wysokiej_wilgotnosci_aktywny)) { // Jeśli tryb nie był aktywny, włącz go i ustaw timer
  1164.                   id(tryb_wysokiej_wilgotnosci_aktywny) = true;
  1165.                   id(osuszanie_timer_koniec_timestamp) = current_timestamp + (int)id(osuszanie_czas_trwania_minuty).state * 60;
  1166.                   ESP_LOGI("Wilgotnosc", "Wilgotnosc %.1f powyżej progu (%d%%). AKTYWUJE osuszanie. Timer na %d minut. Koniec o: %s",
  1167.                            hum, (int)id(wilgotnosc_wysoka).state, (int)id(osuszanie_czas_trwania_minuty).state, id(czas_systemowy).now().strftime("%H:%M:%S").c_str());
  1168.                 } else { // Tryb już był aktywny, ale wilgotność nadal wysoka, więc odświeżamy timer (jeśli nie jest nieskończony)
  1169.                    // Opcjonalnie: odśwież timer, jeśli chcesz, aby każde przekroczenie progu restartowało go
  1170.                    // id(osuszanie_timer_koniec_timestamp) = current_timestamp + (int)id(osuszanie_czas_trwania_minuty).state * 60;
  1171.                    // ESP_LOGD("Wilgotnosc", "Odswiezono timer osuszania. Wilgotnosc %.1f", hum);
  1172.                 }
  1173.               }
  1174.               // Sprawdź, czy należy WYŁĄCZYĆ tryb osuszania (tylko jeśli był aktywny i wilgotność spadła poniżej progu)
  1175.               else if (hum < id(wilgotnosc_niska).state && id(tryb_wysokiej_wilgotnosci_aktywny)) {
  1176.                 id(tryb_wysokiej_wilgotnosci_aktywny) = false;
  1177.                 id(osuszanie_timer_koniec_timestamp) = 0; // Wyzeruj timer, bo wilgotność wróciła do normy
  1178.                 ESP_LOGI("Wilgotnosc", "Wilgotnosc %.1f ponizej progu (%d%%). DEZAKTYWUJE osuszanie.", hum, (int)id(wilgotnosc_niska).state);
  1179.               }
  1180.  
  1181.               int obroty_hum = 3; // Domyślne obroty, gdy osuszanie nie jest potrzebne
  1182.               std::string status_hum = "";
  1183.  
  1184.               // Ustaw obroty i status na podstawie OSTATECZNEGO stanu `tryb_wysokiej_wilgotnosci_aktywny`
  1185.               if (id(tryb_wysokiej_wilgotnosci_aktywny)) {
  1186.                 obroty_hum = 9; // Obroty dla trybu osuszania
  1187.                 // Aktualizacja statusu, aby pokazać pozostały czas
  1188.                 if (id(osuszanie_timer_koniec_timestamp) > 0) {
  1189.                     int remaining_minutes = (id(osuszanie_timer_koniec_timestamp) - current_timestamp) / 60;
  1190.                     status_hum = "osuszanie (" + std::to_string(remaining_minutes) + " min)";
  1191.                     ESP_LOGD("Wilgotnosc", "Osuszanie nadal aktywne. Pozostalo: %d min. Wilgotnosc %.1f", remaining_minutes, hum);
  1192.                 } else {
  1193.                     // Jest to stan przejściowy, gdy tryb jest aktywny, ale timer jeszcze nieustawiony lub wygasł.
  1194.                     status_hum = "osuszanie (koniec)";
  1195.                 }
  1196.               } else {
  1197.                 obroty_hum = 3; // Obroty gdy osuszanie nie jest aktywne
  1198.                 status_hum = "wilgotność stabilna";
  1199.               }
  1200.  
  1201.               // --- KONIEC ZMODYFIKOWANEJ LOGIKI WILGOTNOŚCI ---
  1202.  
  1203.               // Wybór wyższych obrotów
  1204.               int obroty = std::max(obroty_co2_hister, obroty_hum);
  1205.  
  1206.               if (obroty != id(ostatnie_obroty)) {
  1207.                 id(wentylator_nawiewny).make_call().set_value(obroty).perform();
  1208.                 id(wentylator_wywiewny).make_call().set_value(obroty).perform();
  1209.                 id(ostatnie_obroty) = obroty;
  1210.               }
  1211.  
  1212.               // Ustawienie statusu
  1213.               if (obroty == obroty_hum && status_hum != "") {
  1214.                 id(tryb_auto_status) = "Tryb auto - " + status_hum;
  1215.               } else {
  1216.                 id(tryb_auto_status) = "Tryb auto - " + status_co2_hister;
  1217.               }
  1218.             }
  1219.           }
  1220.  
  1221. # Example configuration entry
  1222. psram:
  1223.   mode: quad
  1224.   speed: 40MHZ
  1225.  
  1226. sensor:
  1227.   - platform: template
  1228.     name: "Pozostały czas do wymiany filtra"
  1229.     icon: mdi:air-filter
  1230.     unit_of_measurement: "d"
  1231.     id: pozostalay_czas_filtra
  1232.     accuracy_decimals: 1
  1233.     update_interval: 60s
  1234.     lambda: |-
  1235.       float max_time_days = 90.0; // Domyślna wartość w dniach
  1236.       float running_time_minutes = 0.0;
  1237.      
  1238.       // Sprawdź, czy sensor z czasem pracy wentylatorów ma stan
  1239.       if (id(fan_running_time).has_state()) {
  1240.         running_time_minutes = id(fan_running_time).state;
  1241.       }
  1242.      
  1243.       // Sprawdź, czy sensor z maksymalnym czasem filtra ma stan
  1244.       if (id(filter_sel).has_state()) {
  1245.         // Konwersja string na float bez obsługi wyjątków
  1246.         // Używamy atof (ASCII to float)
  1247.         max_time_days = atof(id(filter_sel).state.c_str());
  1248.       }
  1249.      
  1250.       // Przelicz minuty na dni: 1440 minut = 1 dzień
  1251.       float running_time_days = running_time_minutes / 1440.0;
  1252.  
  1253.       // Oblicz pozostały czas w dniach
  1254.       float remaining_time_days = max_time_days - running_time_days;
  1255.      
  1256.       // Zwróć wynik. Jeśli jest ujemny, zwróć 0.0
  1257.       return remaining_time_days > 0 ? remaining_time_days : 0.0;
  1258.  
  1259.   - platform: template
  1260.     name: "Okap - czas do końca"
  1261.     id: okap_czas_do_konca
  1262.     unit_of_measurement: "min"
  1263.     icon: "mdi:stove-fan"
  1264.     update_interval: 10s # Odświeżaj co 10 sekund dla płynniejszego liczenia
  1265.     lambda: |-
  1266.       int now_timestamp = id(czas_systemowy).now().timestamp;
  1267.       int end_timestamp = id(okap_timer_koniec_timestamp);
  1268.  
  1269.       if (end_timestamp > now_timestamp) {
  1270.         // Oblicz pozostały czas w sekundach, a następnie w minutach
  1271.         int remaining_seconds = end_timestamp - now_timestamp;
  1272.         return (float)std::ceil(remaining_seconds / 60.0); // Zaokrągl w górę do najbliższej minuty
  1273.       } else {
  1274.         return 0; // Jeśli tryb się zakończył lub nieaktywny
  1275.       }
  1276.  
  1277.   - platform: template
  1278.     name: "Osuszanie - czas do końca"
  1279.     id: rekuperator_osuszanie_remaining_time
  1280.     unit_of_measurement: "min"
  1281.     icon: "mdi:timer-sand"
  1282.     accuracy_decimals: 0
  1283.     update_interval: 10s # Aktualizuj co 10 sekund dla płynniejszego odliczania
  1284.     lambda: |-
  1285.       time_t current_timestamp = id(czas_systemowy).now().timestamp;
  1286.       int end_timestamp = id(osuszanie_timer_koniec_timestamp);
  1287.  
  1288.       if (end_timestamp > 0 && current_timestamp < end_timestamp) {
  1289.         // Oblicz pozostały czas w minutach, zaokrąglając w górę, aby pokazywać pełną minutę, dopóki nie minie
  1290.         return (end_timestamp - current_timestamp + 59) / 60;
  1291.       } else {
  1292.         return 0; // Jeśli timer nieaktywny lub minął, zwróć 0
  1293.       }
  1294.  
  1295.   - platform: template
  1296.     name: "Impreza - czas do końca"
  1297.     id: impreza_czas_do_konca
  1298.     unit_of_measurement: "min"
  1299.     icon: "mdi:party-popper"
  1300.     update_interval: 60s
  1301.     lambda: |-
  1302.       int now_timestamp = id(czas_systemowy).now().timestamp;
  1303.       int end_timestamp = id(impreza_timer_koniec_timestamp);
  1304.  
  1305.       if (end_timestamp > now_timestamp) {
  1306.         // Oblicz pozostały czas w sekundach, a następnie w minutach
  1307.         int remaining_seconds = end_timestamp - now_timestamp;
  1308.         return (float)std::ceil(remaining_seconds / 60.0); // Zaokrągl w górę do najbliższej minuty
  1309.       } else {
  1310.         return 0; // Jeśli tryb się zakończył lub nieaktywny
  1311.       }
  1312.  
  1313.   - platform: template
  1314.     name: "Przewietrzanie - czas do końca"
  1315.     id: przewietrzanie_czas_do_konca
  1316.     unit_of_measurement: "min"
  1317.     icon: "mdi:timer-sand"
  1318.     update_interval: 60s # Odświeżaj co 10 sekund dla płynniejszego liczenia
  1319.     lambda: |-
  1320.       int now_timestamp = id(czas_systemowy).now().timestamp;
  1321.       int end_timestamp = id(przewietrzanie_timer_koniec_timestamp);
  1322.  
  1323.       if (end_timestamp > now_timestamp) {
  1324.         // Oblicz pozostały czas w sekundach, a następnie w minutach
  1325.         int remaining_seconds = end_timestamp - now_timestamp;
  1326.         return (float)std::ceil(remaining_seconds / 60.0); // Zaokrągl w górę do najbliższej minuty
  1327.       } else {
  1328.         return 0; // Jeśli przewietrzanie się zakończyło lub nieaktywne
  1329.       }
  1330.  
  1331.   - platform: homeassistant
  1332.     name: "Predkosc Okapu z HA - Atrybut"
  1333.     id: okap_predkosc_atrybut
  1334.     entity_id: fan.fan_hood
  1335.     attribute: percentage
  1336.     internal: true
  1337.  
  1338. # To jest wlasciwy sensor, ktory bedzie uzywany.
  1339. # Jego lambda laczy dane z obu powyższych sensorów.
  1340.   - platform: template
  1341.     name: "Predkosc Okapu z HA"
  1342.     id: okap_predkosc_ha
  1343.     unit_of_measurement: "%"
  1344.     accuracy_decimals: 0
  1345.     internal: false
  1346.     update_interval: 15s
  1347.     lambda: |-
  1348.       if (id(okap_stan_tekstowy_ha).state == "on") {
  1349.         return id(okap_predkosc_atrybut).state;
  1350.       } else {
  1351.         return 0.0;
  1352.       }
  1353.  
  1354.   - platform: template
  1355.     name: "Maksymalna Wilgotność w Domu"
  1356.     id: max_humidity_all_sensors # Nowy ID dla sensora maksymalnej wilgotności
  1357.     unit_of_measurement: "%"
  1358.     accuracy_decimals: 1
  1359.     update_interval: 60s # Ustaw interwał aktualizacji np. na 10 sekund
  1360.     lambda: |-
  1361.       float max_h = 0.0;
  1362.  
  1363.       if (id(wilgotnosc_lazienka).has_state()) {
  1364.           max_h = std::max(max_h, id(wilgotnosc_lazienka).state);
  1365.       }
  1366.       if (id(wilgotnosc_kuchnia).has_state()) {
  1367.           max_h = std::max(max_h, id(wilgotnosc_kuchnia).state);
  1368.       }
  1369.       if (id(wilgotnosc_kotlownia).has_state()) {
  1370.           max_h = std::max(max_h, id(wilgotnosc_kotlownia).state);
  1371.       }
  1372.       if (id(wilgotnosc_salon).has_state()) {
  1373.           max_h = std::max(max_h, id(wilgotnosc_salon).state);
  1374.       }
  1375.  
  1376.       //Jeśli masz więcej sensorów, dodaj kolejne bloki if i std::max()
  1377.  
  1378.       return max_h;
  1379.  
  1380.  
  1381.   - platform: debug
  1382.     free:
  1383.       name: "Heap Free"
  1384.     block:
  1385.       name: "Heap Max Block"
  1386.     loop_time:
  1387.       name: "Loop Time"
  1388.     psram:
  1389.       name: "Free PSRAM"
  1390.     cpu_frequency:
  1391.       name: "CPU Frequency"
  1392.  
  1393.  
  1394.   - platform: internal_temperature
  1395.     name: "Internal Temperature"
  1396.  
  1397.   - platform: uptime
  1398.     id: uptime_sensor
  1399.     internal: True
  1400.     update_interval: 60s
  1401.     on_raw_value:
  1402.       then:
  1403.         - text_sensor.template.publish:
  1404.             id: uptime_human
  1405.             state: !lambda |-
  1406.               int seconds = round(id(uptime_sensor).raw_state);
  1407.               int days = seconds / (24 * 3600);
  1408.               seconds = seconds % (24 * 3600);
  1409.               int hours = seconds / 3600;
  1410.               seconds = seconds % 3600;
  1411.               int minutes = seconds /  60;
  1412.               seconds = seconds % 60;
  1413.               return (
  1414.                 (days ? to_string(days) + "d " : "") +
  1415.                 (hours ? to_string(hours) + "h " : "") +
  1416.                 (minutes ? to_string(minutes) + "m " : "") +
  1417.                 (to_string(seconds) + "s")
  1418.               ).c_str();
  1419.  
  1420.   - platform: homeassistant
  1421.     id: co2_value
  1422.     entity_id: sensor.co2_salon
  1423.     internal: true
  1424.  
  1425.   - platform: homeassistant
  1426.     id: wilgotnosc_lazienka
  1427.     entity_id: sensor.atc_lazienka_parter_humidity
  1428.     internal: true
  1429.  
  1430.   - platform: homeassistant
  1431.     entity_id: sensor.ble_kuchnia_humidity
  1432.     id: wilgotnosc_kuchnia
  1433.     internal: true
  1434.  
  1435.   - platform: homeassistant
  1436.     entity_id: sensor.atc_salon_humidity
  1437.     id: wilgotnosc_salon
  1438.     internal: true
  1439.  
  1440.   - platform: homeassistant
  1441.     entity_id: sensor.atc_04e8_humidity
  1442.     id: wilgotnosc_kotlownia
  1443.     internal: true
  1444.  
  1445.  
  1446.   - platform: template
  1447.     name: "Wykonane korekty (w tej godzinie)"
  1448.     id: licznik_korekt_sensor
  1449.     unit_of_measurement: "x"
  1450.     lambda: 'return id(licznik_korekt);'
  1451.     update_interval: 60s
  1452.  
  1453.   - platform: template
  1454.     name: "Pozostale korekty"
  1455.     id: licznik_korekt_pozostale
  1456.     unit_of_measurement: "x"
  1457.     lambda: 'return id(limit_korekt_na_godzine) - id(licznik_korekt);'
  1458.     update_interval: 60s
  1459.  
  1460.  
  1461.  
  1462.   #####################################################################
  1463.   #                  KANAL NAWIEWU (POWIETRZE WCHODZACE)             #
  1464.   #####################################################################
  1465.  
  1466.   # 1. Czujnik SDP810 dla nawiewu - surowe ciśnienie
  1467.   - platform: sdp3x
  1468.     name: "Cisnienie Roznicowe Nawiew"
  1469.     id: nawiew_raw_pressure
  1470.     address: 0x25
  1471.     i2c_id: bus_a # Zmień na ID Twojej magistrali dla nawiewu
  1472.     update_interval: 5s
  1473.     filters:
  1474.      # Pierwszy filtr w liście
  1475.       - exponential_moving_average:
  1476.           alpha: 0.7 # Wcięcie 2 spacje pod 'exponential_moving_average'
  1477.       # Drugi filtr w liście. Wcięcie jak pierwszy filtr (myślnik w tej samej kolumnie).
  1478.     #  - lambda: 'return x > 0 ? x : 0;'
  1479.      
  1480.     # 2. Sensor szablonowy - oblicza przepływ dla nawiewu w m3/h
  1481.   - platform: template
  1482.     name: "Przeplyw Nawiew m3/h"
  1483.     id: nawiew_airflow_m3_h
  1484.     update_interval: 5s
  1485.     icon: "mdi:air-filter"
  1486.     unit_of_measurement: "m³/h"
  1487.     lambda: |-
  1488.       // Stałe fizyczne
  1489.       const float p_atm = 101325.0;    // Ciśnienie atmosferyczne w Pa
  1490.       const float R = 287.05;          // Stała gazowa dla powietrza J/(kg·K)
  1491.  
  1492.       // Pobierz temperaturę (°C) i przelicz na Kelwiny
  1493.       float temp_c = id(nawiew_temp).state;
  1494.       float temp_k = temp_c + 273.15;
  1495.  
  1496.       // Oblicz gęstość powietrza (rho)
  1497.       float rho = p_atm / (R * temp_k);
  1498.  
  1499.       // Średnica i pole przekroju kanału (0.2 m)
  1500.       float duct_diameter = 0.2;
  1501.       float duct_radius = duct_diameter / 2.0;
  1502.       float duct_area = M_PI * pow(duct_radius, 2.0);
  1503.  
  1504.       // Odczytaj ciśnienie w hPa i przelicz na Pa
  1505.       float pressure_hpa = id(nawiew_raw_pressure).state;
  1506.       float pressure_pa = pressure_hpa * 100.0;
  1507.  
  1508.       // Sprawdź, czy ciśnienie dodatnie
  1509.       if (pressure_pa <= 0) {
  1510.         return 0;
  1511.       }
  1512.  
  1513.       // Oblicz prędkość i przepływ
  1514.       float velocity_mps = sqrt((2.0 * pressure_pa) / rho);
  1515.       float flow_m3_s = velocity_mps * duct_area;
  1516.  
  1517.       // Przelicz na m³/h
  1518.       return flow_m3_s * 3600.0;
  1519.  
  1520.   #####################################################################
  1521.   #                  KANAL WYWIEWU (POWIETRZE WYCHODZACE)             #
  1522.   #####################################################################
  1523.  
  1524.   # 3. Czujnik SDP810 dla wywiewu - surowe ciśnienie
  1525.   - platform: sdp3x
  1526.     name: "Cisnienie Roznicowe Wywiew"
  1527.     id: wywiew_raw_pressure
  1528.     address: 0x25
  1529.     i2c_id: bus_b # Zmień na ID Twojej magistrali dla wywiewu
  1530.     update_interval: 5s
  1531.     filters:
  1532.       - exponential_moving_average:
  1533.           alpha: 0.7
  1534.       #- lambda: 'return x > 0 ? x : 0;'
  1535.      
  1536.   # 4. Sensor szablonowy - oblicza przepływ dla wywiewu w m3/h
  1537.   - platform: template
  1538.     name: "Przeplyw Wywiew m3/h"
  1539.     id: wywiew_airflow_m3_h
  1540.     icon: "mdi:air-filter"
  1541.     unit_of_measurement: "m³/h"
  1542.     update_interval: 5s
  1543.     lambda: |-
  1544.       // Stałe fizyczne
  1545.       const float p_atm = 101325.0;    // Ciśnienie atmosferyczne w Pa
  1546.       const float R = 287.05;          // Stała gazowa dla powietrza J/(kg·K)
  1547.  
  1548.       // Pobierz temperaturę wywiewu i przelicz na Kelwiny
  1549.       float temp_c = id(wywiew_temp).state;
  1550.       float temp_k = temp_c + 273.15;
  1551.  
  1552.       // Oblicz gęstość powietrza (kg/m³) z równania gazu doskonałego
  1553.       float rho = p_atm / (R * temp_k);
  1554.  
  1555.       // Parametry kanału
  1556.       float duct_diameter = 0.2; // Średnica kanału (m)
  1557.       float duct_radius = duct_diameter / 2.0;
  1558.       float duct_area = M_PI * pow(duct_radius, 2.0);  // Pole przekroju ()
  1559.  
  1560.       // Ciśnienie dynamiczne w hPa → Pa
  1561.       float pressure_hpa = id(wywiew_raw_pressure).state;
  1562.       float pressure_pa = pressure_hpa * 100.0;
  1563.  
  1564.       // Jeśli ciśnienie ≤ 0, zwróć 0
  1565.       if (pressure_pa <= 0) {
  1566.         return 0.0;
  1567.       }
  1568.  
  1569.       // Oblicz prędkość przepływu (m/s)
  1570.       float velocity_mps = sqrt((2.0 * pressure_pa) / rho);
  1571.  
  1572.       // Oblicz przepływ objętościowy (m³/s) → przelicz na m³/h
  1573.       float flow_m3_s = velocity_mps * duct_area;
  1574.  
  1575.       return flow_m3_s * 3600.0;
  1576.  
  1577.  
  1578. # 5. Sensor szablonowy - oblicza różnicę przepływu (Nawiew - Wywiew)
  1579.   - platform: template
  1580.     name: "Roznica Przeplywu"
  1581.     id: flow_difference_m3_h
  1582.     icon: "mdi:compare-arrows"
  1583.     unit_of_measurement: "m³/h"
  1584.     update_interval: 10s
  1585.     lambda: |-
  1586.       if (isnan(id(nawiew_airflow_m3_h).state) || isnan(id(wywiew_airflow_m3_h).state)) {
  1587.         return 0.0;
  1588.       }
  1589.       return id(nawiew_airflow_m3_h).state - id(wywiew_airflow_m3_h).state;
  1590.     on_value:
  1591.       - if:
  1592.           condition:
  1593.            # 1. Sprawdź, czy balans przepływu jest włączony
  1594.             - switch.is_on: balans_przeplywu_switch
  1595.             # 2. Sprawdź, czy minął wystarczający czas od ostatniej korekty
  1596.             - lambda: |-
  1597.                 // Czas opóźnienia w minutach (np. 5 minut)
  1598.                 const int delay_minutes = 5; // <--- TUTAJ ZMIENIASZ CZAS OPÓŹNIENIA W MINUTACH
  1599.                 const int delay_seconds = delay_minutes * 60; // Przeliczenie na sekundy
  1600.  
  1601.                 time_t current_time = id(czas_systemowy).now().timestamp;
  1602.                 int last_correction_time = id(ostatni_timestamp_korekty);
  1603.                 return (current_time - last_correction_time) >= delay_seconds;
  1604.           then:
  1605.             - logger.log:
  1606.                 format: "Różnica przepływu: %.1f m³/h"
  1607.                 args: ["id(flow_difference_m3_h).state"]
  1608.  
  1609.             - if:
  1610.                 condition:
  1611.                   lambda: return id(flow_difference_m3_h).state > 30; # Nadciśnienie - nawiew o 25 m³/h większy od wywiewu
  1612.                 then:
  1613.                   - script.execute:
  1614.                       id: sprawdz_ograniczenie_korekt
  1615.                       korekta_id: 1 # Wywołuje korekta_do_nadcisnienia (zwiększa wywiew)
  1616.  
  1617.             - if:
  1618.                 condition:
  1619.                   lambda: return id(flow_difference_m3_h).state < -30; # Podciśnienie - wywiew o 25 m³/h większy od nawiewu
  1620.                 then:
  1621.                   - script.execute:
  1622.                       id: sprawdz_ograniczenie_korekt
  1623.                       korekta_id: 2 # Wywołuje korekta_do_podcisnienia (zmniejsza wywiew)
  1624.  
  1625.  
  1626.  
  1627.  ############   PZEM-004T V3 Reku
  1628.   - platform: pzemac
  1629.     modbus_id: mod_bus_pzem
  1630.     address: 1
  1631.     id: pzemac_1
  1632.     current:
  1633.       name: "Rekuperator prąd"
  1634.       accuracy_decimals: 2
  1635.       id: reku_prad
  1636.     voltage:
  1637.       name: "Rekuperator napięcie"
  1638.       unit_of_measurement: V
  1639.       accuracy_decimals: 2
  1640.       id: reku_napiecie
  1641.     energy:
  1642.       name: "Rekuperator zużycie energii"
  1643.       filters:
  1644.        # Wh to kWh is 0.001
  1645.         - multiply: 0.001
  1646.       unit_of_measurement: kWh
  1647.       accuracy_decimals: 3
  1648.       id: pg_energia
  1649.     power:
  1650.       name: "Rekuperator moc"
  1651.       unit_of_measurement: W
  1652.       accuracy_decimals: 2
  1653.       id: reku_moc
  1654.     frequency:
  1655.       name: "Rekuperator częstotliwość"
  1656.       unit_of_measurement: Hz
  1657.       accuracy_decimals: 2
  1658.     power_factor:
  1659.       name: "Rekuperator współczynnik mocy"
  1660.       accuracy_decimals: 2
  1661.       id: reku_power_factor
  1662.     update_interval: 5s
  1663.  
  1664.   - platform: total_daily_energy
  1665.     name: "Rekuperator dzienne zużycie energii"
  1666.     power_id: pg_energia
  1667.     unit_of_measurement: "kWh"
  1668.     accuracy_decimals: 3
  1669.     id: pg_daily_kwh
  1670.     icon: mdi:counter
  1671.     device_class: energy
  1672.     filters:
  1673.      # Multiplication factor from W to kW is 0.001
  1674.       - multiply: 0.001
  1675.  
  1676.   - platform: template
  1677.     name: ${friendly_name} sprawność
  1678.     unit_of_measurement: "%"
  1679.     lambda: |-
  1680.       float dzielnik = id(wywiew_temp).state - id(czerpnia_temp).state;
  1681.       if (dzielnik != 0) {
  1682.         float dzielna = id(nawiew_temp).state - id(czerpnia_temp).state;
  1683.         return (dzielna / dzielnik) * 100.0;
  1684.       } else {
  1685.         // Zwróć 0 lub inną domyślną wartość, gdy nie można wykonać obliczeń
  1686.         return 0;
  1687.       }
  1688.     accuracy_decimals: 1
  1689.     update_interval: 15s
  1690.  
  1691.    # sprawność temperaturowa = (T2-T1)/(T3-T1)
  1692.    # T1 – temperatura powietrza zewnętrznego (nawiewanego przed wymiennikiem) [°C]
  1693.    # T2 – temperatura powietrza nawiewanego za wymiennikiem [°C]
  1694.    # T3– temperatura powietrza wywiewanego z pomieszczeń przed wymiennikiem [°C]
  1695.  
  1696.   - platform: wifi_signal
  1697.     name: ${friendly_name} RSSI
  1698.     update_interval: 60s
  1699.  
  1700.   - platform: modbus_controller
  1701.     modbus_controller_id: reventon_reku
  1702.     name: ${friendly_name} Temperatura wywiew
  1703.     id: wywiew_temp
  1704.     register_type: holding
  1705.     address: 0x000c #12
  1706.     unit_of_measurement: "°C"
  1707.     device_class: "temperature"
  1708.     value_type: U_WORD
  1709.     filters:
  1710.       - lambda: return x - 40.0;
  1711.     #accuracy_decimals: 1
  1712.  
  1713.   - platform: modbus_controller
  1714.     modbus_controller_id: reventon_reku
  1715.     name: ${friendly_name} Temperatura czerpnia
  1716.     id: czerpnia_temp
  1717.     register_type: holding
  1718.     address: 0x000d #13
  1719.     unit_of_measurement: "°C"
  1720.     device_class: "temperature"
  1721.     value_type: U_WORD
  1722.     filters:
  1723.       - lambda: return x - 40.0;
  1724.     #accuracy_decimals: 1
  1725.    
  1726.   - platform: modbus_controller
  1727.     modbus_controller_id: reventon_reku
  1728.     name: ${friendly_name} Temperatura wyrzutnia
  1729.     id: wyrzutnia_temp
  1730.     register_type: holding
  1731.     address: 0x000f #15
  1732.     unit_of_measurement: "°C"
  1733.     device_class: "temperature"
  1734.     value_type: U_WORD
  1735.     filters:
  1736.       - lambda: return x - 40.0;
  1737.     #accuracy_decimals: 1
  1738.      
  1739.   - platform: modbus_controller
  1740.     modbus_controller_id: reventon_reku
  1741.     name: ${friendly_name} Temperatura nawiew
  1742.     id: nawiew_temp
  1743.     register_type: holding
  1744.     address: 0x000e #14
  1745.     unit_of_measurement: "°C"
  1746.     device_class: "temperature"
  1747.     value_type: U_WORD
  1748.     filters:
  1749.       - lambda: return x - 40.0;
  1750.     #accuracy_decimals: 1
  1751.    
  1752.   - platform: modbus_controller
  1753.     modbus_controller_id: reventon_reku
  1754.     name: ${friendly_name} CO2
  1755.     id: co2_reku
  1756.     register_type: holding
  1757.     address: 0x0300 #768
  1758.     unit_of_measurement: "ppm"
  1759.     device_class: "carbon_dioxide"
  1760.     value_type: U_WORD
  1761.     skip_updates: 100    
  1762.     #accuracy_decimals: 1
  1763.      
  1764.   - platform: modbus_controller
  1765.     modbus_controller_id: reventon_reku
  1766.     name: ${friendly_name} Fan running time
  1767.     id: fan_running_time
  1768.     register_type: holding
  1769.     address: 0x0301 #769
  1770.     unit_of_measurement: "min"
  1771.     value_type: U_WORD
  1772.     filters:
  1773.       - lambda: return x * 6.0;
  1774.     skip_updates: 12  
  1775.     #accuracy_decimals: 1
  1776.      
  1777.   - platform: modbus_controller
  1778.     modbus_controller_id: reventon_reku
  1779.     name: ${friendly_name} Wilgotność
  1780.     id: wilgotnosc_reku
  1781.     register_type: holding
  1782.     address: 0x0302 #770
  1783.     unit_of_measurement: "%"
  1784.     device_class: "humidity"
  1785.     value_type: U_WORD
  1786.     skip_updates: 10      
  1787.     #accuracy_decimals: 1
  1788.      
  1789. binary_sensor:
  1790.     - platform: template
  1791.       name: "Rekuperator Potrzeba Osuszania"
  1792.       id: rekuperator_potrzeba_osuszania
  1793.       device_class: moisture
  1794.       lambda: |-
  1795.         float hum = 0.0;
  1796.         if (id(max_humidity_all_sensors).has_state()) {
  1797.             hum = id(max_humidity_all_sensors).state;
  1798.         } else {
  1799.             ESP_LOGW("Potrzeba Osuszania", "Maksymalna wilgotność nie jest dostępna dla sensora 'Potrzeba Osuszania'!");
  1800.             return id(potrzeba_osuszania_histereza_state);
  1801.         }
  1802.  
  1803.         // Pobierz aktualną godzinę
  1804.         auto t = id(czas_systemowy).now();
  1805.         int hour = t.hour;
  1806.  
  1807.         // Sprawdź, czy jest noc (np. od 23:00 do 5:59 rano)
  1808.         bool is_night_time = (hour >= 23 || hour < 6);
  1809.  
  1810.         bool current_histereza_state = id(potrzeba_osuszania_histereza_state);
  1811.         bool new_histereza_state = current_histereza_state;
  1812.  
  1813.         // --- Logika dla pory DZIENNEJ (standardowe progi) ---
  1814.         if (!is_night_time) {
  1815.           if (hum > id(wilgotnosc_wysoka).state) {
  1816.             new_histereza_state = true;
  1817.           } else if (hum < id(wilgotnosc_niska).state) {
  1818.             new_histereza_state = false;
  1819.           }
  1820.         }
  1821.         // --- Logika dla pory NOCNEJ (progi nocne lub brak aktywacji) ---
  1822.         else { // is_night_time jest true
  1823.           // OPCJA 1: Użyj innych progów dla nocy (mniej restrykcyjne lub inne)
  1824.           // Jeśli masz zdefiniowane wilgotnosc_nocna_high i wilgotnosc_nocna_low, możesz ich użyć
  1825.           if (hum > id(wilgotnosc_nocna_high).state) { // Progi zdefiniowane dla trybu nocnego
  1826.             new_histereza_state = true;
  1827.           } else if (hum < id(wilgotnosc_nocna_low).state) { // Progi zdefiniowane dla trybu nocnego
  1828.             new_histereza_state = false;
  1829.           }
  1830.          
  1831.           // OPCJA 2: Całkowite wyłączenie sensora w nocy (zawsze FALSE)
  1832.           // Jeśli wolisz, żeby klimatyzator nie osuszał w nocy w ogóle, odkomentuj poniższą linię:
  1833.          // new_histereza_state = false;
  1834.           // Wtedy powyższa logika histerezy dla nocy nie miałaby znaczenia.
  1835.         }
  1836.  
  1837.  
  1838.         if (new_histereza_state != current_histereza_state) {
  1839.           id(potrzeba_osuszania_histereza_state) = new_histereza_state;
  1840.           ESP_LOGI("Potrzeba Osuszania", "Zmiana statusu 'Potrzeba Osuszania' z %s na %s (Wilgotnosc: %.1f, Pora nocna: %s)",
  1841.                   (current_histereza_state ? "ON" : "OFF"), (new_histereza_state ? "ON" : "OFF"), hum, (is_night_time ? "TAK" : "NIE"));
  1842.         }
  1843.        
  1844.         return new_histereza_state;
  1845.  
  1846.  
  1847.     - platform: homeassistant
  1848.       id: okno_salon
  1849.       entity_id: binary_sensor.shelly_blu_door_window_ce04_window
  1850.  
  1851.     - platform: homeassistant
  1852.       id: okno_salon2
  1853.       entity_id: binary_sensor.shelly_blu_door_window_24b7_window
  1854.  
  1855.     - platform: homeassistant
  1856.       id: okno_gabinet_l
  1857.       entity_id: binary_sensor.shelly_blu_door_window_cc52_window
  1858.  
  1859.     - platform: homeassistant
  1860.       id: okno_gabinet_p
  1861.       entity_id: binary_sensor.shelly_blu_door_window_882d_window
  1862.  
  1863.     - platform: homeassistant
  1864.       id: okno_lazienka
  1865.       entity_id: binary_sensor.shelly_blu_door_window_06b3_window
  1866.  
  1867.     - platform: homeassistant
  1868.       id: dom_pusty
  1869.       entity_id: input_boolean.dom_pusty
  1870.       internal: False
  1871.       name: Dom pusty
  1872.  
  1873.     - platform: template
  1874.       id: czy_jakies_okno_otwarte
  1875.       name: "Czy jakiekolwiek okno otwarte"
  1876.       lambda: |-
  1877.         return id(okno_salon).state || id(okno_salon2).state || id(okno_gabinet_l).state
  1878.         || id(okno_gabinet_p).state || id(okno_lazienka).state;
  1879.       #on_press:
  1880.        # then:
  1881.         #  - if:
  1882.          #     condition:
  1883.           #      lambda: return id(aktywny_tryb) != "okno";
  1884.            #   then:
  1885.             #    - logger.log: "Wykryto otwarte okno — przełączam na tryb OKNO"
  1886.              #   - script.execute:
  1887.               #      id: set_mode
  1888.                #     tryb: "okno"
  1889.  
  1890.  
  1891.       #on_release:
  1892.        # then:
  1893.         #  - if:
  1894.          #     condition:
  1895.           #      lambda: return id(aktywny_tryb) == "okno";
  1896.            #   then:
  1897.             #    - logger.log: "Wszystkie okna zamknięte — przywracam tryb AUTO"
  1898.              #   - script.execute:
  1899.               #      id: set_mode
  1900.                #     tryb: "auto"
  1901.  
  1902.     - platform: status
  1903.       name: ${friendly_name} Logger Status
  1904.  
  1905.     - platform: modbus_controller
  1906.       modbus_controller_id: reventon_reku
  1907.       name: ${friendly_name} On/Off status
  1908.       #device_class: running
  1909.       register_type: holding
  1910.       #entity_category: diagnostic
  1911.       address: 0x0009 #9
  1912.       #bitmask: 0x1
  1913.      
  1914.     - platform: modbus_controller
  1915.       modbus_controller_id: reventon_reku
  1916.       name: ${friendly_name} Auto restart status
  1917.       #device_class: running
  1918.       register_type: holding
  1919.       #entity_category: diagnostic
  1920.       address: 0x0000 #0
  1921.       #bitmask: 0x1
  1922.      
  1923.     - platform: modbus_controller
  1924.       modbus_controller_id: reventon_reku
  1925.       name: ${friendly_name} Nagrzewnica status
  1926.       #device_class: running
  1927.       register_type: holding
  1928.       #entity_category: diagnostic
  1929.       address: 0x0001 #1
  1930.       #bitmask: 0x1
  1931.      
  1932.     - platform: modbus_controller
  1933.       modbus_controller_id: reventon_reku
  1934.       name: ${friendly_name} Zewnętrzny sygnał ON/OFF
  1935.       #device_class: running
  1936.       register_type: holding
  1937.       #entity_category: diagnostic
  1938.       address: 0x0010 #16
  1939.       #bitmask: 0x1
  1940.      
  1941.     - platform: modbus_controller
  1942.       modbus_controller_id: reventon_reku
  1943.       name: ${friendly_name} Sygnał ON/OFF czujnika CO2
  1944.       #device_class: running
  1945.       register_type: holding
  1946.       #entity_category: diagnostic
  1947.       address: 0x0011 #17
  1948.       #bitmask: 0x1
  1949.  
  1950. switch:
  1951.   - platform: template
  1952.     name: "Auto tryb zimowy"
  1953.     id: auto_tryb_zimowy
  1954.     icon: "mdi:snowflake"
  1955.     optimistic: true
  1956.     restore_mode: RESTORE_DEFAULT_OFF
  1957.     on_turn_on:
  1958.       then:
  1959.         - lambda: |-
  1960.             // Sprawdź czy nie jesteśmy w trybie okno (celowa dysproporcja)
  1961.             if (id(aktywny_tryb) == "okno") {
  1962.               ESP_LOGW("TrybZimowy", "Nie można włączyć trybu zimowego - aktywny jest tryb okno z celową dysproporcją");
  1963.               id(auto_tryb_zimowy).publish_state(false);
  1964.               return;
  1965.             }
  1966.            
  1967.             // Zapisz aktualny stan balansu i oryginalne obroty przed włączeniem trybu zimowego
  1968.             id(poprzedni_balans_przed_zimowym) = id(balans_przeplywu_switch).state;
  1969.             id(oryginalne_obroty_nawiew) = id(wentylator_nawiewny).state;
  1970.             id(oryginalne_obroty_wywiew) = id(wentylator_wywiewny).state;
  1971.             // Wyłącz balans przepływu dla trybu zimowego
  1972.             id(balans_przeplywu_switch).turn_off();
  1973.             id(tryb_zimowy_aktywny) = false; // Zacznij od nieaktywnego stanu
  1974.             ESP_LOGI("TrybZimowy", "Auto tryb zimowy włączony. Zapamiętano obroty: nawiew=%d, wywiew=%d",
  1975.                     id(oryginalne_obroty_nawiew), id(oryginalne_obroty_wywiew));
  1976.     on_turn_off:
  1977.       then:
  1978.        # Najpierw przywróć stan balansu
  1979.         - if:
  1980.             condition:
  1981.               lambda: 'return id(poprzedni_balans_przed_zimowym);'
  1982.             then:
  1983.               - switch.turn_on: balans_przeplywu_switch
  1984.             else:
  1985.               - switch.turn_off: balans_przeplywu_switch
  1986.        
  1987.         # Opóźnienie dla ustabilizowania balansu
  1988.         - delay: 100ms
  1989.        
  1990.         # Przywróć oryginalne obroty (jeśli tryb zimowy był aktywny)
  1991.         - if:
  1992.             condition:
  1993.               lambda: 'return id(tryb_zimowy_aktywny);'
  1994.             then:
  1995.               - number.set:
  1996.                   id: wentylator_nawiewny
  1997.                   value: !lambda 'return id(oryginalne_obroty_nawiew);'
  1998.               - number.set:
  1999.                   id: wentylator_wywiewny
  2000.                   value: !lambda 'return id(oryginalne_obroty_wywiew);'
  2001.               - logger.log: "Przywrócono oryginalne obroty po wyłączeniu trybu zimowego"
  2002.        
  2003.         # Zakończ tryb zimowy
  2004.         - lambda: |-
  2005.             id(tryb_zimowy_aktywny) = false;
  2006.             ESP_LOGI("TrybZimowy", "Auto tryb zimowy wyłączony. Przywrócono balans: %s",
  2007.                     id(poprzedni_balans_przed_zimowym) ? "ON" : "OFF");
  2008.  
  2009.   - platform: template
  2010.     name: "Tryb przewietrzania"
  2011.     id: tryb_przewietrzania_switch
  2012.     optimistic: true
  2013.     restore_mode: RESTORE_DEFAULT_OFF
  2014.     on_turn_on:
  2015.       - script.execute: wlacz_przewietrzanie
  2016.     on_turn_off:
  2017.       - script.execute: zakoncz_przewietrzanie
  2018.  
  2019.   - platform: template
  2020.     name: "Automatyczny Balans Przeplywu"
  2021.     id: balans_przeplywu_switch
  2022.     icon: "mdi:light-switch"
  2023.     # Ustawia przełącznik na wyłączony po restarcie ESP
  2024.     restore_mode: RESTORE_DEFAULT_OFF
  2025.     optimistic: true
  2026.  
  2027.   - platform: template
  2028.     name: ${friendly_name} switch
  2029.     id: reku_switch
  2030.     restore_mode: RESTORE_DEFAULT_OFF
  2031.     lambda: |-
  2032.       if (id(reku_on_off_switch).state==1) {
  2033.         return true;
  2034.       } else {
  2035.         return false;
  2036.       }
  2037.     turn_on_action:
  2038.       - number.set:
  2039.           id: reku_on_off_switch
  2040.           value: 1
  2041.     turn_off_action:
  2042.       - number.set:
  2043.           id: reku_on_off_switch
  2044.           value: 0
  2045.  
  2046. number:
  2047.     - platform: template
  2048.       name: "Czas osuszania"
  2049.       id: osuszanie_czas_trwania_minuty
  2050.       min_value: 5
  2051.       max_value: 240 # Np. do 4 godzin
  2052.       step: 5
  2053.       initial_value: 60 # Domyślnie 60 minut (1 godzina)
  2054.       unit_of_measurement: "min"
  2055.       mode: box
  2056.       restore_value: yes
  2057.       optimistic: True
  2058.  
  2059.     - platform: template
  2060.       name: "Wilgotnosc Nocna - Aktualny Poziom"
  2061.       id: hum_nocna_current_level_state
  2062.       min_value: 0
  2063.       max_value: 1 # 0 dla niskiej wilgotności (obroty 2), 1 dla wysokiej (obroty 4)
  2064.       step: 1
  2065.       mode: BOX
  2066.       optimistic: True
  2067.       initial_value: 0 # Domyślny poziom po starcie (0 = niska wilgotność)
  2068.       internal: true
  2069.  
  2070.     - platform: template
  2071.       name: "CO2 Nocny - Aktualny Poziom"
  2072.       id: co2_nocny_current_level_state
  2073.       min_value: 0
  2074.       max_value: 1 # W trybie nocnym masz tylko dwa poziomy: niski (2) i wysoki (5)
  2075.       step: 1
  2076.       mode: BOX
  2077.       optimistic: True
  2078.       initial_value: 0 # Domyślny poziom po starcie (0 = niski/normalny)
  2079.       internal: false
  2080.  
  2081.     - platform: template
  2082.       name: "CO2 Auto - Aktualny Poziom"
  2083.       id: co2_auto_current_level_state
  2084.       min_value: 0
  2085.       max_value: 3
  2086.       step: 1
  2087.       mode: BOX
  2088.       optimistic: true
  2089.       initial_value: 0 # Domyślny poziom po starcie (0 = normalny/niski)
  2090.       internal: false #
  2091.  
  2092.     - platform: template
  2093.       name: "Czas trwania trybu Impreza"
  2094.       id: impreza_czas_trwania_godz
  2095.       min_value: 1
  2096.       max_value: 8
  2097.       step: 1
  2098.       unit_of_measurement: "h"
  2099.       mode: BOX
  2100.       restore_value: yes
  2101.       initial_value: 5 # Domyslny czas trwania 5 godzin
  2102.       optimistic: true
  2103.  
  2104.     - platform: template
  2105.       name: "Czas trwania trybu Okap"
  2106.       id: okap_czas_trwania_min
  2107.       min_value: 5
  2108.       max_value: 60
  2109.       step: 5
  2110.       unit_of_measurement: "min"
  2111.       mode: BOX
  2112.       restore_value: yes
  2113.       initial_value: 15 # Domyslny czas trwania 15 minut
  2114.       optimistic: true
  2115.  
  2116.     - id: co2_auto_high
  2117.       name: "CO2 Auto High"
  2118.       min_value: 400
  2119.       max_value: 2000
  2120.       step: 10
  2121.       unit_of_measurement: "ppm"
  2122.       optimistic: true
  2123.       initial_value: 1200
  2124.       platform: template
  2125.       mode: box
  2126.  
  2127.     - id: co2_auto_medium
  2128.       name: "CO2 Auto Medium"
  2129.       min_value: 400
  2130.       max_value: 2000
  2131.       step: 10
  2132.       unit_of_measurement: "ppm"
  2133.       optimistic: true
  2134.       initial_value: 1000
  2135.       platform: template
  2136.       mode: box
  2137.  
  2138.     - id: co2_auto_low
  2139.       name: "CO2 Auto Low"
  2140.       min_value: 400
  2141.       max_value: 2000
  2142.       step: 10
  2143.       unit_of_measurement: "ppm"
  2144.       optimistic: true
  2145.       initial_value: 800
  2146.       platform: template
  2147.       mode: box
  2148.  
  2149.     - id: co2_nocny_high
  2150.       name: "CO2 Nocny High"
  2151.       min_value: 400
  2152.       max_value: 2000
  2153.       step: 10
  2154.       unit_of_measurement: "ppm"
  2155.       optimistic: true
  2156.       initial_value: 800
  2157.       platform: template
  2158.       mode: box
  2159.  
  2160.     - id: co2_nocny_low
  2161.       name: "CO2 Nocny Low"
  2162.       min_value: 400
  2163.       max_value: 2000
  2164.       step: 10
  2165.       unit_of_measurement: "ppm"
  2166.       optimistic: true
  2167.       initial_value: 700
  2168.       platform: template
  2169.       mode: box
  2170.  
  2171.     - id: wilgotnosc_wysoka
  2172.       name: "Wilgotność Wysoka"
  2173.       min_value: 40
  2174.       max_value: 100
  2175.       step: 1
  2176.       unit_of_measurement: "%"
  2177.       optimistic: true
  2178.       initial_value: 70
  2179.       platform: template
  2180.       mode: box
  2181.  
  2182.     - id: wilgotnosc_niska
  2183.       name: "Wilgotność Niska"
  2184.       min_value: 30
  2185.       max_value: 100
  2186.       step: 1
  2187.       unit_of_measurement: "%"
  2188.       optimistic: true
  2189.       initial_value: 55
  2190.       platform: template
  2191.       mode: box
  2192.  
  2193.     - id: wilgotnosc_nocna_high
  2194.       name: "Wilgotność nocna wysoka"
  2195.       min_value: 60
  2196.       max_value: 100
  2197.       step: 1
  2198.       unit_of_measurement: "%"
  2199.       optimistic: true
  2200.       initial_value: 80
  2201.       platform: template
  2202.       mode: box
  2203.  
  2204.     - id: wilgotnosc_nocna_low
  2205.       name: "Wilgotność nocna niska"
  2206.       min_value: 30
  2207.       max_value: 100
  2208.       step: 1
  2209.       unit_of_measurement: "%"
  2210.       optimistic: true
  2211.       initial_value: 65
  2212.       platform: template
  2213.       mode: box
  2214.  
  2215.     - platform: template
  2216.       name: "Czas przewietrzania"
  2217.       id: czas_przewietrzania_minuty
  2218.       min_value: 1
  2219.       max_value: 30
  2220.       step: 1
  2221.       unit_of_measurement: "min"
  2222.       optimistic: true
  2223.       restore_value: true
  2224.       initial_value: 10
  2225.       mode: box
  2226.  
  2227.  
  2228.     - platform: modbus_controller
  2229.       modbus_controller_id: reventon_reku
  2230.       id: reku_on_off_switch
  2231.       address: 0x0009 #9
  2232.       value_type: U_WORD
  2233.       min_value: 0
  2234.       max_value: 1
  2235.       step: 1
  2236.       mode: box
  2237.       internal: true
  2238.  
  2239.     - platform: modbus_controller
  2240.       modbus_controller_id: reventon_reku
  2241.       name: ${friendly_name} Temperatura X otwarcia bypass’u
  2242.       address: 0x0002 #2
  2243.       unit_of_measurement: "°C"
  2244.       id: temp_x_bypass
  2245.       value_type: U_WORD
  2246.       min_value: 5
  2247.       max_value: 30
  2248.       step: 1
  2249.       mode: box
  2250.       entity_category: config
  2251.  
  2252.      
  2253.     - platform: modbus_controller
  2254.       modbus_controller_id: reventon_reku
  2255.       name: ${friendly_name} Temperatura Y otwarcia bypass’u
  2256.       address: 0x0003 #3
  2257.       unit_of_measurement: "°C"
  2258.       id: temp_y_bypass
  2259.       value_type: U_WORD
  2260.       min_value: 2
  2261.       max_value: 15
  2262.       step: 1
  2263.       mode: box
  2264.       entity_category: config      
  2265.      
  2266.     - platform: modbus_controller
  2267.       modbus_controller_id: reventon_reku
  2268.       name: ${friendly_name} Interwał odszraniania
  2269.       address: 0x0004 #4
  2270.       unit_of_measurement: "min"
  2271.       #device_class: "temperature"
  2272.       id: defrosting_interval
  2273.       value_type: U_WORD
  2274.       min_value: 15
  2275.       max_value: 99
  2276.       step: 1
  2277.       mode: box
  2278.       entity_category: config      
  2279.      
  2280.     - platform: modbus_controller
  2281.       modbus_controller_id: reventon_reku
  2282.       name: ${friendly_name} Temperatura odszraniania
  2283.       address: 0x0005 #5
  2284.       unit_of_measurement: "°C"
  2285.       id: defrosting_enter_temperature
  2286.       value_type: U_WORD
  2287.       min_value: -9
  2288.       max_value: 5
  2289.       lambda: "return x - 40.0;"
  2290.       write_lambda: "return x + 40.0;"
  2291.       step: 1
  2292.       mode: box
  2293.       entity_category: config      
  2294.      
  2295.     - platform: modbus_controller
  2296.       modbus_controller_id: reventon_reku
  2297.       name: ${friendly_name} Czas odszraniania
  2298.       address: 0x0006 #6
  2299.       unit_of_measurement: "min"
  2300.       #device_class: "temperature"
  2301.       id: defrost_duration_time
  2302.       value_type: U_WORD
  2303.       min_value: 2
  2304.       max_value: 20
  2305.       step: 1
  2306.       mode: box
  2307.       entity_category: config      
  2308.      
  2309.     - platform: modbus_controller
  2310.       modbus_controller_id: reventon_reku
  2311.       name: ${friendly_name} Czujnik CO2
  2312.       address: 0x0007 #7
  2313.       unit_of_measurement: "ppm"
  2314.       id: co2_set
  2315.       value_type: U_WORD
  2316.       min_value: 00
  2317.       max_value: 2500
  2318.       lambda: "return x * 10.0;"
  2319.       write_lambda: "return x / 10.0;"
  2320.       step: 1
  2321.       mode: box
  2322.       entity_category: config  
  2323.       skip_updates: 20      
  2324.      
  2325.     - platform: modbus_controller
  2326.       modbus_controller_id: reventon_reku
  2327.       name: ${friendly_name} Wentylator nawiewny
  2328.       icon: "mdi:fan-speed-1"
  2329.       address: 0x000a #10
  2330.       lambda: |-
  2331.          if (x == 2) {
  2332.          return 1;
  2333.          }
  2334.          if (x == 3) {
  2335.          return 2;
  2336.          }
  2337.          if (x == 5) {
  2338.          return 3;          
  2339.          }
  2340.          if (x == 8) {
  2341.          return 4;          
  2342.          }        
  2343.          if (x == 9) {
  2344.          return 5;          
  2345.          }          
  2346.          if (x == 10) {
  2347.          return 6;          
  2348.          }            
  2349.          if (x == 11) {
  2350.          return 7;          
  2351.          }            
  2352.          if (x == 12) {
  2353.          return 8;          
  2354.          }
  2355.          if (x == 13) {
  2356.          return 9;          
  2357.          }
  2358.          if (x == 14) {
  2359.          return 10;          
  2360.          } else {
  2361.           return NAN;
  2362.          }
  2363.       write_lambda: |-
  2364.          if (x == 1) {
  2365.          return 2;
  2366.          }
  2367.          if (x == 2) {
  2368.          return 3;
  2369.          }
  2370.          if (x == 3) {
  2371.          return 5;          
  2372.          }
  2373.          if (x == 4) {
  2374.          return 8;          
  2375.          }        
  2376.          if (x == 5) {
  2377.          return 9;          
  2378.          }          
  2379.          if (x == 6) {
  2380.          return 10;          
  2381.          }            
  2382.          if (x == 7) {
  2383.          return 11;          
  2384.          }            
  2385.          if (x == 8) {
  2386.          return 12;          
  2387.          }
  2388.          if (x == 9) {
  2389.          return 13;          
  2390.          }
  2391.          if (x == 10) {
  2392.          return 14;          
  2393.          } else {
  2394.           return NAN;
  2395.          }  
  2396.       #unit_of_measurement: "%"
  2397.       id: wentylator_nawiewny
  2398.       value_type: U_WORD
  2399.       entity_category: ""
  2400.       min_value: 1
  2401.       max_value: 10
  2402.       step: 1
  2403.       mode: slider
  2404.  
  2405.     - platform: modbus_controller
  2406.       modbus_controller_id: reventon_reku
  2407.       name: ${friendly_name} Wentylator wywiewny
  2408.       icon: "mdi:fan-speed-1"
  2409.       address: 0x000b #11
  2410.       lambda: |-
  2411.          if (x == 2) {
  2412.          return 1;
  2413.          }
  2414.          if (x == 3) {
  2415.          return 2;
  2416.          }
  2417.          if (x == 5) {
  2418.          return 3;          
  2419.          }
  2420.          if (x == 8) {
  2421.          return 4;          
  2422.          }        
  2423.          if (x == 9) {
  2424.          return 5;          
  2425.          }          
  2426.          if (x == 10) {
  2427.          return 6;          
  2428.          }            
  2429.          if (x == 11) {
  2430.          return 7;          
  2431.          }            
  2432.          if (x == 12) {
  2433.          return 8;          
  2434.          }
  2435.          if (x == 13) {
  2436.          return 9;          
  2437.          }
  2438.          if (x == 14) {
  2439.          return 10;          
  2440.          } else {
  2441.           return NAN;
  2442.          }
  2443.       write_lambda: |-
  2444.          if (x == 1) {
  2445.          return 2;
  2446.          }
  2447.          if (x == 2) {
  2448.          return 3;
  2449.          }
  2450.          if (x == 3) {
  2451.          return 5;          
  2452.          }
  2453.          if (x == 4) {
  2454.          return 8;          
  2455.          }        
  2456.          if (x == 5) {
  2457.          return 9;          
  2458.          }          
  2459.          if (x == 6) {
  2460.          return 10;          
  2461.          }            
  2462.          if (x == 7) {
  2463.          return 11;          
  2464.          }            
  2465.          if (x == 8) {
  2466.          return 12;          
  2467.          }
  2468.          if (x == 9) {
  2469.          return 13;          
  2470.          }
  2471.          if (x == 10) {
  2472.          return 14;          
  2473.          } else {
  2474.           return NAN;
  2475.          }  
  2476.       #unit_of_measurement: "%"
  2477.       id: wentylator_wywiewny
  2478.       value_type: U_WORD
  2479.       entity_category: ""
  2480.       min_value: 1
  2481.       max_value: 10
  2482.       step: 1
  2483.       mode: slider    
  2484.      
  2485. select:
  2486.   - platform: template
  2487.     name: "Tryb rekuperatora (ESP)"
  2488.     id: tryb_select_esp
  2489.     optimistic: true
  2490.     options:
  2491.      - auto
  2492.       - normalny
  2493.       - nocny
  2494.       - impreza
  2495.       - urlopowy
  2496.       - serwisowy
  2497.       - okno
  2498.       - okap_auto
  2499.     initial_option: auto
  2500.     restore_value: yes
  2501.     on_value:
  2502.       then:
  2503.         - script.execute:
  2504.             id: set_mode
  2505.             mode: !lambda 'return x;'
  2506.             target_reku_speed: 0
  2507.  
  2508.   - platform: modbus_controller
  2509.     modbus_controller_id: reventon_reku
  2510.     name: ${friendly_name} Ustawienia wielofunkcyjne
  2511.     icon: "mdi:fan"
  2512.     id: multi_sel
  2513.     address: 0x0018 #24
  2514.     entity_category: config
  2515.     value_type: U_WORD
  2516.     optimistic: false
  2517.     optionsmap:
  2518.       "Kasuj": 0
  2519.       "Usunięcie alarmu filtra": 1
  2520.       "Usunięcie harmonogramu tygodniowego": 2
  2521.      
  2522.   - platform: modbus_controller
  2523.     modbus_controller_id: reventon_reku
  2524.     name: ${friendly_name} Ustawienia alarmu filtra
  2525.     icon: "mdi:fan"
  2526.     id: filter_sel
  2527.     address: 0x0019 #25
  2528.     entity_category: config
  2529.     value_type: U_WORD
  2530.     optimistic: false
  2531.     optionsmap:
  2532.       "45 dni": 0
  2533.       "60 dni": 1
  2534.       "90 dni": 2
  2535.       "180 dni": 3
  2536.  
  2537.  
Advertisement
Comments
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • User was banned
Add Comment
Please, Sign In to add comment