Hello everyone,
I hope you're all well. I'm writing to you because I'm having a problem with a script related to a PI controller for a radiant floor heating system. The system consists of a 0/10 valve and a flow sensor. To prevent the controller from becoming too sensitive to temperature variations, a moving average filter was added. However, despite this precaution, the PID remains very lazy and unstable.
I've attached the script and an image of the configured parameters. The setpoints, proportional, and integral values are adjustable via the supervisory system. I feel like I'm a bit lost in the dark, so I'm counting on your expert help to resolve this situation. I forgot about the Pt1000 sensors read by the KNX module with a 2-minute cyclic variation and a value change of 0.2K, but the valves are 0/10 controlled by the KNX module.
Thank you so much for your support and availability.
Michael
-- ============================================================
-- REGOLATORE PI – MISCELATRICE PAVIMENTO RADIANTE
-- SOLO SONDA MANDATA
-- ============================================================
---------------------------------------------------------------
-- PARAMETRI BASE (persistenti)
---------------------------------------------------------------
local Kp = storage.get('Kp_PINT') or 3.0
local Ki = storage.get('Ki_PINT') or 0.02
local integral = storage.get('integral_PINT') or 0
local last_mode = storage.get('last_mode')
---------------------------------------------------------------
-- CONFIGURAZIONE KNX
---------------------------------------------------------------
local group_mode = '5/0/0' -- 1=inverno / 0=estate
local group_in = '5/2/1' -- temperatura mandata
local group_out = '5/0/1' -- 0–10 V miscelatrice
local group_set_winter = '5/2/4'
local group_set_summer = '5/2/5'----
local group_kp = '5/2/6'
local group_ki = '5/2/7'
---------------------------------------------------------------
-- COSTANTI DI CONTROLLO
---------------------------------------------------------------
local cycle_time = 120 -- secondi
local filter_samples = 3 -- filtro ridotto
local out_min, out_max = 0, 100 -- %
local max_delta = 2 -- base rampa
local deadband = 0.3 -- °C
---------------------------------------------------------------
-- VARIABILI
---------------------------------------------------------------
local readings = {}
local last_output = storage.get('last_output') or 0
---------------------------------------------------------------
-- MEDIA MOBILE TEMPERATURA MANDATA
---------------------------------------------------------------
local function moving_average(v)
if type(v) ~= 'number' then return nil end
table.insert(readings, v)
if #readings > filter_samples then
table.remove(readings, 1)
end
local s = 0
for _, x in ipairs(readings) do
s = s + x
end
return s / #readings
end
---------------------------------------------------------------
-- CICLO PRINCIPALE
---------------------------------------------------------------
while true do
-------------------------------------------------------------
-- Aggiornamento Kp / Ki da KNX
-------------------------------------------------------------
local v = grp.getvalue(group_kp)
if type(v) == 'number' then
Kp = v
storage.set('Kp_PINT', Kp)
end
v = grp.getvalue(group_ki)
if type(v) == 'number' then
Ki = v
storage.set('Ki_PINT', Ki)
end
-------------------------------------------------------------
-- Modalità estate / inverno
-------------------------------------------------------------
local is_winter = grp.getvalue(group_mode) and true or false
if last_mode ~= is_winter then
integral = 0
last_mode = is_winter
storage.set('last_mode', is_winter)
end
-------------------------------------------------------------
-- Setpoint
-------------------------------------------------------------
local set = is_winter
and grp.getvalue(group_set_winter)
or grp.getvalue(group_set_summer)
if type(set) ~= 'number' then
set = is_winter and 35 or 25
end
-------------------------------------------------------------
-- Temperatura mandata filtrata
-------------------------------------------------------------
local T = grp.getvalue(group_in)
local Tf = moving_average(T)
if Tf then
-----------------------------------------------------------
-- LIMITI TEMPERATURA MANDATA
-----------------------------------------------------------
local Tmax = set + 2.0
local Tstop = set + 3.0
-----------------------------------------------------------
-- ERRORE
-------------------------------------------------------
I hope you're all well. I'm writing to you because I'm having a problem with a script related to a PI controller for a radiant floor heating system. The system consists of a 0/10 valve and a flow sensor. To prevent the controller from becoming too sensitive to temperature variations, a moving average filter was added. However, despite this precaution, the PID remains very lazy and unstable.
I've attached the script and an image of the configured parameters. The setpoints, proportional, and integral values are adjustable via the supervisory system. I feel like I'm a bit lost in the dark, so I'm counting on your expert help to resolve this situation. I forgot about the Pt1000 sensors read by the KNX module with a 2-minute cyclic variation and a value change of 0.2K, but the valves are 0/10 controlled by the KNX module.
Thank you so much for your support and availability.
Michael
-- ============================================================
-- REGOLATORE PI – MISCELATRICE PAVIMENTO RADIANTE
-- SOLO SONDA MANDATA
-- ============================================================
---------------------------------------------------------------
-- PARAMETRI BASE (persistenti)
---------------------------------------------------------------
local Kp = storage.get('Kp_PINT') or 3.0
local Ki = storage.get('Ki_PINT') or 0.02
local integral = storage.get('integral_PINT') or 0
local last_mode = storage.get('last_mode')
---------------------------------------------------------------
-- CONFIGURAZIONE KNX
---------------------------------------------------------------
local group_mode = '5/0/0' -- 1=inverno / 0=estate
local group_in = '5/2/1' -- temperatura mandata
local group_out = '5/0/1' -- 0–10 V miscelatrice
local group_set_winter = '5/2/4'
local group_set_summer = '5/2/5'----
local group_kp = '5/2/6'
local group_ki = '5/2/7'
---------------------------------------------------------------
-- COSTANTI DI CONTROLLO
---------------------------------------------------------------
local cycle_time = 120 -- secondi
local filter_samples = 3 -- filtro ridotto
local out_min, out_max = 0, 100 -- %
local max_delta = 2 -- base rampa
local deadband = 0.3 -- °C
---------------------------------------------------------------
-- VARIABILI
---------------------------------------------------------------
local readings = {}
local last_output = storage.get('last_output') or 0
---------------------------------------------------------------
-- MEDIA MOBILE TEMPERATURA MANDATA
---------------------------------------------------------------
local function moving_average(v)
if type(v) ~= 'number' then return nil end
table.insert(readings, v)
if #readings > filter_samples then
table.remove(readings, 1)
end
local s = 0
for _, x in ipairs(readings) do
s = s + x
end
return s / #readings
end
---------------------------------------------------------------
-- CICLO PRINCIPALE
---------------------------------------------------------------
while true do
-------------------------------------------------------------
-- Aggiornamento Kp / Ki da KNX
-------------------------------------------------------------
local v = grp.getvalue(group_kp)
if type(v) == 'number' then
Kp = v
storage.set('Kp_PINT', Kp)
end
v = grp.getvalue(group_ki)
if type(v) == 'number' then
Ki = v
storage.set('Ki_PINT', Ki)
end
-------------------------------------------------------------
-- Modalità estate / inverno
-------------------------------------------------------------
local is_winter = grp.getvalue(group_mode) and true or false
if last_mode ~= is_winter then
integral = 0
last_mode = is_winter
storage.set('last_mode', is_winter)
end
-------------------------------------------------------------
-- Setpoint
-------------------------------------------------------------
local set = is_winter
and grp.getvalue(group_set_winter)
or grp.getvalue(group_set_summer)
if type(set) ~= 'number' then
set = is_winter and 35 or 25
end
-------------------------------------------------------------
-- Temperatura mandata filtrata
-------------------------------------------------------------
local T = grp.getvalue(group_in)
local Tf = moving_average(T)
if Tf then
-----------------------------------------------------------
-- LIMITI TEMPERATURA MANDATA
-----------------------------------------------------------
local Tmax = set + 2.0
local Tstop = set + 3.0
-----------------------------------------------------------
-- ERRORE
-------------------------------------------------------
