ESP32 i podwójne zliczanie impulsów

Początki są trudne. Postanowiłem podłączyć licznik SMD72Bi do homeassistanta. Licznik jest dwukierunkowy, ale zaznaczam na początku jest wykorzystywany jako podlicznik do PC i liczy tylko pobór. Ma wyjście impulsowe 1000im/kWh zamykające obwód w momencie mignięcia diody. Podłączyłem to wyjście bezpośrednio do GND i D4 (GPIO4) na płytce ESP32.
Kompletnie się na tym nie znam i działa na tym co znajdę w internecie część rozumiejąc, a część robiąc kopiuj/wklej. ESP32 jest już widoczne w homeassitant i z tym nie ma problemu, encje odczytuje itp.
Problem1.
Zliczam impulsy w ten sposób:

sensor:
  - platform: pulse_counter
    pin: GPIO4
    unit_of_measurement: "kWh"
    name: "Licznik energii"
    id: licznik_energii
    filters:
      - multiply: 0.001

i w logach urządzenia widzę to tak:

13:03:14	[D]	[pulse_counter:186]	
'Licznik energii': Retrieved counter: 88.00 pulses/min

Dokładnie 2 razy za dużo impulsów, przypuszczam że bezpośrednie podłączenie zlicza mi załączenie i wyłączenie impulsu, ale prawdziwym probleme (to chyba da się obejść zmieniając multiply na 0,0005) że w homeassistacie w ogóle pokazuje dziwne rzeczy:

Tak ze 100x mniej podliczył energii niż by wynikało ze zliczanych impulsów.

Problem 2.
Jak pobrać bieżącą moc z licznika. Wymyśliłem sobie żeby zliczyać czas pomiędzy impulsami (1Wh) i liczyć P=W/t, ale jak pobrać ten czas z ESP32, a może w ogóle nie tędy droga i trzeba to zrobić inaczej, choć taki sposób by mi pasował bo byłby najaktualniejszy - minimalny pobór na PC to 7W czyli ~500s pomiędzy impulsami, a maksymalny to może być około 4kW czyli ~0,9s.

Ile takie ESP32 zniesie wgrań konfiguracji?

Na początek naucz się jak zamieszczać kod na forum:

Z tego co można wyczytać w dokumentacji ESPHome, to lepiej jest użyć komponentu

Zamiast pulse_counter. Zobacz jaki mam kod dla zliczania impulsów na wodomierzu.

U mnie działa to dobrze z ustawieniami dla internal_filter: 100ms oraz opcjonalnym timeout: 1min (wskazówka wodomierza może zatrzymać się pod czujnikiem i impuls może trwać godzinami).

sensor:
  - platform: pulse_meter
    name: "Przepływ wody"
    pin:
      number: GPIO5
#      inverted: true 
      mode:
        input: true
        pullup: true
    internal_filter_mode: PULSE
    unit_of_measurement: "l/min."
    accuracy_decimals: 0
    icon: "mdi:gauge"
    internal_filter: 100ms
    timeout: 1min
    total:
      id: water_usage_total
      name: "Woda zużycie total"
      icon: "mdi:water"
      unit_of_measurement: "m³"
      accuracy_decimals: 3
      device_class: water
      state_class: total_increasing
      filters:
        - multiply: 0.001

Jeśli bezpośrednio, to musisz wiedzieć czy tak możesz. Na dodatek nie użyłeś dla GPIO wewnętrznego pullup: true i input: true.

A podstawa to ustalenie jak prawidłowo można się podłączyć pod te wyjście impulsowe tego licznika, co jest elementem wykonawczym na tym wyjściu.

Tym bardziej powinieneś korzystać z oficjalnej dokumentacji, a nie przypadkowych zlepkach informacji, z przypadkowych źródeł wklejając kod bez zrozumienia. Zamiast tracić bliżej nieokreślony czas na próby, mając nadzieję, że się uda, lepiej poświęcić go na zrozumienie jak to działa.

2 polubienia

Potestowałem przez tydzień, sprawdzałem pobieranie impulsów za pomocą pulse_meter, pulse_counter, binary_sensor, próbowałem to jakoś dopasować i uzyskałem taki efekt


za pomocą:

