Multizone thermostat incl various control options

Hi Geert,

You are right. Each room has a satellite, with each satellite having a room temperature sensor and a hvac mode (heat and/or cool) with each mode having a switch to open/close the valve of that room.

The master thermostat does not have a room temperature sensor. It requires also at least one hvac mode (heat and/or cool) with each mode having a switch to switch the heater on/off. The operation_mode options "minimal_on", "balanced" or "continuous" defines the scheduling of the satellites:

  • "minimal_on"': schedule satellites opening and have the heater as short active as possible but thus with high heat requirement;
  • "continuous": tries to keep the heater as long as possible active resulting in low heat requirement
  • "balanced": schedule opening of satellites ‘balanced’ between heater active duration and heat requirement

I use for the satellites: 2 sonoff 4ch relays for the floor heating, 2 zigbee radiator valves. The master switch is a sonoff single relay to activate a honeywell valve (district heating).

Some config examples can be found here github.

This is my current config

# MASTER
- platform: multizone_thermostat
  name: master
  unique_id: mz_master
  initial_hvac_mode: "off"
  initial_preset_mode: "none"
  room_area: 142 # total heated area
  passive_switch_check: True
  heat:
    entity_id: switch.mainvalve
    passive_switch_duration:
      days: 14
    master_mode:
      satelites: [living, sl1, bathroom, sl2, sl3, sl4, sl5]
      operation_mode: "balanced"
      control_interval:
        minutes: 30
      pwm_duration:
        minutes: 30
      pwm_scale: 100
      pwm_resolution: 50
      PID_valve_mode:
        goal: .8
        kp: -0.5
        ki: -0.0003
        kd: 0

  restore_from_old_state: False
  restore_parameters: False
  restore_integral: False

- platform: multizone_thermostat
  name: living
  unique_id: mz_living
  sensor: sensor.temp_living
  filter_mode: 1
  sensor_out: sensor.openweather_temperature
  room_area: 62
  initial_hvac_mode: "off"
  precision: 0.1
  sensor_stale_duration:
    hours: 12
  passive_switch_check: True
  heat:
    entity_id: input_boolean.living # operating multiple switches
    passive_switch_duration:
      days: 15
    initial_target_temp: 19
    away_temp: 15
    proportional_mode:
      control_interval:
        minutes: 15
      pwm_duration:
        minutes: 30
      pwm_scale: 100
      pwm_resolution: 50
      pwm_threshold: 2
      PID_mode:
        kp: 35
        ki: 0.004
        kd: -150000
        window_open_tempdrop: -3.6
      weather_mode:
        ka: 2
        kb: -15

  restore_from_old_state: True
  restore_parameters: True
  restore_integral: True

- platform: multizone_thermostat
  name: sl1
  unique_id: mz_sl1
  sensor: sensor.temp_sl1
  filter_mode: 1
  sensor_out: sensor.openweather_temperature
  room_area: 20
  initial_hvac_mode: "off"
  precision: 0.1
  sensor_stale_duration:
    hours: 12
  passive_switch_check: True
  heat:
    entity_id: switch.actuator_8
    passive_switch_duration:
      days: 15
    initial_target_temp: 17.5
    away_temp: 15
    proportional_mode:
      control_interval:
        minutes: 15
      pwm_duration:
        minutes: 30
      pwm_scale: 100
      pwm_resolution: 50
      pwm_threshold: 2
      PID_mode:
        kp: 35
        ki: 0.004
        kd: -150000
        window_open_tempdrop: -3.6
      weather_mode:
        ka: 2
        kb: -20

  restore_from_old_state: True
  restore_parameters: True
  restore_integral: True

