PID algorithm questions - Novodk - 15.04.2023
Hi, I'm trying to setup underfloor heating with SE Valves
This is my setup/configuration, but I have some questions:
- Is this how it should be done?
- If I want to use GA 1/1/3 to indicate if the heating is On or Off with 1 bit 01.001 switching how can I do that?
- What if I want heating in more rooms? Do I just make a new Residential script and according Group Addresses?
- he Manual mode, it says that the PID algorithm is stopped when this object value is 1, is it stopped or just paused? Should I use it to stop heating if my window is open in that room?
- Again about the Manual mode, I have window switches connected to a knx binary input, and the way I use it is with 1bit - 01.009, so that it says Close(1) if the window is closed and Open(0) if the window is open, but thats reversed, how to fix that?
Group Addresses:
1/1/0 Test Room Setpoint Temp - (2 byte - 09-001 Temperature) - Temp setpoint visualization
1/1/1 Test Room Output Value - (1 byte - 05.001 scale) - SE Valve, Group Object: 0: Valve Actuating value - Drive to position
1/1/2 Test Room Actual Position - (1 byte - 05.001 scale) - SE Valve, Group Object: 2: Actual valve position - Indicate actual valve position
1/1/3 Test Room Heating On/Off - (1 bit - 01.001 switch) -
1/1/4 Test Room Window Contact - (1 bit - 01.009 open/close) - Binary input, Group Object: 0: Window open/close
1/1/5 Test Room Current Temp - (2 byte - 09-001 Temperature) - PIR with temp, Group Object: 11: Temperature sensor:Output
Scripting -> Common functions
Code: -- PID init, returns new PID object
function PID:init(params)
local n = setmetatable({}, { __index = PID })
local k, v
-- set user parameters
n.params = params
-- copy parameters that are set by user
for k, v in pairs(PID.defaults) do
if n.params[ k ] == nil then
n.params[ k ] = v
end
end
-- reverse gains in inverted mode
if n.params.inverted then
n.params.kp = -n.params.kp
n.params.ki = -n.params.ki
n.params.kd = -n.params.kd
end
return n
end
-- resets algorithm on init or a switch back from manual mode
function PID:reset()
-- previous value
self.previous = grp.getvalue(self.params.current)
-- reset iterm
self.iterm = 0
-- last running time
self.lasttime = os.time()
-- clamp iterm
self:clampiterm()
end
-- clamps iterm value
function PID:clampiterm()
self.iterm = math.max(self.iterm, self.params.min)
self.iterm = math.min(self.iterm, self.params.max)
end
-- clamp and set new output value
function PID:setoutput()
local t, object, value
self.output = math.max(self.output, self.params.min)
self.output = math.min(self.output, self.params.max)
value = math.floor(self.output)
local t = type(self.params.output)
-- write to output if object is set
if t == 'string' or t == 'table' then
if t == 'string' then
self.params.output = { self.params.output }
end
for _, output in ipairs(self.params.output) do
grp.write(output, value, dt.scale)
end
end
end
-- algorithm step, returns nil when disabled or no action is required, output value otherwise
function PID:run()
local result
-- get manual mode status
local manual = self.params.manual and grp.getvalue(self.params.manual) or false
-- in manual mode, do nothing
if manual then
self.running = false
-- not in manual, check if reset is required after switching on
elseif not self.running then
self:reset()
self.running = true
end
-- compute new value if not in manual mode
if self.running then
-- get time between previous and current call
local now = os.time()
self.deltatime = now - self.lasttime
self.lasttime = now
-- run if previous call was at least 1 second ago
if self.deltatime > 0 then
result = self:compute()
end
end
return result
end
-- computes new output value
function PID:compute()
local current, setpoint, deltasc, deltain, output
-- get input values
current = grp.getvalue(self.params.current)
setpoint = grp.getvalue(self.params.setpoint)
-- delta between setpoint and current
deltasc = setpoint - current
-- calculate new iterm
self.iterm = self.iterm + self.params.ki * self.deltatime * deltasc
self:clampiterm()
-- delta between current and previous value
deltain = current - self.previous
-- calculate output value
self.output = self.params.kp * deltasc + self.iterm
self.output = self.output - self.params.kd / self.deltatime * deltain
-- write to output
self:setoutput()
-- save previous value
self.previous = current
return self.output
end
Scripting -> Resident -> PID Test Room (Sleep interval 10 seconds)
Code: -- init pid algorithm
if not p then
p = PID:init({
current = '1/1/5',
setpoint = '1/1/0',
output = '1/1/1',
manual = '1/1/4',
min = '0',
max = '100',
kp = '1',
ki = '1',
kd = '1'
})
end
-- run algorithm
p:run()
RE: PID algorithm questions - admin - 17.04.2023
1. Your PID library is incomplete. Copy it from here: https://openrb.com/example-pid-thermostat-with-lm2/
Don't put quotes around numbers, only around group addresses. You can remove min/max/kp/ki/kd settings from PID configuration if you are using the default values.
2. You can add an extra if into the script to check whether heating is enabled and window is closed. Remove manual = '1/1/4', from PID configuration.
Code: if grp.getvalue('1/1/3') and grp.getvalue('1/1/4') then
p:run()
else
grp.checkwrite(p.params.output, 0)
end
3. You can have multiple PIDs in a single script (use different variable names for each). Or you can make separate script for each PID. There's no big difference unless you need more than 10-20 PIDs. See this: https://forum.logicmachine.net/showthread.php?tid=2920
4. The manual mode does not turn off the output. It just stops the PID algorithm. Script in nr.2 sets output to 0 when either the heating is disabled or a window is open.
|