sensor:
  # Licznik impulsów na minutę
  - platform: pulse_counter
    pin:
      number: GPIO4
      mode: INPUT_PULLUP
    name: "Impulsy na minutę"
    id: impulsy_na_minute
    unit_of_measurement: "imp/min"
    update_interval: 30s
    internal_filter: 13us 
  
  - platform: template
    name: "Moc1 (W)"
    id: moc_sensor1
    unit_of_measurement: "W"
    accuracy_decimals: 1
    lambda: |-
      return id(impulsy_na_minute).state * 30;
    update_interval: 30s    

  # Obliczanie energii w kWh
  - platform: total_daily_energy
    name: "Zużycie energii (kWh)"
    power_id: moc_sensor1
    unit_of_measurement: "kWh"
    accuracy_decimals: 3
    filters:
      - multiply: 0.001  # Konwersja z Wh na kWh
    state_class: total_increasing

Jak widać po mnożniku ESP32 zlicza mi impulsy podwójnie, ale stabilnie to co liczy ESP zgadza się z tym co pokazuje licznik. Wejście beznapięciowe NO podłaczyłem rezystorem 10k z 3,3V do gpio i włączyłem mode: INPUT_PULLUP (sprawdzałem z miernikiem) i pomimo że to teoretycznie to samo to takie podwójne połączenie mi działa.

Jak najlepiej takie beznapięciowe NO podłączyć do gpio i oprogramować żeby nie liczyło podwójnie?

Czy można bezproblemowo wykorzystać inne wolne gpio na tym ESP32 do podłączenia termometrów np. na DS18B20

To nie jest teoretycznie to samo. Masz teraz zdublowany pullup - jeden którym jest fizyczny rezystor, a drugi którym jest źródło prądowe wewnątrz MCU, ich prąd jakoś-tam się sumuje. W dużym uproszczeniu to jest sytuacja taka jakbyś podłączył 2 rezystory równolegle do zasilania.
Jeśli wartość rezystora 10k jest za duża, to wystarczy dobrać odpowiednio mniejszą (kierując się datasheetem, rozsądkiem i najlepiej realnymi pomiarami, zwykle nie powinna być mniejsza niż 1k więc warto wypróbować np. 2k2, 3k3, 4k7 i 6k8 tylko idąc od końca).

1 polubienie

Dzięki, popróbuję. Mierzyłem i przy wyłączonym pullup nie było napięcia na gpio a przy włączonym się pojawiało, ale nie działo idealnie dużo impulsów było gubionych. Wyłączę pullup i dam mniejsza rezystancję.

Tak się przyglądam - po czym kol. wnioskuje, że liczy podwójnie?

Po tym że w instrukcji licznika jest podane 1000 impulsów/kWh. Również obserwacja diody na to wskazuje, tak samo jak liczydło z licznika i ilość zliczonych impulsów na wyjściu NO. Licznik to SDM72Bi. Na razie działa dobrze z tym kodem co załączyłem, wartości zliczane ESP32 w homeassistant zgadzają się z tym co podaje licznik na swoim wyświetlaczu i widać już wyraźnie że to co podaje PC w swojej apce (i w homeassistant) to jest liczone dobrze, ale tylko dla spręzarki, grzałki CWU i grzałki CO, notomiast nie zlicza zużycia energii elektroniki (7W) i podgrzewania tacy ociekowej i grzałki oleju (200W) przy ujemnych temperaturach.

1 polubienie

Zrób właściwą konwersję jednostek i poruszaj się w spójnych jednostkach .

Mierzysz co 30sek, więc w jednostkach/minutę energii powinno być 2* więcej.
W tym miejscu energię odczytaną z licznika masz w jednostkach kWh aby było spójnie (wyrażone w minutach) gdzieś powinieneś wyrównać [kWh/60] = [kWmin]
np. w tej lambdzie.

Najlepiej (skoro używasz interwału 30s) przelicz to na kWsek.
Jeśli przekształcisz to na papierze to otrzymasz właściwy współczynnik dla lambdy mocy.

`return id(impulsy_na_minute).state * współczynnik;`

Przestań kombinować z rezystorami i GPIO, bo problem leży w matematyce.
Już kiedyś to było napisane: zmierzoną energię przeliczamy na moc , żeby następnie wyliczyć energię. :exploding_head:

Na próbę zmień interwał obliczeń na 60sek i sprawdź poprawność, ponieważ 60sek a 30 to dokładnie *2

Ja napisałem takie coś w esphome, wydaje mi się, że działa bo impulsy przybywają z mrugnięciem diody, mój licznik ma 800imp/kWh - próbowałem uzyskać moc aktualną co 5s ale wartości są z kosmosu - wydaje mi się, że sposób w jaki obliczam aktualną moc nie jest prawidłowy tylko jak to wyliczyć aby było dobrze?