- platform: multizone_thermostat
  name: sl2
  unique_id: mz_sl2
  sensor: sensor.temp_sl2
  filter_mode: 1
  sensor_out: sensor.openweather_temperature
  room_area: 13
  initial_hvac_mode: "off"
  precision: 0.1
  sensor_stale_duration:
    hours: 12
  passive_switch_check: True
  heat:
    entity_id: switch.actuator_6
    passive_switch_duration:
      days: 15
    initial_target_temp: 17.5
    away_temp: 15
    proportional_mode:
      control_interval:
        minutes: 15
      pwm_duration:
        minutes: 30
      pwm_scale: 100
      pwm_resolution: 50
      pwm_threshold: 2
      PID_mode:
        kp: 35
        ki: 0.004
        kd: -150000
        window_open_tempdrop: -3.6
      weather_mode:
        ka: 2
        kb: -20

  restore_from_old_state: True
  restore_parameters: True
  restore_integral: True

- platform: multizone_thermostat
  name: sl3
  unique_id: mz_sl3
  sensor: sensor.temp_sl3
  filter_mode: 2
  sensor_out: sensor.openweather_temperature
  room_area: 7
  initial_hvac_mode: "off"
  precision: 0.1
  sensor_stale_duration:
    hours: 12
  passive_switch_check: True
  heat:
    entity_id: switch.actuator_5
    passive_switch_duration:
      days: 15
    initial_target_temp: 17.5
    away_temp: 15
    proportional_mode:
      control_interval:
        minutes: 15
      pwm_duration:
        minutes: 30
      pwm_scale: 100
      pwm_resolution: 50
      pwm_threshold: 2
      PID_mode:
        kp: 35
        ki: 0.004
        kd: -150000
        window_open_tempdrop: -3.6
      weather_mode:
        ka: 2
        kb: -20

  restore_from_old_state: True
  restore_parameters: True
  restore_integral: True

- platform: multizone_thermostat
  name: bathroom
  unique_id: mz_bathroom
  sensor: sensor.temp_bath
  filter_mode: 1
  room_area: 7
  initial_hvac_mode: "off"
  precision: 0.1
  sensor_stale_duration:
    hours: 12
  passive_switch_check: True
  heat:
    entity_id: switch.actuator_7
    passive_switch_duration:
      days: 15
    initial_target_temp: 19.0
    away_temp: 17
    proportional_mode:
      control_interval:
        minutes: 15
      pwm_duration:
        minutes: 30
      pwm_scale: 100
      pwm_resolution: 50
      pwm_threshold: 2
      PID_mode:
        kp: 35
        ki: 0.004
        kd: -150000

  restore_from_old_state: True
  restore_parameters: True
  restore_integral: True

- platform: multizone_thermostat
  name: sl4
  unique_id: mz_sl4
  sensor: sensor.temp_sl4
  filter_mode: 2
  sensor_out: sensor.openweather_temperature
  room_area: 15
  initial_hvac_mode: "off"
  precision: 0.1
  sensor_stale_duration:
    hours: 12
  passive_switch_check: True
  heat:
    # proportional valve
    entity_id: number.sl4_valve_control
    passive_switch_duration:
      days: 15
    initial_target_temp: 17.5
    away_temp: 15
    proportional_mode:
      control_interval:
        minutes: 15
      # proportional valve thus no pwm
      # pwm_duration:
      #   minutes: 30
      pwm_scale: 100
      pwm_resolution: 50
      pwm_threshold: 2
      PID_mode:
        kp: 8
        ki: 0.001
        kd: -100000
        window_open_tempdrop: -3.6
      weather_mode:
        ka: 2
        kb: -20

  restore_from_old_state: True
  restore_parameters: True
  restore_integral: True

- platform: multizone_thermostat
  name: sl5
  unique_id: mz_sl5
  sensor: sensor.temp_sl5
  filter_mode: 2
  sensor_out: sensor.openweather_temperature
  room_area: 18
  initial_hvac_mode: "off"
  precision: 0.1
  sensor_stale_duration:
    hours: 12
  passive_switch_check: True
  heat:
    # proportional valve
    entity_id: number.sl5_valve_control
    passive_switch_duration:
      days: 15
    initial_target_temp: 17.5
    away_temp: 15
    proportional_mode:
      control_interval:
        minutes: 15
      # proportional valve thus no pwm
      # pwm_duration:
      #   minutes: 30
      pwm_scale: 100
      pwm_resolution: 50
      pwm_threshold: 2
      PID_mode:
        kp: 8
        ki: 0.001
        kd: -100000
        window_open_tempdrop: -3.6
      weather_mode:
        ka: 2
        kb: -20

  restore_from_old_state: True
  restore_parameters: True
  restore_integral: True

