### Example: PID thermostat with LM

#### PID function

There is a PID function already added by default in Logic Machine -> Scripts -> Tools menu.

`PID = {  -- default params  defaults = {    -- invert algorithm, used for cooling    inverted = false,    -- minimum output value    min = 0,    -- maximum output value    max = 100,    -- proportional gain    kp = 1,    -- integral gain    ki = 1,    -- derivative gain    kd = 1,  }} -- PID init, returns new PID objectfunction 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 nend -- resets algorithm on init or a switch back from manual modefunction 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 valuefunction 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 valuefunction 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  endend -- algorithm step, returns nil when disabled or no action is required, output value otherwisefunction 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 resultend -- computes new output valuefunction 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.outputend`

#### Usage

`p = PID:init(parameters)p:run()`

#### Parameters

Mandatory:

• current – (object address or name) current temperature value (2 byte float or any numeric value)
• setpoint – (object address or name) temperature set point value (2 byte float or any numeric value)

Optional:

• manual – (object address or name) PID algorithm is stopped when this object value is 1
• output – (object address or name, can be a table with multiple objects) output object (1 byte scaled)
• inverted – (boolean, defaults to false) invert algorithm, can be used for cooling
• min – (number, defaults to 0) minimum output value
• max – (number, defaults to 100) maximum output value
• kp – (number, defaults to 1) proportional gain
• ki – (number, defaults to 1) integral gain
• kd – (number, defaults to 1) derivative gain

PID algorithm should be placed inside a Scripts -> Resident –> Add new script.

Script example:

`-- init pid algorithmif not p then  p = PID:init({    current = '1/1/1',    setpoint = '1/1/2',    output = '1/1/3'  })end -- run algorithmp:run()`

Script example with multiple output objects:

`-- init pid algorithmif not p then  p = PID:init({    current = '1/1/1',    setpoint = '1/1/2',    output = { 'PWM 1', 'PWM 2', '1/1/5' }  })end -- run algorithmp:run()`

Output value:
p:run() returns output value. If output parameter is not set, you can use the return value to control output objects manually in the script.

#### Maximum count of PID per device

LogicMachine normally can handle up to 30 PID loops. But it depends on resident script sleep time. We suggest to use a single script for all PID controllers instead of a separate script for each.