I have a hydronic heating system that pumps hot water into gigantic slab of concrete. When someone opens the door and the cold air hits its thermostat, it pumps hot water into the slab of concrete for quite some time, and then the slab of concrete is too hot and people have to open the window to cool down the house, which just causes the problem to repeat.
I used to be a control system engineer, so I decided to write a Proportional Integral Differential algorithm and tune it for a slower response, relying on the Integral term to eventually surf the setpoint exactly if things are stable. But I don't have any direct control over the boiler, nor the temperature of the water being circulated, so I figured I'd do a duty-cycle control — control what percentage of the time hot water is being pumped in. And, since the physical thermostat is automated already, I decided it would be easiest (and safest) to just bump up its setpoint at the beginning of the duty cycle (to turn on the heating), and bump down its setpoint proportionally through the duty cycle (to turn off the heating), which also provides some safety as even if my PID algorithm is badly tuned or has some catastrophic bug hopefully the only damage is a setpoint that's off by the bump-up or bump-down amount. (Note that @bravenel 's Thermostat Controller uses the same technique to turn on and off a thermostat.)
This is probably only useful of you if you find your heating system overreacting, overshooting or undershooting. But it might be useful if you just want to smooth out how it behaves, or learn about PID control.
I have had this working as a clunky rule-machine repeat loop for at least two years. My two buddies ChatGPT and CoPilot helped me rewrite it as a Groovy App today and it seems to be working. Since Groovy is much more capable than Rule Machine, I'd like to extend it a little after I finish testing it:
- Run it more than once-per-minute, perhaps every 30 seconds, otherwise a duty-cycle of 20 minutes is about the minimum it can really work with, giving it +-5% control on the duty-cycle.
- When measuring the derivative, the rate of change of the temperature, looking back only 2 minutes into the history is often not enough to determine whether the house is heating up or cooling down. I could track more minutes into the past and have it configurable how many minutes to look back.
- I should allow a different temperature measurement device than the thermostat being controlled, another reason to use this and tune for a slow response is if one wanted to measure the temperature far away from the actual heating source.
The red bars in the little picture below show the old Rule Machine implementation of this increasing the duty-cycle between 11PM and about 2:30AM, and then decreasing the duty cycle until 5AM. This Groovy version has the same algorithm and parameters, so should work the same.
Note the Wikipedia article has advice on tuning a PID controller.
PID Thermostat App
Author: John Abraham
License: Apache License Version 2.0
Project URL: GitHub Repository
Overview
The PID Thermostat App is a Groovy-based smart home app for the Hubitat platform, that applies a duty-cycle to a thermostat using a Proportional-Integral-Derivative (PID) controller.
Some heating systems have too much lag, or you may not them to over-respond to changes in temperature. I have a hydronic in-floor heating, with hot water heating a gigantic slab of concrete. If someone opens the door, a blast of cold air hits a normal thermostat, so it decides to rigourously pump water into the slab, so much that 20 minutes after the door has closed the slab is now way too hot, and the room overshoots its temperature for an hour or more.
This app uses the well-known PID algorithm to figure out how much heating is needed as a percentage of the total capacity, and applies it for a percentage of the time, over the course of Cycle Time, which defaults to 1200s / 20 min. So, if you need 15% heat, it will heat for the first 3 minutes of every 20 minutes. If you need 50% heat, it will heat for the first 10 minutes of every 20 minutes. The portion of heating is called the duty-cycle, and it's in the variable W_control.
This app is only useful if you find your heating system overreacting, overshooting or undershooting, or just want to smooth out how it behaves.
Although there is no warranty and no liability for damages, the App only changes the setpoint of your real thermostat up and down. If your desired setpoint is 18C and your Temperature Threshold is 4 degrees, the app should only turn your real thermostat up to 22 when the duty-cycle is on, and turn your real thermostat down to 14 when the duty cycle is off, so regardless of what happens with the W_control and even if you badly tune your PID algorithm it shouldn't cause any big troubles.
For more information, you can refer to
the Wikipedia article on PID control
and Wikipedia article on Duty Cycle.
Features
- Configurable PID parameters (
Proportional
,Integral
,Derivative
) to tune your controller. - Supports a manual setpoint or one from a device such as a virtual thermostat.
- Adjustable temperature threshold and cycle time.
- Dynamic logging with levels (
trace
,debug
,info
,warn
,error
) for easy troubleshooting. - Anti-reset windup control to avoid PID saturation issues.
- State management to maintain continuity of PID control across cycles.
How It Works
The app adjusts the targeted thermostat, turning it on by setting its heating temperature to be Temperature Threshold
above the setpoint
to turn it on, or setting its heating temperature to be Temperature Threshold
below the setpoint
to turn it off. Using the PID control algorithm, the app calculates a duty cycle to determine the fraction of time the thermostat should remain active versus inactive to achieve a steady state. This duty cycle is adjusted by monitoring the difference between the target setpoint (desired temperature) and an actual measured temperature.
PID Parameters:
- Proportional Gain (P): Reacts to the magnitude of the temperature error. Higher values lead to stronger reactions.
- Integral Gain (I) (integral in degrees*seconds) : Reacts to accumulated errors over time to eliminate longer-term temperature deviations.
- Derivative Gain (D) (derivative in degrees per second): Reacts to the rate of temperature change, helping to stabilize overshooting.
The app periodically (every minute) runs a control loop for continuous adjustments.
Installation
Prerequisites
- Hubitat Elevation smart home hub.
- A compatible thermostat device connected to the Hubitat hub.
- (Optional) a hubitat device such as a virtual thermostat (supporting the heatingSetpoint attribute) or some other temperature attribute, for you to adjust the desired temperature (otherwise you can set it in the app directly)
Using the Script
- Download or copy the Groovy file pid_thermostat-singleapp.groovy from the GitHub repository.
- Log in to your Hubitat Elevation instance.
- Navigate to the Apps Code section from the Hubitat interface.
- Click on New App and paste the Groovy code.
- Save the app and click Load to initialize it.
- Go to the Apps section, add the newly installed app, and configure the settings as required.
Configuration
Preferences:
During setup in Hubitat, you will be asked to configure the following:
-
Logging Level
Choose the verbosity of logs for debugging or troubleshooting. Options:trace
debug
info
(Default)warn
error
-
Thermostat Selection
Select a thermostat device to control. -
Setpoint Source
Specify how the setpoint is determined:- Use a device which has a
heatingSetpoint
(e.g. a virtual thermostat) ortemperature
attribute for dynamic setpoint adjustments. - Manually input a setpoint value.
- Use a device which has a
-
PID Parameters
Fine-tune the behavior of the PID controller. Default values seem to work ok for thermostats in Celsius, they are probably too high for Fahrenheit.- Proportional Gain (
P
) (Default:0.25
) - Integral Gain (
I
) (Default:0.00007
) - Derivative Gain (
D
) (Default:0.1
)
- Proportional Gain (
-
Temperature Threshold
Specify the temperature delta used to adjust the thermostat’s setpoint when cycling. (Default:4
degrees) -
Cycle Time
Define the time interval for the duty cycle in seconds. For example, a1200
-second cycle will adjust the heat every 20 minutes. (Default:1200
seconds)
Logging
The app supports dynamic logging at different levels of detail. Based on your selected log level, the following kinds of logs will be generated:
- trace: Detailed execution flow, including intermediate calculations.
- debug: Key state changes like PID parameter updates and thermostat mode adjustments.
- info: Messages summarizing major events like initialization, updates, or completed cycles.
- warn: Warnings about incorrect configurations or potential errors.
- error: Critical log messages when input validation fails or app malfunctions.
Logs will appear in the Hubitat log interface for easy monitoring.
Version History
- V0.1: Initial release – core functionality for PID-based thermostat adjustments.
License
This project is licensed under the Apache License, Version 2.0. See the LICENSE file for details.
Acknowledgments
- Thanks to NelsonClark for inspiration and reference implementations of advanced virtual thermostat apps.
- Developed by John Abraham in 2025.
Contributing
Contributions are welcome! If you'd like to enhance the app, feel free to fork the repository, work on the changes, and submit a pull request.
For bug reports or feature requests, open an issue on the GitHub project page.
Support
If you encounter any issues or have questions about the app, please open an issue on GitHub or reach out via the Hubitat community forums.