Meghalt a Computherm – hogyan csináltam okostermosztátot Shelly-vel és Home Assistanttal


A lassú halál

A Computherm Q7RF termosztátunk már egy ideje nem volt teljesen megbízható.

A tünetek klassikusak voltak: az adó leadta a jelet, a vevő visszajelzett – a piros lámpa felvillant, jelezte, hogy fűtés indult. De a kazánba valójában nem érkezett meg a rövidzár. A fűtésjel elveszett valahol a levegőben :D.

A fűtésszerelő már mondogatta, hogy lassan cserés lesz. Aztán egy kocogtatás, újraszerelés, és megjavult – egy időre. Ez az állapot ment hónapok óta.

Ma este viszont végleg megadta magát.

Hallani lehetett a kattanást a vevőnél – a relé megmozdult. Aztán 0.2 másodperccel később jött a visszakattanás. Ki. Be. Ki. Mintha valami belülről nem bírta volna tartani a kontaktust. Kocogtatás, ütögetés, leszerelélem, visszaszerelélem – semmi. Szeles, hideg este volt, és nem akartam, hogy a família reggel fagyban keljen.


Rendeljek új termosztátot?

Az első gondolat az volt: rendeljek egy új Computherm-et. Van wifis változatuk is – állítólag Home Assistanttal is együttműködik, és örülnék egy magyar márkának. De az online rendelés minimum másnap, de inkább 3 nappal később érkezett volna meg - a hétvége kezdete miatt, és most este kell megoldás, nem holnap.

Akkor esett le, hogy ennél sokkal egyszerűbb a helyzet.

Korábbi szerelgetések alkalmával már jól felmértem, hogyan működik a rendszer. A kazán fűtésjele két pont rövidzárásával adható – ennyi. Ha valamivel be tudom zárni ezt a két pontot, a kazán fűt. Ezt meg tudja csinálni egy relé. Azt meg tudja vezérelni egy Shelly.

Előkerestem egy Shelly 1 Mini Gen3-at, amire nem volt éppen szükség. Fogó, csavarhúzó, WAGO-k, és nekiálltam.


A kazán megnyitása

A kazán előlapját leszedtem. Megkerestem a két pontot, amelyeket rövidre kell zárni a fűtésjelhez – ez megvolt a korábbi tapasztalatokból. A tápfeszültség bekötése mellette volt, ott 2 WAGO-val le tudtam ágazni a Shelly tápellátásához.

kazan_shelly_wago.jpeg A Shelly 1 Mini Gen3 a kazán belsejébe kerül, WAGO összekötőkkel a tápellátás leágazásához.

Áram alá helyeztem, csatlakoztattam a wifi hálózathoz – az extenderem szerencsére elég erős jelet ad ott is. Beállítottam fix IP-t, hogy ne okozzon gondot semmilyen router-újraindítás. Gyors teszt a Shelly appon keresztül: a kapcsolás működött, a kazán elindult.

Ezután összekötöttem az I/O kimeneteket a kazán fűtésjel-pontjaihoz. Áram vissza, újabb teszt – a fűtés beindul, ahogy kell.

A hardver rész 20 perc volt.


Thermosztát logika Home Assistantban

Idáig csak annyit értem el, hogy be tudom kapcsolni a kazánt. De egy valódi termosztáthoz kell az “agy” is – valami, ami tudja, mikor kell fűteni és mikor nem, és amelyik tartani tud egy heti programot.

A generic_thermostat integráció

A Home Assistant beépített generic_thermostat platformja pontosan erre való. Egy virtuális klímaentitást hoz létre két összetevőből:

Amikor a szobahőmérséklet a beállított célérték alá esik (a konfigurált tolerancián belül), a termosztát bekapcsolja a Shellyt, ami rövidre zárja a kazán fűtésjel-pontjait. Ha a hőmérséklet eléri a célértéket, kikapcsol. Ez hisztézises szabályozás – pontosan így működnek a klasszikus bimetál termosztátok is.

A konfiguráció:

climate:
  - platform: generic_thermostat
    name: "Házifűtés"
    heater: switch.futesi_relay
    target_sensor: sensor.nappali_homerseklet_homerseklet
    min_temp: 15
    max_temp: 25
    cold_tolerance: 0.3
    hot_tolerance: 0.3
    precision: 0.1
    target_temp: 20
    initial_hvac_mode: "heat"