esphome:
  name: s2mini
  friendly_name: s2mini

esp32:
  board: esp32-s2-saola-1
  framework:
    type: esp-idf

logger:

api:
  encryption:
    key: ""

ota:
  - platform: esphome
    password: ""

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  power_save_mode: none
  ap:
    ssid: "S2Mini Fallback Hotspot"
    password: "obvVY4ouG2dr"

captive_portal:
web_server:
  port: 80

time:
  - platform: homeassistant
    id: ha_time
    on_time:
      # Reset miesięczny - 1 dzień miesiąca o 00:00
      - cron: "0 0 1 * * *"
        then:
          - lambda: |-
              id(energia_miesiac) = 0.0;
              ESP_LOGI("energy", "Reset miesięcznego zużycia");

      # Reset dzienny - codziennie o 00:00
      - cron: "0 0 * * * *"
        then:
          - lambda: |-
              id(energia_dzien) = 0.0;
              ESP_LOGI("energy", "Reset dziennego zużycia");

      # Reset roczny - 1 stycznia o 00:00
      - cron: "0 0 1 1 * *"
        then:
          - lambda: |-
              id(energia_rok) = 0.0;
              ESP_LOGI("energy", "Reset rocznego zużycia");

output:
  - platform: gpio
    pin: GPIO15
    id: led_pin

globals:
  - id: energia_dzien
    type: float
    restore_value: true
    initial_value: "0.0"
  - id: energia_miesiac
    type: float
    restore_value: true
    initial_value: "0.0"
  - id: energia_rok
    type: float
    restore_value: true
    initial_value: "0.0"
  - id: energia_lifetime
    type: float
    restore_value: true
    initial_value: "0.0"

sensor:
  - platform: wifi_signal
    name: "WiFi Signal"
    update_interval: 10s

  - platform: pulse_counter
    pin:
      number: GPIO14
      mode:
        input: true
        pullup: true
    name: "Impulsy energii"
    id: energia_pulsowa
    update_interval: 60s
    unit_of_measurement: "imp/min"
    accuracy_decimals: 0
    filters:
      - multiply: 1.0
    on_value:
      then:
        - lambda: |-
            if (x > 0) {
              float wh = x / 60.0;  // 1 impuls = 1 Wh → x impulsów w 60s
              id(energia_dzien) += wh / 1000.0;
              id(energia_miesiac) += wh / 1000.0;
              id(energia_rok) += wh / 1000.0;
              id(energia_lifetime) += wh / 1000.0;
              id(led_pin).turn_on();
              delay(50);  // Mrugnięcie LED
              id(led_pin).turn_off();
            }

  - platform: uptime
    name: "Uptime"

  - platform: template
    name: "Energia dzienna"
    id: sensor_energia_dzien
    unit_of_measurement: "kWh"
    device_class: energy
    state_class: total
    accuracy_decimals: 3
    update_interval: 10s
    lambda: |-
      return id(energia_dzien);

  - platform: template
    name: "Energia miesięczna"
    id: sensor_energia_miesiac
    unit_of_measurement: "kWh"
    device_class: energy
    state_class: total
    accuracy_decimals: 3
    update_interval: 10s
    lambda: |-
      return id(energia_miesiac);

  - platform: template
    name: "Energia roczna"
    id: sensor_energia_rok
    unit_of_measurement: "kWh"
    device_class: energy
    state_class: total
    accuracy_decimals: 3
    update_interval: 10s
    lambda: |-
      return id(energia_rok);

  - platform: template
    name: "Energia całkowita"
    id: sensor_energia_lifetime
    unit_of_measurement: "kWh"
    device_class: energy
    state_class: total_increasing
    accuracy_decimals: 3
    update_interval: 10s
    lambda: |-
      return id(energia_lifetime);

  - platform: template
    name: "Moc chwilowa"
    unit_of_measurement: "W"
    device_class: power
    state_class: measurement
    accuracy_decimals: 0
    update_interval: 60s
    lambda: |-
      return id(energia_pulsowa).state * 60.0 * 60.0 / 800.0;  // imp/min → W (800 imp/kWh)

switch:
  - platform: template
    name: "Reset liczników energii"
    icon: "mdi:restart"
    turn_on_action:
      - lambda: |-
          id(energia_dzien) = 0.0;
          id(energia_miesiac) = 0.0;
          id(energia_rok) = 0.0;
          id(energia_lifetime) = 0.0;
          ESP_LOGI("reset", "Liczniki energii wyzerowane");

