Thermostat with PID controller

Anyway, there are some issues in the smart thermostat code. I had a look to the pidcontroler.PIDAutotune class, for example and it requires the following parameters at init :

def __init__(self, setpoint, out_step=10, sampletime=5, lookback=60,
                 out_min=float('-inf'), out_max=float('inf'), noiseband=0.5, time=time):

with the description of the parameters :

    Args:
        setpoint (float): The target value.
        out_step (float): The value by which the output will be
            increased/decreased when stepping up/down.
        sampletime (float): The interval between run() calls.
        loockback (float): The reference period for local minima/maxima.
        out_min (float): Lower output limit.
        out_max (float): Upper output limit.
        noiseband (float): Determines by how much the input value must
            overshoot/undershoot the setpoint before the state changes.
        time (function): A function which returns the current time in seconds.

And when looking at how it is called by the SmartThermostat class, I can see this :

self.pidAutotune = pid_controller.PIDAutotune(self._target_temp, self.difference,
            self._keep_alive.seconds, self._keep_alive.seconds, self.minOut, self.maxOut,
            noiseband, time.time)

You can see that both sampletime and lookback are set to the same value self._keep_alive.seconds ! setting the same value for both will lead to a buffer of only 1 value, while the autotuner needs a lot of values to be able to see at least 1.5 cycles with the min and max values inside.
Will try to analyze the full code and see how I can improve it to my needs, but I think I’ll have to change some input parameters (the keep_alive becoming a sampletime and define a separated parameter for PID lookback).

Another issue found.
Line 504 in climate.py there are typo issues that would lead to an error if the control_output (output of PID) is negative

await self.pwm_switch(self.pwm * self.control_output / self.minOut, self.pwm * self.minOut / self.control_outpu, time.time() - self.time_changedt)

I’m building my own repo with the code I fixed, may share it when working fine.
By setting the debug level in the logger I can get much better understanding of the issues. With +2°C change in setpoint, the control_output was only 14% with Kp = 10, that’s why the system was much too slow to react. by using Kp = 100 now the heater immediately switches ON.
Now let’s start the tuning.

Yes, you need to find a way to record all the necessary data points to use with the tool.

The “sampletime” must be fast enough to capture the evolution of temperature as stated in the FAQ, but given that temperature dynamics are slow by nature, default values should be OK.

Good to see you are double checking your PID implementation, I cannot stress enough that a standard PID implementation is necessary (with basic tweaks, like anti-wind up). But as I said, the Arduino implementation is battle tested and highly recommended. Take a look at it, the blog post explain the why and how of every part of the PID code:

Very nice blog, thanks for the link. It confirms the PID code was good, but the use in the climate.py was incorrect.
However I had to change the way the PID is working, to replace the fixed sampling period with variable dt, as the temperature sensor is battery powered. But it makes difficult the PWM management.

What you might try is to change the keep_alive to the minimum refresh interval of your sensor. If I remember it correctly you mentioned it was 10 minutes?

One of the things I like in a Thermostat is to be able to tell it at what time I want a given temperature, and then it will start heating when needed. So a while earlier in deep winter than at the start of the heating season.

I think it is predictable with a 1st order approach/linear approach. While the system is not heating, we can observe how fast the temperature drops. And when the system is heating, we observe how fast it heats, and we can suppose that this is the speed for the current cold.

I think the “fall time” is sufficiently linear with -LossWatts and the “rise time” is sufficiently linear with HeatWatts-LossWatts=NetHeatWatts. Now when we know “NetHeatWatts” from the previous heat cycle, we can already get an idea of the time needed to heat from say 18°C to 20°C and therefore determine when to start heating to get 20°C at 6:30 for instance. It does not have to be precise to the second, a warm radiator is always nicer than a cold one, at the same temperature.

It’s not dead easy to do, but I think it’s doable.

1 Like

It seems I found the right settings…

  kp : 50
  ki : 0.001
  kd : 150000
  pwm: 900
  keep_alive: 00:15:00

