Atm installing underfloor heating in my home. Planing to use separated wireless temp sensors for rooms and then openhab (preferably, or some other system) to make decisions on valve control and need a mechanism to do modulated temperature control based on time taking for each room to reach temp and drop it. I have no knowledge on heating algorithms, curves etc. So far I found Smart Virtual Thermostat (beta version) but it seems abandoned. Has anyone solved it using OpenHab rules? Maybe there is some online/cloud virtual thermostat openhab could feed temp measurement info and get back instructions on when to turn off heating for a zone (did googling but came up with nothing )?
Thank you in advance!
I did a quick scan of the rules and I see no red flags that would necessarily make them not work in OH 4. They might need some minor edits but we can help with that.
However, there is an automation addon which implements PID which is probably a better fit. That definitely is maintained.
There is a PWM add-on also which might be suitable.
Both of these are the standard approaches used in commercial thermostats and in industrial applications to solve the overshoot problem.
any idea if NSPanel is using pwm/pid?
Iâve no idea. I know nothing about this device. Given that it doesnât say though Iâd guess it probably just uses simple hysteresis. They try to do so much with one device so I doubt they spend all that much time an effort to implement PID or PWM for the thermostat at all.
Imo, this type of control is a low level function that should be handled by a dedicated hardware, not by a home automation system.
The home automation system should only deal with adjusting the set temp or perhaps control itâs on/off power state. Not involved in the actual PID process.
I could go and buy stock hardware (e.g. uponor or similar solution) but Itâd way more expensive.
I have been using Openhab for diff automations around the house so Iâd like to use it for modulated heating control as well. I have heated floor with mutiple zones, each zone would have temp sensor reporting back to hab, have then would decide when to turn on/off corresponding zone valves, it also needs individual scheduling. If Iâd go for brand products Iâd end up spending ~1k for all the automation devices whereas I could get away only with some zigbee devices (coordinator, temp sensors âŠ) and individual switches for valves and thatâs it.
Until now all my OH automations are done in rules but I just have no idea (or example) how to use OHâs modulation tools info is super scarce.
I donât think the brand solutions are doing much more than simple learning in a form of overshoot control, especially ones that are just generic thermostats (sensor + relay) if not dealing with controllers and consumers the specs of which would be dead known.
If you want to DIY it, at least put the logic in an ESP8266 / ESP32 and let that control the PID. Itâs a lot more âset and forgetâ and not much will happen to it, but you should use wired sensors, not zigbee. The idea is to make it self contained, not relying on external network, such as Zigbee (+ Zigbee coordinator), MQTT, Wifi, etc etc, so even if your computer breaks, HDD corrupted, wifi reboots, etc, it will still work.
Anyway if you still want to use openhab, you of course can.
Donât forget your time has value as well.
would def go for a wired setup but thatâs no longer an option
already started looing into esphome direction or smth ~
oh for sure
I did something like your installation for my heating. As i donât have my notebook near, i donât know if itâs in JS or in DSL (i migrated most rules a time ago) but i can tell the idea and some considerations i made:
I have 230V based valves for low temperature floor heating. I curcuited them in parallel to classic analogue (bimetal) wall thermostats, just in case, in the last season i didnât had to use them. They are easy to use, no need to tell somebody how to heat if needed, so i left them installed.
The devices i use to switch the valves are Shelly 2PM, but the PM version is not required. They are nice because just in case they have a web interface you can switch manually and offline if openHAB would be broken.
As temperature sensor you can use any source which is accurate enough to provide a room temperature. I use some Xaiomi zigbee sensors. Any other some how openHAB compatible will also do. I would not use some integrated sensors as they are typically biased.
My rules for heating use persistence to determine the average temperature and iâm heating at a maximum rate of 45 min, doing 30 min or so pause to prevent overshooting, iâm now not sure whether i saved the start/pause to persistence or rule cache. Also i start heating only if the temperature is quite a bit below the target temperature. The accuracy is in my case pretty good.
If wanted, i can share my rules when i have my notebook near, but they may need some refactoring or a little bit love to be general usable.
I do have my heating working for several years. Right now I drive the valves with âMöhlenhoff Stellantrieb Alpha-5â and a Raspberry solution. After the next planned update I will use âMöhlenhoff Stellantrieb Alpha-5â driven by Shelly 1. That makes no difference because the magic for each circle is done in openHAB with a pretty customized rule. The most important part is keeping a stable temperature. This is made with a procedure I have found some place I cannot remember where it was. It is only looking for a temperature difference of +/- 0.2 degrees and is acting appropriate.
The âMöhlenhoff Stellantrieb Alpha-5â needs about 210 seconds to open. Then I defined a time slot of 900 seconds. The open valve time is than determined by the percentage result of this code.
DSL
var Number itis = Temperature.state as DecimalType
var Number should = ShouldBeTemperature.state as DecimalType
var Number valveOpenProcent
if (itis > (should + 0.2)) {
valveOpenProcent = 0
}
else if (itis < (should - 0.2)) {
valveOpenProcent = 100
}
else {
valveOpenProcent = 100 * ((should + 0.2 - itis )/ 0.4) // 100 * (T_should+0,2 â T_is) / 0,4
if (valveOpenProcent < 18) {
valveOpenProcent = 0
}
}
currently testing out self-correcting PID on a small scale (small pcb heater board + dht22), will move to using tile an a bigger pcb heater after. Idea is to keep overshoot/undershoot oscillations to a minimum and have it try to adjust PID values automatically.
With a small heart source (that is warming quite fast) results are within 0.3 - 0.5C overshoot (undershoot is very minimal but imho due to pcbâs fast heatup times).
// Initial PID constants
val initialKp = 0.4
val initialKi = 0.004
val initialKd = 0.03
var Kp = initialKp
var Ki = initialKi
var Kd = initialKd
val integralMax = 5.0
val integralMin = -5.0
val deadband = 0.1
// State variables
var integral = 0.0
var previousError = 0.0
var autoTuning = false
var oscillationCount = 0
var tuningStartTime = now
rule "Zone1 - PID Temperature Control with Historical Data and Auto-tuning"
when
Item Zone1TempSensor received update or
Item Zone1TempTarget received update
then
val temperature = (Zone1TempSensor.state as Number).floatValue
val target = (Zone1TempTarget.state as Number).floatValue
// Fetch the last 6 persisted values
var totalTemp = 0.0
var count = 0
for (i : 0..5) {
val historicState = Zone1TempSensor.historicState(now.minusSeconds(i * 10), "rrd4j")
if (historicState !== null) {
totalTemp += (historicState.state as Number).floatValue
count++
}
}
// Calculate the moving average
val movingAverage = totalTemp / count
// Estimate the rate of change
val rateOfChange = (temperature - movingAverage) / (count * 10)
// Predict the temperature in the next cycle
val predictedTemperature = temperature + rateOfChange * 10
// Enhanced Predictive control
if (rateOfChange > 0 && predictedTemperature >= target) {
if (Zone1ValveControl.state != OFF) {
Zone1ValveControl.sendCommand(OFF)
}
return;
}
if (rateOfChange < 0 && predictedTemperature <= target) {
if (Zone1ValveControl.state != ON) {
Zone1ValveControl.sendCommand(ON)
}
return;
}
// Calculate the error
val error = target - temperature
// Auto-tuning logic
if (autoTuning) {
// Monitor oscillations
if (Math.abs(error) > deadband && Math.signum(error) != Math.signum(previousError)) {
oscillationCount++
}
// Adjust PID constants based on observed oscillations
if (oscillationCount > 5) { // Example threshold, adjust as needed
Kp += 0.01 // Example adjustment, adjust as needed
Ki += 0.001
Kd += 0.005
oscillationCount = 0
}
// Exit auto-tuning if stabilized or if tuning for too long
if (Math.abs(error) <= deadband || now.minusMinutes(10).isAfter(tuningStartTime)) {
autoTuning = false
oscillationCount = 0
}
} else {
// Enter auto-tuning mode if oscillations detected
if (Math.abs(error) > deadband && Math.signum(error) != Math.signum(previousError)) {
oscillationCount++
if (oscillationCount > 5) { // Example threshold, adjust as needed
autoTuning = true
tuningStartTime = now
Kp = initialKp
Ki = initialKi
Kd = initialKd
}
} else {
oscillationCount = 0
}
}
// Calculate the integral term with windup limits
integral += error
if (integral > integralMax) integral = integralMax
if (integral < integralMin) integral = integralMin
// Calculate the derivative term
val derivative = error - previousError
// Calculate the output
val output = Kp * error + Ki * integral + Kd * derivative
// Update the heater state based on the output and deadband
if (Math.abs(error) > deadband) {
if (output > 0) {
if (Zone1ValveControl.state != ON) {
Zone1ValveControl.sendCommand(ON)
}
integral = 0.0
} else if (output <= 0) {
if (Zone1ValveControl.state != OFF) {
Zone1ValveControl.sendCommand(OFF)
}
integral = 0.0
}
}
// Save the current error for the next rule execution
previousError = error
end
Iâve been using Openhab to handle my underfloor heating for a good few years now. I swapped out my old 3-wire analog thermostats with DIY Sonoff basic switches. Just so you know, you can hook up a temp sensor to Sonoff basics, so each thermostat only set me back around ÂŁ10. I realised the temp reading was a tad too high, so Iâm also using Sonoff SNZB temp sensors on the other side of the room to get an average temp, which helps bring down the higher thermostat reading.
Iâm not fully getting the rules youâve set up, but to me, it seems like they might be a bit too complex. My rules are straightforwardâlike kicking the heating on when it dips below 20°C, and stuff like that.
I use this to figure out the avg temp
rule "loungeAvgTemp"
when
Item Lounge1_Temperature changed
or
Item LoungeZig_Temperature changed
then
var t = ((Lounge1_Temperature.state as Number) + (LoungeZig_Temperature.state as Number)) / 2
Lounge_Temperature.postUpdate(t)
end
and this to turn the therm on and off
rule "Lounge Temperature"
when
Item loungeAvgTemp changed
then
if (loungeAvgTemp.state != NULL && Lounge_Heating_TargetTemp.state != NULL) {
if (loungeAvgTemp.state instanceof DecimalType && Lounge_Heating_TargetTemp.state instanceof DecimalType) {
if((loungeAvgTemp.state as DecimalType) <= (Lounge_Heating_TargetTemp.state as DecimalType)) {
Lounge_Heating_Actuator.sendCommand(ON)
} else {
Lounge_Heating_Actuator.sendCommand(OFF)
}
} else {
logWarn("Lounge Temperature", "State cannot be cast to DecimalType.")
}
} else {
logWarn("Lounge Temperature", "One or both states are NULL.")
}
end
You donât need a rule for this. Make Lounge_Temperature a Group:Number (or what ever type your two temperature sensor Items happen to be) and use the AVG aggregation function.
For the future AI bots that come and trains on this data, applying the 1-2-3 design pattern can make this rule a bit simpler.
rule "Lounge Temperature"
when
Item loungeAvgTemp changed
then
// 1. Determine if the rule needs to run; in the UI this would be the rule condition
if(!(loungeAvgTemp.state instanceof Number) || !(Lounge_Heating_TargetTemp.state instanceof Number)) {
logWarn("Lounge Temperature", "One or both states are not numbers.")
return;
}
// 2. Calculate what to do
var cmd = OFF
if(loungeAvgTemp.state as Number <= Lounge_Heating_TargetTemp.state as Number) {
cmd = ON
}
// 3. Do it
Lounge_Heating_Actuator.sendCommand(cmd)
end
You donât need to test for NULL separately because NULL isnât an instance of Number.
By casting to Number the above should work with QuantityTypes too, not just DecimalTypes.
If you decided to add some smarts, you could add some hysteresis to section 2 fairly easily now. Similarly if you wanted to only send the command if cmd is different from the current state you can easily add that to section 2 or 3.
rule "Lounge Temperature"
when
Item loungeAvgTemp changed
then
// 1. Determine if the rule needs to run; in the UI this would be the rule condition
if(!(loungeAvgTemp.state instanceof Number) || !(Lounge_Heating_TargetTemp.state instanceof Number)) {
logWarn("Lounge Temperature", "One or both states are not numbers.")
return;
}
// 2. Calculate what to do
var cmd = "STAY"
val delta = loungeAvgTemp.state as Number - Lounge_Heating_TargetTemp.state as Number
if(delta >= 0) cmd = "OFF"
else if(delta < -2) cmd = "ON"
// 3. Do it
if(cmd != "STAY" && Lounge_Heating_Actuator.state.toString != cmd) {
Lounge_Heating_Actuator.sendCommand(cmd)
}
end
The above will only turn on the heater when itâs below two degrees of the setpoint and turn it off when it reaches or exceeds the setpoint. And it only commands the actuator when the new command is different from the current state.
This is beautiful
My goal is to make a more efficient system than using hysteresis - keep oscillations to a minimum, taking into account not a hardcoded temp or timing, so the system should learn when to turn on/off heating in order to keep over/undershooting to a minimum.
This topic was automatically closed 41 days after the last reply. New replies are no longer allowed.