Just did my first test, this is the actual config

- platform: multizone_thermostat
  name: beneden
  unique_id: mz_beneden
  sensor: sensor.klimaat_keuken_temperature
  filter_mode: 1
  sensor_out: sensor.molenvelden_sensors_temperature
  room_area: 62
  initial_hvac_mode: "heat"
  precision: 0.1
  sensor_stale_duration:
    hours: 12
  passive_switch_check: True
  heat:
    entity_id: switch.0x94deb8fffe3162c0
    switch_mode: 'NO'
    passive_switch_duration:
      days: 7
    initial_target_temp: 21
    away_temp: 15
    proportional_mode:
      control_interval:
        minutes: 15
      pwm_duration:
        minutes: 30
      pwm_scale: 100
      pwm_resolution: 50
      pwm_threshold: 2
      PID_mode:
        kp: 35
        ki: 0.004
        kd: -150000
      weather_mode:
        ka: 2
        kb: -15
  restore_from_old_state: True
  restore_parameters: True
  restore_integral: True

What I saw is that the thermmostat stay inactive, but I see it calculates PWM value. Also the switch never does nothing (test smart zigbee plug, working in HA)…
This is the entity data

hvac_modes:
  - heat
  - 'off'
min_temp: 17
max_temp: 24
target_temp_step: 0.1
preset_modes:
  - none
  - away
current_temperature: 21.2
temperature: 22
hvac_action: idle
preset_mode: none
Emergency_mode: []
self_controlled: self_controlled
current_outdoor_temp: 13.3
filter_mode: 1
room_area: 62
hvac_def:
  heat:
    temperature: 22
    satelite_allowed: true
    control_mode: proportional_mode
    control_interval: 900
    pwm_duration: 1800
    pwm_scale: 100
    control_output:
      offset: 0
      pwm_out: 100
    detailed_output: false
    Open_window: false
    PID_values:
      - 35
      - 0.004
      - -150000
    PID_P: null
    PID_I: 100
    PID_D: null
    PID_valve_pos: null
    ab_values:
      - 2
      - -15
    wc_valve_pos: null
friendly_name: beneden
supported_features: 17

I see you use a ‘NO’ (normally open) switch while the integral of the PID (PID_I) is 100. The PID is equal to the pwm_scale and therfore sets the valve open 100% of the time. Thus it makes sense the valve is not closing. Has it been running for a while such that the integral was able to rise to 100 as the room temperature was 21.2 and setpoint 22?

The other parts of the PID are by default not shown to save database size. What you could do to see the other PID contributions (PID_P, PID_D) is set the detailed output to True

You also use the restore option. Could you first try with False? This would also reset the integral part.

I think the implementation of the NO switch might not have been working fine. I’ve been working on some updates and merged them just a second ago. Could you give this version a try?

I’ve installed the updated version and indeed, the switch now gets used. I was just wondering, the NO/NC, I think I understood it the opposite way. I used NO as for heating the switch needs to be closed, if I understood your reaction the normal mode is when heating is needed ?
I’ll now start monitoring the behavior and extend with a master.
Thanks for your support !

Nice to hear it works!

NO and NC refer to the idle state of the valve. For normally open the valve is by default open and needs to be powered to close, NC is vice versa.

1 Like


Temperature set to 21.3, but quite some overshoot… What can be done ?

hi Geert,

Are you running the multizone now with a master thermostat?

