Mixing valves / central heating / 3-way mixers / floor heating

intro:
i had my water-based wall heating’s temperatures regulated by node red over mqtt/tasmota the last couple of years, but as this setup started acting up and HA got more established in my home lately i figured i should reconsider doing this in esphome/HA directly.

the chicken coop incident

in europe most motorized 3-way mixing valves like this or this seem to work similar. they have 2 AC lines in - one driving the motor to one direction, the other to the opposite, an internal endstop seems to turn off the motor and it doesn’t explode or anything if you happen to have both lines live at a time (at least for a short period of time). most common seem to be a running time of 120s for the full 90° angle.

there are several threads in here asking about how to use these 3 way mixing valves with esphome, mostly concentrating on the aspect of using it with the thermostat/climate component - which is meant to be a simple on or off of a heating or cooling device to achieve a certain temperature. but actually the challenge most (like myself) face is how to best implement these timings and switches and ideally get a position … then i remembered my chicken coop door …

and so it struck me: the valve is a cover!

specifically a time based cover as we know the time it takes for fully opening and closing and have no endstops. it might also be possible with a template cover, but so far i found no need for it.

so i went on flashing my sonoff 4ch with esphome.
in my installation the first relay switches the circulating pump, the second turns the mixer left (‘closing’ / letting less hot water in), the third one to the right (/‘opening’), the fourth unused till now.

switch:
- platform: gpio
  name: "heating 4chR1"
  pin: GPIO12
  id: relay_1
- platform: gpio
  name: "heating 4chR2"
  pin: GPIO5
  id: relay_2
- platform: gpio
  name: "heating 4chR3"
  pin: GPIO4
  id: relay_3
- platform: gpio
  name: "heating 4chR4"
  pin: GPIO15
  id: relay_4

so the magic for getting this controlled follows:

cover:
  - platform: time_based
    name: "Mixer"

    open_action:
      - switch.turn_on: relay_3
    open_duration: 120sec

    close_action:
      - switch.turn_on: relay_2
    close_duration: 120sec

    stop_action:
      - switch.turn_off: relay_3
      - switch.turn_off: relay_2

and that’s actually it for esphome

(unless you want to also make the physical buttons on the 4ch useable)
binary_sensor:
- platform: gpio
  pin:
    number: GPIO0
    mode:
      input: true
      pullup: true
    inverted: true
  name: "heating 4chB1"
  on_press:
    - switch.toggle: relay_1
- platform: gpio
  pin:
    number: GPIO9
    mode:
      input: true
      pullup: true
    inverted: true
  name: "heating 4chB2"
  on_press:
    - switch.toggle: relay_2
- platform: gpio
  pin:
    number: GPIO10
    mode:
      input: true
      pullup: true
    inverted: true
  name: "heating 4chB3"
  on_press:
    - switch.toggle: relay_3
- platform: gpio
  pin:
    number: GPIO14
    mode:
      input: true
      pullup: true
    inverted: true
  name: "heating 4chB4"
  on_press:
    - switch.toggle: relay_4
- platform: status
  name: "heating 4CH Status"

on to home assistant, to get the basics going for testing i set up a dashboard with an entity card of the four relays and one for the mixer:

image

whereas the position isn’t usually part of a mixer’s entity card. for that i installed the great lovelace-slider-entity-row which gives a controllable visual feedback of the cover’s state. very cool.

with that, the card’s code looks like that:

type: entities
entities:
  - entity: cover.heating_sonoff4ch_mixer
    name: Mixer
    icon: mdi:valve
  - type: custom:slider-entity-row
    entity: cover.heating_sonoff4ch_mixer
    name: Position
    icon: none

… and it just works.

now on we go, trying to get the cover’s actions implemented in a climate component - not sure yet if in HA or esphome directly, as the sensor data comes from another device…

hope this helps someone at some point.

That’s a really nice idea and solution!
For robustness you could implement a full close or open cycle after restart (or power failure), since the device might loose sync.
Maybe even every x hours.

Hi, great approach. I’m still struggling with how to set the desired temperature effectively. How do you manage it, for example, if you want the automation to aim for 30°C? My solution isn’t working well—it opens the valve, waits until the temperature reaches 31°C, then slowly closes the valve, but by then the temperature rises to 33°C. After a delay, it drops back to 30°C, and the cycle repeats. Hoping I can adopt your approach. Thanks for sharing your work and thank you for your help.

1 Like

this is how things work for this kind of ‘delayed’ systems - on average it should result in the temperature you set. one could implement a PID controller, but that’s usually overkill (assuming we’re talking about central heating)

Alright, thanks. Overkill sounds good. Maybe I’ll make a PID-thing my next project.

Hi
I’m pretty new into this stuff. I want to implement this into my central heating using same device. Could you provide more information, perhaps for temp sensor, I saw can be added also to sonoff 4ch. How you actually set and controll your target temperature? I am now waiting all necessary hardware to arrive in order to flash esphome and start playing :slight_smile:

script:
#RADIKA3T
  - id: radikakolmteekraanlahti
    then: 
    - repeat:
        count: 200
        then:
    
        - switch.turn_on: radika3tlahti
        - delay: 800ms
        - switch.turn_off: radika3tlahti
        - delay: 12s

  - id: radikakolmteekraankinni
    then:
    - repeat:
        count: 200
        then:
        - switch.turn_on: radika3tkinni
        - delay: 800ms
        - switch.turn_off: radika3tkinni
        - delay: 12s