A cold_tolerance és hot_tolerance értékek szabályozzák a hisztézist: 0.3 fokkal a célhőfok alatt bekapcsol, 0.3 fokkal felette kikapcsol. Ez elég szoros vezérlés – a kazán nem fog percenként ki-be kapcsolgatni, de a hőmérséklet sem fog sokat ingadozni.

Érdekes módon épp a közelmúltban próbáltam hisztézist beállítani egy Moes Zigbee termosztáton – és nem sikerült. A készülék firmware-je egyszerűen nem tette lehetővé. Ez a két sor konfigurációban jól összefoglalja, hogy mit jelent az, ha az ember nem egy eszköz zárt programjára van bízva, hanem maga tartja a kezében a vezérlési logikát.

A heti program

A generic_thermostat maga nem tartalmaz időzítő logikát – ez nem hiba, hanem tervezési döntés. A HA-ban a schedule külön komponens: időalapú automatizálások állítják be a célhőmérsékletet a climate entitáson.

Az egész konfiguráció egy HA package fájlba kerül (packages/heating.yaml), hogy ne keveredjen az automations.yaml tartalmával. A program:

Egy példa automatizálás:

- alias: "Fűtés: esti program (hétköznap)"

  # TRIGGER: minden nap 16:00-kor elsül
  trigger:
    - platform: time
      at: "16:00:00"

  # CONDITION: de csak akkor fut le, ha éppen hétköznap van
  condition:
    - condition: time
      weekday: [mon, tue, wed, thu, fri]

  # ACTION: lépések sorban
  action:
    # 1. Jelzőzászló BE – jelzi, hogy most egy automatizálás állítja a hőmérsékletet,
    #    nem ember. Ez megakadályozza, hogy az override-detektáló tévesen beinduljón.
    - service: input_boolean.turn_on
      target: { entity_id: input_boolean.futes_auto_beallitas }

    # 2. Ha éppen fut egy kézi override timer (pl. valaki korábban felnyomta 23°C-ra),
    #    azt töröljük – a program visszaveszi az irányítást.
    - service: timer.cancel
      target: { entity_id: timer.futes_override }

    # 3. A lényeg: beállítjuk a célhőmérsékletet 21°C-ra.
    - service: climate.set_temperature
      target: { entity_id: climate.hazifutes }
      data: { temperature: 21 }

    # 4. Várunk 3 másodpercet, hogy a climate entitás state-je biztosan frissüljön,
    #    mielőtt a zászlót visszakapcsoljuk.
    - delay: "00:00:03"

    # 5. Jelzőzászló KI – ettől fogva az override-detektálás újra aktív.
    - service: input_boolean.turn_off
      target: { entity_id: input_boolean.futes_auto_beallitas }

Az input_boolean.futes_auto_beallitas szerepét mindjárt kifejtem.

Teljes heating.yaml package (climate + timer + automatizálások)
# packages/heating.yaml

climate:
  - platform: generic_thermostat
    name: "Házifűtés"
    unique_id: hazifutes_generic
    heater: switch.futesi_relay
    target_sensor: sensor.nappali_homerseklet_homerseklet
    min_temp: 15
    max_temp: 25
    cold_tolerance: 0.3
    hot_tolerance: 0.3
    precision: 0.1
    target_temp: 20
    initial_hvac_mode: "heat"
    away_temp: 16

timer:
  futes_override:
    name: "Fűtés ideiglenes hőmérséklet"
    duration: "02:00:00"
    restore: true
    icon: mdi:timer-outline

input_boolean:
  futes_auto_beallitas:
    name: "Fűtés: automatikus beállítás folyamatban"
    icon: mdi:cog-outline