In order to investigate further you need to get more data, for instance: room and outdoor temperature, valve opening (master and satellite) and P-I-D contributions and the behaviour relative to each other.

Furthermore in thermostat-with-pid-controller (this multizone uses the same control method for a satellite), gives some detailled explanation/discussions about tuning. For instance this post Thermostat with PID controller - #260 by adrien.b reference to the discussion manual tuning. Note that the graph is based on an, I think, on-off thermostat.

You can start control the detailed logging per thermostat via ‘developer tools’ → ‘services’ → ‘multizone_thermostat: detailed_output’. After selection you can use the bottom ‘fill example data’. Noite that a long time use of detailed output will increase the database size thus make sure to disable it when all is working

An example to activate detailed logging:

service: multizone_thermostat.detailed_output
data:
  entity_id: climate.study
  hvac_mode: heat
  new_mode: True

Hereafter via ‘developer tools’ → ‘statuses’ the detailed output is visible. In your previous post these were then reported by ‘null’

To stop detailed logging:

....
  new_mode: False

To get these as sensors to create graphs, you could use templates to create sensors. Something like

- sensor:
    - name: "pwm_master"
      # friendly_name: "pwm master"
      state: "{{ state_attr('climate.master', 'hvac_def')['heat']['control_output']['pwm_out'] | float(0)}}"
      unit_of_measurement: "%"
    - name: "sl5_valve_pos"
      # friendly_name: "valve pos sl5"
      state: "{{ state_attr('climate.sl5', 'hvac_def')['heat']['control_output']['pwm_out'] | float(0)}}"
      unit_of_measurement: "%"
    - name: "sl5_PIDvalve_pos"
      # friendly_name: "PIDvalve pos sl5"
      state: "{{ state_attr('climate.sl5', 'hvac_def')['heat']['PID_valve_pos'] | float(0)}}"
      unit_of_measurement: "%"
    - name: "sl5_WCvalve_pos"
      # friendly_name: "WCvalve pos sl5"
      state: "{{ state_attr('climate.sl5', 'hvac_def')['heat']['wc_valve_pos'] | float(0)}}"
      unit_of_measurement: "%"
    - name: "sl5_PID_P"
      # friendly_name: "PID-P sl5"
      state: "{{ state_attr('climate.sl5', 'hvac_def')['heat']['PID_P'] | float(0)}}"
      unit_of_measurement: "%"
    - name: "sl5_PID_I"
      # friendly_name: "PID-I sl5"
      state: "{{ state_attr('climate.sl5', 'hvac_def')['heat']['PID_I'] | float(0)}}"
      unit_of_measurement: "%"
    - name: "sl5_PID_D"
      # friendly_name: "PID-D sl5"
      state: "{{ state_attr('climate.sl5', 'hvac_def')['heat']['PID_D'] | float(0)}}"
      unit_of_measurement: "%"

edit: added link to some discussion about tuning

I tried to do some tweaking, but seems I do something wrong…
I’m not running a master thermostat yet.
This is the output of the main zone, setpoint temperature was 21.1 degrees:


And this is my config

  heat:
    entity_id: switch.klep_open_gv_achter
    switch_mode: 'NC'
    passive_switch_duration:
      days: 7
    initial_target_temp: 21
    away_temp: 15
    proportional_mode:
      control_interval:
        minutes: 15
      pwm_duration:
        minutes: 30
      pwm_scale: 100
      pwm_resolution: 50
      pwm_threshold: 5
      PID_mode:
        kp: 15
        ki: 0.001
        kd: 6000
      weather_mode:
        ka: 2
        kb: -10

I read an article on the PID settings, but it isn’t reallt clear.

pwm_out is positive the whole time so it should toggle the switch (from off to on and back off) at least one time per 30 minuten. Do you register and hear activation of ‘switch.klep_open_gv_achter’ ?

The heating is working, that’s no issue, last on was about 10 minutes which is indeed 1/3 of 30 minutes. It just seems the ON time is way to short to reach the setpoint of 21.1 degrees. Current outdoor temp is 0 degrees, so not sure why weather compensation is 8% already.