and climat :

climate:
  #RADIKAKLIIMA 
  - platform: thermostat
    name: Radikakontuur
    id: radikakontuur
    icon: mdi:heater
    visual:
      min_temperature: 20 °C
      max_temperature: 75 °C
      temperature_step: 0.1 °C
    min_idle_time: 2s
    min_heating_run_time: 2s
    min_cooling_run_time: 2s
    min_heating_off_time: 2s
    min_cooling_off_time: 2s
    heat_overrun: 0.0
    heat_deadband: 0.0
    cool_overrun: 0.0
    cool_deadband: 0.0
    sensor: pealevool
    
    heat_action:
      - script.execute: radikakolmteekraanlahti
      - switch.turn_off: radika3tkinni
    cool_action:
      - script.execute: radikakolmteekraankinni
      - switch.turn_off: radika3tlahti
    idle_action:
      - script.stop: radikakolmteekraanlahti
      - script.stop: radikakolmteekraankinni
      - switch.turn_off: radika3tkinni
      - switch.turn_off: radika3tlahti

position is not visible,but works perfectly.

Hi Dradiwabel (respectively Stefan Weber ;-))

I’m having exactly the same challenge, but additionally my heating system is just controlled by the outside temerature. So I desperately need a heating curve which calculates the according flow temperature, which is needed fot the target temperature in each climate entity. Any idea how to get this curve?

Thanks

Hi
I have made a devise just like this. First create variables for heating curve. first one defines how steep is the curve. second one shifts the hole curve up and down. Third one makes the curve bend a little.

number:
  - platform: template
    name: "Kayran Jyrkkyys"
    id: angle
    optimistic: True
    max_value: 1
    min_value: 0
    step: 0.01
    mode: BOX
    restore_value: True
    initial_value: 0.3

  - platform: template
    name: "Suuntasiirto"
    id: shift
    optimistic: True
    max_value: 5
    min_value: -5
    step: 0.1
    mode: BOX
    restore_value: True
    initial_value: 0

  - platform: template
    name: "Exponentti"
    id: exponent
    optimistic: True
    max_value: 0.005
    min_value: 0
    step: 0.0005
    mode: BOX
    restore_value: True
    initial_value: 0.0001

Then I have template sensor to calculate the desired flow temperature based on the outside temperature and the variables. This calculates the curve so that the flow temperature is 22 degrees at 16 degrees outside temperature, if the shift is 0.

  - platform: template
    name: "Käyrä"
    id: curve
    accuracy_decimals: 1
    update_interval: 5min
    lambda: |-
      float angle = id(angle).state;
      float intersection = id(exponent).state * 16 * 16 + angle * 16 + 22;
      float outside_temp = id(outside_temp).state;
      float curve = -id(exponent).state * outside_temp * outside_temp - angle * outside_temp + intersection + id(shift).state;
      return curve;
    on_value: 
      then:
        - climate.control:
            id: sekoittaja
            target_temperature: !lambda 'return x;'

Then I have the actual pid controller, that controls the mixer.

climate:
  - platform: pid
    name: "Sekoittaja"
    id: sekoittaja
    sensor: meno
    default_target_temperature: 25°C
    heat_output: sd_open
    cool_output: sd_close
    control_parameters:
      kp: 0.1
      ki: 0.000005
      kd: 2.12322
      output_averaging_samples: 1   # smooth the output over 1 samples
      derivative_averaging_samples: 5  # smooth the derivative value over 5 samples
    deadband_parameters:
      threshold_high: 1°C       # deadband within +/- 1°C of target_temperature
      threshold_low: -1°C
      kp_multiplier: 0.15
      ki_multiplier: 1
      kd_multiplier: 0.5
      deadband_output_averaging_samples: 5
    visual:
      min_temperature: 15
      max_temperature: 40

output:
  - platform: sigma_delta_output
    update_interval: 1s
    id: sd_open
    pin: 
      number: 13
      inverted: true

  - platform: sigma_delta_output
    update_interval: 1s
    id: sd_close
    pin: 
      number: 27
      inverted: true

I hope this helped even a little.

oh, wow, quite some variables to fiddle around with …

so you only set the one pair of temperatures (16 / 22) and do everything else with angle/shift/expo? didn’t that take quite a while to get it right?

you probably know, but usually this kind of controllers offer 3 pairs of temperatures to set. like the minimum outside expected, the general average and just below the temperature when there’s no more heating needed… setting the corresponding temperatures for the heating medium needed to achieve the desired inside temperature is what gives a rough curve and the 3 ‘handles’ to directly adjust.

but guess i’ll have to give it a try with your approach either :hugs:

@konddda
Thanks, I’ll give it a try, but will take some time to understand it and check if it matches my “heating system” … I’m not a coder, just a beginner with some basic knowledge of if, then, else statements …
But anyhow thanks for your contribution!!

Yes I only set one pair of temperatures. It didn’t take that long to get it right. I already knew my flow temperature would be around 35 C at -25 C. So calculated the angle based on that. And then I added a little bend(expo) just because the heat curves I found online seemed to bend a little. After that I have done little fine tuning based on the house temperatures.
Of course you can also calculate the curve funktion from three pairs also, this just seemed more simple to me.
If you want to visualice the curve you can write the equation to a graphic calculator like geogebra and try diffirent variables

This the equation I wrote.
x = outside temperature
flow temperature = -expo * x^2 - angle * x + shift + expo * 16^2 + angle * 16 + 22