There is an issue, still. During my daughter’s bath, the room temperature increased over the set point, and when it decreased the PID output was too low to slow down the ramp down and stabilize around the set point, it undershoot of 1°C before recovering.

I’d like to plot the P, I and D values computed by the controller, but I don’t know how to expose them as attributes in the climate entity or as dedicated sensor’s in Home Assistant. I can’t find docs on developing for HACS.

Hi Adrien. Nice to see the progress. Moments when a large external distortion , such as heat of a bath or open window are tricky.

How long does it take before the temperature stabilises tot the setpoint back again?

I think what happens is: The room temp above setpoint will result in lowering of the integral part which defines the average opening time. As soon as the temp starts to drop the integral is already too low and needs to build up again.

For thermostats there is sometimes a routine to check for an open window to avoid that the thermostat runs off and tries to compensate the rapid heat loss.

In your case I imagine it could be two options:

  • you need precisely the opposite of a window routine. Keep the thermostat as is while bathing, especially the integral.
  • different PID settings, but not sure which settings work best
    – a slower reacting PID. Lower ki but temp will rise even more during bathing but with less drop.
    – or more aggressive, higher KP and kd?

See here an example to expose variables from the controller in HA. Use the functions name and a dict as return .
https://github.com/vindaalex/multizone-thermostat/blob/066f721642e719817bb3edfe427d9f0d27f42859/custom_components/multizone_thermostat/climate.py#L789

Thanks for the example. Works really nice.

1 Like

First release of my first custom component. Thanks @fabian.n for the base integration and @axax and @pidtuner for the support.

Hi adrien.b,

thanks for taking this project over.
I switched to your repository.

Thanks a lot,
pacemaker

Hello !

I installed and I test on a room. But if you leave the option autotune the thermostat remains on idle…

Hello @Canaletto, did you make a step on the target temperature to start the autotuner ?

When using the autotune, the PID is disabled, and the ambient temperature need to be outside of the noise band around the set point. If your noise band is 0.5°C, the set point needs to be set to at least +0.3°C over the ambient temperature measured by the sensor to trigger the autotuner.
Then the autotuner should start enabling and disabling the heater to measure the oscillation of the temperature around the new set point, both in time constant and amplitude, and then compute and report the new PID gains in log.

1 Like

Not easy to set up…

An idea would be to give some examples of configuration according to rooms and convectors type ? (or am I saying something wrong?)

I took the basic settings (example) and I put a standard thermostat (with a fake switch) in parallel to observe…
image

@Canaletto who’s who ? There is no legend on which one is the PID or the generic thermostat.
The best is to look at the history chart of the Smart Thermostat entity in the entity card.

In the history tab you’ll find the chart with the set point, the temperature measurement and the state of the heater (orange bars below the temperature).

You may also create template sensors to monitor the PID attributes below :

{{ state_attr('climate.salle_de_bain', 'control_output') | float }}
{{ state_attr('climate.salle_de_bain', 'pid_p') | float }}
{{ state_attr('climate.salle_de_bain', 'pid_i') | float }}
{{ state_attr('climate.salle_de_bain', 'pid_d') | float }}

With this you can see what are the outputs of the PID Vs time, it greatly helped me to adjust the gains.

2 Likes

it is obvious that the PID is the one that says on/off. This is also visible on the history of the thermostat. But here I did it on the sx of both thermostats, the PID and the normal one with the same settings.

PS : as tu créé un sujet sur un fil en Français ? (HACF ou LU)

OK, but it looks like for both thermostats, the temperature never reach the set point.
On the PID the PWM is increasing slowly, surely due to integral part that is cumulative.
You need to look at the control_output and pid_* attributes to see the values, if error is big, the output should be 100% with large pid_p value, and when the temperature curve is steep the pid_d should be largely negative to override the pid_p. The integral should only compensate residual error.

Taking into account the outside temperature, even the wind or the temperature felt in case of wind would be interesting? (I think of poorly insulated houses).