Kilka luźnych spostrzeżeń

  • masz warunek >0, czyli jeśli wartość jest niezerowa to ją publikujesz, ale jeśli jest zerowa to jej nie publikujesz, czemu? przecież zero jest tak samo dobrą wartością jak nie-zero
  • skoro masz pullup, to znaczy, że zliczasz binarne zera (w sumie to bez znaczenia, bo ta platforma liczy na zboczu, domyślnie rosnącym, czyli gdy LED gaśnie)
  • masz 800 imp/kWh, ale zasadnicze obliczenia robisz chyba jak dla 1000 imp/kWh
  • jeśli chcesz uzyskać dokładność pojedynczego pomiaru mocy na poziomie 10% to w okienku pomiarowym powinno się zmieścić przynajmniej koło 10 impulsów (a aby pomiar z okienkiem 5s był w ogóle możliwy, to obciążenie minimalne musi być koło 1kW i mimo to będziesz miał dokładność rzędu 80% czyli żadną)

Warunek dałem jako >0, dlatego, że jest jako on value, a coś musiałem dać aby liczyć, nie mam innego pomysłu na tą konstrukcję aby było lepiej?

Te 1000 dotyczy konwersji W → kW, obliczenie z impulsów jest 800

return id(energia_pulsowa).state * 60.0 * 60.0 / 800.0;  // imp/min → W (800 imp/kWh)

Chce uzyskać moc, tak jak np. pokazuje ją falownik fronius na podstawie licznika S0 co ok 3s fronius zmienia moc pobieraną na podstawie odczytu z licznika po S0 - jak to zrobić? to co mam to wydaje mi się, że nie działa pokazuje jakąś moc, która jeżeli się nie zmienia i jest stałą wartością (urządzenie podłączone jako obciążenie pobiera stałą wartość mocy) jest nawet prawidłowa, ale jak następują zmiany to już nie jest. Fronius po S0 czyta nawet 20W i pokazuje to dość prawidłowo (utility meter na mocy zwracanej z falownika zlicza taki sam pobór jak licznik operatora).

Metodą zliczania ilości impulsów w stałych interwałach nie da się mierzyć małych i mocno zmiennych wartości, zamiast tego użyj

Jeszcze jedna uwaga - masz (ESP32) S2, w niektórych wykonaniach działa on wyjątkowo niestabilnie (może dlatego jest śmiesznie tani), szczególnie mam na myśli Wemos S2 mini i jego klony.
Masz sensor uptime to sprawdzaj czy MCU się nie wywraca co chwilę…

zero też jest wartością… swoją drogą to uzasadnienie się kupy nie trzyma, bo wykonujesz każde on_value, a dopiero w środku sprawdzasz czy jest >0 i od tego uzależniasz publikację wyniku (moim zdaniem to doprowadzi do systematycznego fałszowania wyników wyłącznie w górę) więc uważam, że warunek w środku lambdy jest zbędny

Po to dałem uptime aby sprawdzić - ale wychodzi na to, że moje pracuje dość stabilnie.
Przy pomocy pule meter sensor takie coś będzie prawidłowo pokazywać moc?

!!ŹLE!! - nie działa ale zostawiam dla potomnych
sensor:
  - platform: pulse_meter
    pin:
      number: GPIO14
      mode:
        input: true
        pullup: true
    name: "Impulsy na minutę"
    id: imp_na_min
    unit_of_measurement: "imp/min"
    icon: "mdi:flash"
    accuracy_decimals: 0

    total:
      name: "Energia całkowita"
      id: energia_calkowita
      unit_of_measurement: "kWh"
      device_class: energy
      state_class: total_increasing
      accuracy_decimals: 3
      filters:
        - multiply: 0.00125 
      restore: true

  - platform: template
    name: "Moc chwilowa"
    id: moc_chwilowa
    unit_of_measurement: "W"
    device_class: power
    state_class: measurement
    accuracy_decimals: 2
    lambda: |-
        return id(imp_na_min).state * 1000.0 / 800.0;
    update_interval: 3s

multiply: 0.00125 bo 1 impuls to 1.25 Wh = 0.00125 kWh dobrze myślę?
A return id(imp_na_min).state * 1000.0 / 800.0;
ponieważ
moc = imp/min * 60 * (1 / 800) * 1000
Edit:
Energia wydaje się ok, ale moc nadal źle, chyba moc trzeba by określać na przyroście energii, albo wysyłać impulsy i energię do HA i już tam z energii wyliczać moc?