Ok, good that it is operating. Evaluating your graph shows:

  • pwm_out: is positive and slowly rising
  • wc_valve_pos: is positive all the time (approx 30). thus want to heat
  • pid_valve_pos: starts negative and slowly rises.
  • pid_i: is negative and very slowly rising
  • `pid_d’: do not show to respond to dropping temperature

pwm_out is the sum of wc_valve_pos + pid_valve_pos. In the situation when the room temperature is above setpoint and the ‘wc control’ part specifies heat it will result in the pid_i part to compensate that (shown in the graph by the negative pid_i at the start). the pid_i only start to rise when the room temp is below the setpoint. In your case the pid_i might not change quick enough.

Some comments to your config:

  • kd is positive, I would expect a negative value.
  • do you use filter_mode ?

I think in you could try the following:

  • increase kp and ‘ki’ to react more and make more responsive to temp error
  • the wc might be requesting heat for too high outdoor temp . Changing to
pid:
    kp: 35
    ki: 0.05
    kd: -50000
wc:
    ka: 1.75
    kb: -18

You can set these via services which keep the current thermostat running. Note you need to update the config at the end when it is running fine.

  • pid via (i think something like) multizone..set_pid and
  • reset the integral multizone..set_integral (note that this not give a direct result but you have to wait for the next pwm loop)

At the bottom of the example you can pre-fill the service with an example to ease filling in.

I made the modifications.
filter_mode: 2 is used
This is the result (still 21.1 as setpoint)


So it heats up faster now but overshooting…

Now the pid_i is the cause of the overshoot. You can seethe pid_i rising fast and dominating component to the pwm_out. the pid_p proportional part (part directly responding to the setpoint and actual room temp) is only responding with approx 20 at max. pid_d is still not contributing.

First step is too reduce pid_i to 0.005. This will result in a slower build-up of the integral. Hereafter, depending its response, the pid_d needs probably adjustment.

We’re going in the right direction I guess…


Still some overshoot.
Now should I change pid_d which stays behind, it seems not doing much…
Changed pid_d to -25000.

It has improved indeed. Floor heating reacts slow so you need almost 12 hours to see the effect of changes.

pid_d is responding to the rate of temperature change (velocity of temperature change). At approx 1hr the temperature is not rising or dropping (velocity=0) however the pid_d is zero at 5hr, thus quite some lag is present. I would recommend to change the filter_mode to 1, this should reduce the lag.

Probably the pid_i is slightly too high as well and changes too fast.

Could you try the following

filter_mode: 1
pid_i: 0.001

These are the last 12 hours… It seems it should get quicker to the desired temperatue

    PID_values:
      - 35
      - 0.001
      - -25000
...
    ab_values:
      - 1.75
      - -18

You could try to increase the pid_p with 10 . Probably you will need to increase the pid_d as well, but I would recommend to change one at the time.

The graphs also show that the heat loss is low (takes a long time to cool down) and the floor heating reacts slowly (one of your previous graphs show a pwm peak and the room temperature reaching its peak 6hrs later). Thus this will be a limit how reactive you could make the thermostat. So you probably will not be able to to heat up the room within an hour (or even 2-3hr) or use a significant lower setpoint at night.

@axax many thanks for all this support, you’re a PID wizard :slight_smile:
Temperature indeed isn’t changed over night, this isn’t possible with inertion in our system, so it’s always at 21 degrees.
When you say increase pid_d, as it’s negative, you mean -25000 → -20000 for example ?

The pid_d might need to lower, for instance -30000. When you first start with the increase of the pid_p it could result in a oscillation of the room temperature. In such case more extreme pid_d (lower) could help to stabilise it again.

Note that success is not guaranteed :wink:

1 Like

A new version v0.7 has been released including numerous improvements, bug-fixes, new features and some breaking changes. Please see release notes for full details.

1 Like