automation:

  - id: futes_program_ejjel_hetkoznap
    alias: "Fűtés: éjjeli program (hétköznap)"
    trigger:
      - platform: time
        at: "22:00:00"
    condition:
      - condition: time
        weekday: [mon, tue, wed, thu, fri]
    action:
      - service: input_boolean.turn_on
        target: { entity_id: input_boolean.futes_auto_beallitas }
      - service: timer.cancel
        target: { entity_id: timer.futes_override }
      - service: climate.set_temperature
        target: { entity_id: climate.hazifutes }
        data: { temperature: 18 }
      - delay: "00:00:03"
      - service: input_boolean.turn_off
        target: { entity_id: input_boolean.futes_auto_beallitas }

  - id: futes_program_reggel_hetkoznap
    alias: "Fűtés: reggeli program (hétköznap)"
    trigger:
      - platform: time
        at: "06:00:00"
    condition:
      - condition: time
        weekday: [mon, tue, wed, thu, fri]
    action:
      - service: input_boolean.turn_on
        target: { entity_id: input_boolean.futes_auto_beallitas }
      - service: timer.cancel
        target: { entity_id: timer.futes_override }
      - service: climate.set_temperature
        target: { entity_id: climate.hazifutes }
        data: { temperature: 21 }
      - delay: "00:00:03"
      - service: input_boolean.turn_off
        target: { entity_id: input_boolean.futes_auto_beallitas }

  - id: futes_program_nappal_hetkoznap
    alias: "Fűtés: nappali program (hétköznap)"
    trigger:
      - platform: time
        at: "08:00:00"
    condition:
      - condition: time
        weekday: [mon, tue, wed, thu, fri]
    action:
      - service: input_boolean.turn_on
        target: { entity_id: input_boolean.futes_auto_beallitas }
      - service: timer.cancel
        target: { entity_id: timer.futes_override }
      - service: climate.set_temperature
        target: { entity_id: climate.hazifutes }
        data: { temperature: 19 }
      - delay: "00:00:03"
      - service: input_boolean.turn_off
        target: { entity_id: input_boolean.futes_auto_beallitas }

  - id: futes_program_este_hetkoznap
    alias: "Fűtés: esti program (hétköznap)"
    trigger:
      - platform: time
        at: "16:00:00"
    condition:
      - condition: time
        weekday: [mon, tue, wed, thu, fri]
    action:
      - service: input_boolean.turn_on
        target: { entity_id: input_boolean.futes_auto_beallitas }
      - service: timer.cancel
        target: { entity_id: timer.futes_override }
      - service: climate.set_temperature
        target: { entity_id: climate.hazifutes }
        data: { temperature: 21 }
      - delay: "00:00:03"
      - service: input_boolean.turn_off
        target: { entity_id: input_boolean.futes_auto_beallitas }

  - id: futes_program_reggel_htvege
    alias: "Fűtés: reggeli program (hétvége)"
    trigger:
      - platform: time
        at: "08:00:00"
    condition:
      - condition: time
        weekday: [sat, sun]
    action:
      - service: input_boolean.turn_on
        target: { entity_id: input_boolean.futes_auto_beallitas }
      - service: timer.cancel
        target: { entity_id: timer.futes_override }
      - service: climate.set_temperature
        target: { entity_id: climate.hazifutes }
        data: { temperature: 21 }
      - delay: "00:00:03"
      - service: input_boolean.turn_off
        target: { entity_id: input_boolean.futes_auto_beallitas }

  - id: futes_program_ejjel_htvege
    alias: "Fűtés: éjjeli program (hétvége)"
    trigger:
      - platform: time
        at: "23:00:00"
    condition:
      - condition: time
        weekday: [sat, sun]
    action:
      - service: input_boolean.turn_on
        target: { entity_id: input_boolean.futes_auto_beallitas }
      - service: timer.cancel
        target: { entity_id: timer.futes_override }
      - service: climate.set_temperature
        target: { entity_id: climate.hazifutes }
        data: { temperature: 18 }
      - delay: "00:00:03"
      - service: input_boolean.turn_off
        target: { entity_id: input_boolean.futes_auto_beallitas }

  - id: futes_override_detektalas
    alias: "Fűtés: kézi override detektálás"
    trigger:
      - platform: state
        entity_id: climate.hazifutes
        attribute: temperature
    condition:
      - condition: state
        entity_id: input_boolean.futes_auto_beallitas
        state: "off"
      - condition: template
        value_template: >
          {{ trigger.from_state is not none
             and trigger.to_state is not none
             and trigger.from_state.state not in ['unavailable','unknown']
             and trigger.to_state.state not in ['unavailable','unknown'] }}
    action:
      - service: timer.start
        target: { entity_id: timer.futes_override }
        data: { duration: "02:00:00" }

  - id: futes_override_vege
    alias: "Fűtés: override lejárt, visszaáll programra"
    trigger:
      - platform: event
        event_type: timer.finished
        event_data:
          entity_id: timer.futes_override
    action:
      - service: input_boolean.turn_on
        target: { entity_id: input_boolean.futes_auto_beallitas }
      - service: climate.set_temperature
        target: { entity_id: climate.hazifutes }
        data:
          temperature: >
            {% set h = now().hour %}
            {% set wd = now().weekday() %}
            {% if wd < 5 %}
              {% if h >= 22 or h < 6 %}18
              {% elif h >= 6 and h < 8 %}21
              {% elif h >= 8 and h < 16 %}19
              {% else %}21{% endif %}
            {% else %}
              {% if h >= 23 or h < 8 %}18
              {% else %}21{% endif %}
            {% endif %}
      - delay: "00:00:03"
      - service: input_boolean.turn_off
        target: { entity_id: input_boolean.futes_auto_beallitas }