Chyba udało mi się dojść do tego, ja się nie wiem dlaczego uparłem, że muszę zliaczać impulsy i dopiero potem wyliczać moc, energię itp. a to tak powinno być wg. tego linka co wyżej - dzięki za pomoc!


sensor:
  - platform: pulse_meter
    pin:
      number: GPIO14
      mode:
        input: true
        pullup: true
    name: "Moc chwilowa"
    id: moc_chwilowa
    unit_of_measurement: "W"
    device_class: power
    state_class: measurement
    accuracy_decimals: 0
    internal_filter: 20ms
    filters:
      - multiply: 75  
    total:
      name: "Energia całkowita"
      id: energia_total
      unit_of_measurement: "kWh"
      device_class: energy
      state_class: total_increasing
      accuracy_decimals: 3
      filters:
        - multiply: 0.00125

multiply: 75 bo → (60 / 800) * 1000

Na 100% źle bo liczysz ją dokładnie identycznie jak energię…

edit no i teraz edytowałeś i jest teraz inaczej, ale zaczynam wątpić że to było w dobrą stronę, w ogóle energia i moc nie są powiązane liniowo, tylko energia jest całką mocy w domenie czasu (lub licząc odwrotnie moc jest pochodną energii).

Przykład w dokumentacji opiera się na pominiętym gdzieś założeniu, że wyniki są mierzone w pulsach/minutę (co jest dość dziwną jednostką częstotliwości), mam wrażenie że ktoś tam “ulepszył” dokumentację wywalając z niej masę istotnych szczegółów.

Teraz działa - dzięki :wink:
Zostawię dla potomnych cały yaml:

esphome:
  name: s2mini # zmień na swój model płytki
  friendly_name: s2mini # zmień na swój model płytki

esp32:
  board: esp32-s2-saola-1 # zmień na swój model płytki
  framework:
    type: esp-idf

logger:

api:
  encryption:
    key: ""

ota:
  - platform: esphome
    password: ""

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  power_save_mode: none # to zapobiega oszczędzaniu energii i rozłączaniu z WiFi

  ap:
    ssid: "S2Mini Fallback Hotspot"
    password: "obvVY4ouG2dr"

captive_portal:
web_server:
  port: 80

time:
  - platform: homeassistant
    id: ha_time

output:
  - platform: gpio
    pin: GPIO15
    id: led_pin

sensor:
  - platform: pulse_meter
    pin:
      number: GPIO14
      mode:
        input: true
        pullup: true
    name: "Moc chwilowa"
    id: moc_chwilowa
    unit_of_measurement: "W"
    device_class: power
    state_class: measurement
    accuracy_decimals: 1
    internal_filter: 20ms
    filters:
      - multiply: 75  # (60 / 800) * 1000
    total:
      name: "Energia całkowita"
      id: energia_total
      unit_of_measurement: "kWh"
      device_class: energy
      state_class: total_increasing
      accuracy_decimals: 4
      filters:
        - multiply: 0.00125  # 1 impuls = 1.25 Wh = 0.00125 kWh
    on_raw_value:
      then:
        - output.turn_on: led_pin
        - delay: 50ms
        - output.turn_off: led_pin
  - platform: uptime
    name: "Uptime"
  - platform: internal_temperature
    name: "Internal Temperature"
  - platform: wifi_signal
    name: "WiFi Signal"
    update_interval: 10s

Problem jaki widzę to wartość Energia całkowita nie jest zapisywana we flash esp przez co przy restarcie płytki zostaje wyzerowana, a więc jako zmienna do zliczania energii w HA się może nie nadawać, restore_value: true albo restore: true nie jest wspierane przez pulse_meter:
[restore] is an invalid option for [total]. Please check the indentation.
[restore_value] is an invalid option for [total]. Did you mean [on_value], [on_raw_value]?
jak to ugryźć po stronie HA aby zliczało dobrze energię po wyzerowaniu po stronie esp?

Poprzez stworzenie na bazie odczytu z ESP pomocnika dla licznika total i następnie w oparciu o niego liczniki (pomocników) w cyklach jakie cię interesują.

to działa bodajże tylko dla zmiennych global oraz niektórych wybranych integracji (ze względu na zapis w RTC flash), więc by się dało musisz zakombinować z użyciem zmiennych globalnych
https://esphome.io/components/globals.html