ControllerObj = {} require("lib") function ControllerObj:new() local obj = obj or {} setmetatable(obj, {__index = self}) obj.isEnabled = false obj.p = 1.0 -- proportional gain obj.tn = 1.0 -- integral reset time obj.d = 0.0 -- derivative obj.dFilter = 0 -- derivative filter time (in cylces) obj.dError = false -- use error for derivative. if false, use value obj.sp = 10.0 -- setpoint obj.pv = 0.0 obj.intMax = 100.0 -- integral max obj.intMin = 0.0 -- integral min obj.outMax = 100.0 -- output max obj.outMin = 0.0 -- output min obj.error = 0.0 -- gain obj.errorLast = 0.0 -- last error obj.errorList = {} obj.dCalcLast = 0.0 -- last value used for derivative calculation obj.dCalcList = {} -- smoothing buffer obj.dCalcSum = 0.0 -- obj.int = 0.0 -- integral obj.deriv = 0 -- derivative obj.cv = 0 -- output obj.tLast = os.epoch("utc") obj.firstCall = true return obj end function ControllerObj:update(pv) -- time delta calc so the integral is more accurate and the controller doesnt fluctuate with bad server tickrates -- os.epoch("utc") is used because its far more accurate than os.time local tNow = os.epoch("utc") local tDelta = (tNow - self.tLast) / 1000 if tDelta <= 0 or tDelta >= 10 then tDelta = 1 end self.pv = pv -- immediately exit if the controller is disabled if not self.isEnabled then return 0.0 end -- gain self.error = (self.sp - self.pv) * self.p -- integral if self.tn <= 0 then self.int = 0.0 elseif self.cv < self.outMax and self.cv > self.outMin then -- anti windup if self.int + self.error + self.deriv >= self.outMax then self.int = self.outMax - (self.error + self.deriv) else self.int = ((self.error / self.tn) * tDelta) + self.int end end if self.int <= self.intMin then self.int = self.intMin end if self.int >= self.intMax then self.int = self.intMax end -- derivative local dVal = 0.0 if self.dError then dVal = self.error -- error derivative else dVal = -(self.pv*self.p) -- value RoC derivative end if self.d >= 0 then if self.dFilter > 0 then self.dCalcSum = smooth(self.dCalcList, dVal, self.dFilter) self.deriv = ((self.dCalcSum - self.dCalcLast) * self.d)*tDelta self.dCalcLast = self.dCalcSum else self.dCalcSum = 0.0 self.deriv = ((dVal - self.dCalcLast) * self.d)*tDelta self.dCalcLast = dVal end else self.deriv = 0 end self.tLast = os.epoch("utc") -- output self.cv = self.error + self.int + self.deriv if self.cv <= self.outMin then self.cv = self.outMin end if self.cv >= self.outMax then self.cv = self.outMax end if self.firstCall and self.cv >= self.outMax then self.int = 0 end self.firstCall = false return self.cv end function ControllerObj:setEnabled(bool) self.isEnabled = bool or false if not self.isEnabled then self:reset() end end function ControllerObj:setGain(gain) self.p = gain end function ControllerObj:setResetTime(time) self.tn = time end function ControllerObj:setDerivative(deriv) self.d = deriv end function ControllerObj:setDerivativeMode(bool) self.dError = bool end function ControllerObj:setDerivativeFilter(cycles) self.dFilter = cycles end function ControllerObj:setSetpoint(sp) self.sp = sp end function ControllerObj:setIntegralMax(max) self.intMax = max end function ControllerObj:setIntegralMin(min) self.intMin = min end function ControllerObj:setOutputMax(max) self.outMax = max end function ControllerObj:setOutputMin(min) self.outMin = min end function ControllerObj:reset() -- reset the controller values to 0 self.error = 0.0 self.int = 0.0 self.deriv = 0.0 self.cv = 0.0 self.errorList = {} self.firstCall = false end function ControllerObj:getPV() return self.pv end function ControllerObj:getCV() return self.cv end