A kézi override megoldása

Eddig volt egy automatikusan futó programom. De a feleségem elvárása egyszerű volt: ha hazajön és fázik, tudjon benyomni egy hőmérsékletet – aztán álljon vissza magától. Ne kelljen emlékezni semmire, ne kelljen visszakapcsolni.

A Lovelace thermostat kártyán közvetlenül lehet változtatni a célhőmérsékletet. Az ötlet az volt, hogy ha valaki kézzel változtatja ezt az értéket, induljon el egy 2 órás visszaszámláló. Amikor lejár, a rendszer visszaállítja az éppen aktuális program-hőmérsékletet.

A trükkös rész: honnan tudja, hogy ember nyúlt hozzá?

Az automatizálások is változtatják a climate entitás hőmérsékletét – nem akarom, hogy az ilyenkor is elindítsa a timert. Erre egy belső jelzőzászló, egy input_boolean szolgál:

- id: futes_override_detektalas
  alias: "Fűtés: kézi override detektálás"
  trigger:
    - platform: state
      entity_id: climate.hazifutes
      attribute: temperature
  condition:
    - condition: state
      entity_id: input_boolean.futes_auto_beallitas
      state: "off"
  action:
    - service: timer.start
      target: { entity_id: timer.futes_override }
      data: { duration: "02:00:00" }

Amikor a 2 óra lejár, egy Jinja template kiszámolja, hogy melyik időszak lenne éppen aktív (reggel, nappal, este, éjjel, hétvége), és visszaállítja a megfelelő hőmérsékletet. Ha viszont a 2 óra lejárta előtt eljön egy program-időpont (pl. 22:00), az automatizálás törli a timert és felülírja a hőmérsékletet – az override tehát korábban is véget érhet.

Pontosan úgy viselkedik, mint egy hagyományos termosztát “felülbíráló” gombja – csak nem kell hozzá fizikai gomb.


Dashboard és az HA app

A konfiguráció megvolt, pusholás, HA újraindítás. Az entitás megjelent, a klíma kártya azonnal működött.

Egyetlen apróság maradt: a kártya 22°C-t mutatott, pedig a szenzor 22.3°C-t mért. A precision: 0.5 nemcsak a megjelenítés pontosságát szabályozza, hanem azt is, hogy milyen léptékben lehet beállítani a célhőmérsékletet. 0.1-re állítva pontosan azt mutatja, amit a szenzor mér.

Ezután következett a feleségem bevezetése a HA app világába. Telepítés iPhone-ra, külön felhasználó létrehozása, és egy dedikált dashboard, ahol egyelőre csak a thermosztát kártya van – semmi más, semmi zavaró.

lovelace_thermostat.png A dedikált dashboard – csak a thermosztát, minden más el van rejtve.

Ha fázik: megnyitja az appot, húzza feljebb a csúszkát. Két óra múlva visszaáll magától.


Összegzés

Ami tűzoltásnak indult, az egy tartós megoldás lett – és valószínűleg jobb, mint az, amit lecseréltünk.

A Computherm Q7RF egyirányú rádiókapcsolattal dolgozott: az adó jelet küldött, a vevő fogadta (vagy nem). Nem volt visszajelzés arról, hogy a kazán valóban fűt-e. Most van egy Home Assistant entitás, amely mutatja az aktuális és a célhőmérsékletet, a kazán relé státuszát, és az egész vezérelhető telefonról – bárhonnan.

A hőmérsékletérzékelők már ott voltak a ház különböző pontjain, extra hardverre nem volt szükség. A Shelly 1 Mini Gen3 pár ezer forint. Az egész megoldás olcsóbb volt egy új termosztátnál, és sokkal rugalmasabb.

Ha egyszer több zónát szeretnék vezérelni, vagy jelenlétalapú automatizmust akarok beépíteni – a rendszer készen áll rá.

A legjobb megoldás az, amelyik akkor is jól működik, amikor senki nem gondol rá.