Advertisement
hoblin

draft PID reactor controller

Jan 15th, 2017
122
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 6.65 KB | None | 0 0
  1. -- draft PID reactor controller
  2. -- pastebin get YqeJL0qV startup
  3.  
  4. local pid = {}
  5.  
  6. local function clamp(x, min, max)
  7.   if x > max then
  8.     return max
  9.   elseif x < min then
  10.     return min
  11.   else
  12.     return x
  13.   end
  14. end
  15.  
  16. local seconds = nil
  17. do
  18.   local done, socket = pcall(require, "socket")
  19.   if not done then
  20.     socket = nil
  21.   end
  22.   local done, computer = pcall(require, "computer")
  23.   if not done then
  24.     computer = nil
  25.   end
  26.   seconds = (socket and socket.gettime) or (computer and computer.uptime) or os.time
  27. end
  28.  
  29. -- all values of the PID controller
  30. -- values with '_' at beginning are considered private and should not be changed.
  31. pid = {
  32.   kp = 10,
  33.   ki = 3,
  34.   kd = 3,
  35.   input = nil,
  36.   target = nil,
  37.   output = nil,
  38.  
  39.   minout = -math.huge,
  40.   maxout = math.huge,
  41.  
  42.   _lasttime = nil,
  43.   _lastinput = nil,
  44.   _Iterm = 0
  45. }
  46.  
  47. -- Creates a new PID controller.
  48. -- Passing table as an argument will make it used as an object base.
  49. -- It allows for convinient saving and loading of adjusted PID controller.
  50. function pid:new(save)
  51.   assert(save == nil or type(save) == "table", "If save is specified the it has to be table.")
  52.   save = save or {}
  53.   setmetatable(save, self)
  54.   self.__index = self
  55.   return save
  56. end
  57.  
  58. -- Exports calibration variables and targeted value.
  59. function pid:save()
  60.   return {kp = self.kp, ki = self.ki, kd = self.kd, target = self.target, minout = self.minout, maxout = self.maxout}
  61. end
  62.  
  63. -- This is main method of PID controller.
  64. -- After creation of controller you have to set 'target' value in controller table
  65. -- then in loop you should regularly update 'input' value in controller table,
  66. -- call c:compute() and set 'output' value to the execution system.
  67. -- c.minout = 0
  68. -- c.maxout = 100
  69. -- while true do
  70. --   c.input = getCurrentEnergyLevel()
  71. --   c:compute()
  72. --   reactorcontrol:setAllControlRods(100 - c.output) -- PID expects the increase of output value will cause increase of input
  73. --   sleep(0.5)
  74. -- end
  75. -- You can limit output range by specifying 'minout' and 'maxout' values in controller table.
  76. -- By passing 'true' to the 'compute' function you will cause controller to not to take any actions but only
  77. -- refresh internal variables. It is most useful if PID controller was disconnected from the system.
  78. function pid:compute(waspaused)
  79.   assert(self.input and self.target, "You have to sepecify current input and target before running compute()")
  80.  
  81.   -- reset values if PID was paused for prolonegd period of time
  82.   if waspaused or self._lasttime == nil or self._lastinput == nil then
  83.     self._lasttime = seconds()
  84.     self._lastinput = self.input
  85.     self._Iterm = self.output or 0
  86.     return
  87.   end
  88.  
  89.   local err = self.target - self.input
  90.   local dtime = seconds() - self._lasttime
  91.  
  92.   if dtime == 0 then
  93.     return
  94.   end
  95.  
  96.   self._Iterm = self._Iterm + self.ki * err * dtime
  97.   self._Iterm = clamp(self._Iterm, self.minout, self.maxout)
  98.  
  99.   local dinput = (self.input - self._lastinput) / dtime
  100.   self.output = self.kp * err + self._Iterm - self.kd * dinput
  101.   self.output = clamp(self.output, self.minout, self.maxout)
  102.  
  103.   self._lasttime = seconds()
  104.   self._lastinput = self.input
  105. end
  106.  
  107. reactorList = {}
  108. debugMode = true
  109.  
  110. -- File needs to exist for append "a" later and zero it out if it already exists
  111. -- Always initalize this file to avoid confusion with old files and the latest run
  112. local logFile = fs.open("reactorcontrol.log", "w")
  113. if logFile then
  114.   logFile.writeLine("Minecraft time: Day "..os.day().." at "..textutils.formatTime(os.time(),true))
  115.   logFile.close()
  116. else
  117.   error("Could not open file reactorcontrol.log for writing")
  118. end
  119.  
  120. local function printLog(printStr)
  121.   if debugMode then
  122.     print(printStr)
  123.     local logFile = fs.open("reactorcontrol.log", "a") -- See http://computercraft.info/wiki/Fs.open
  124.     if logFile then
  125.       logFile.writeLine(printStr)
  126.       logFile.close()
  127.     else
  128.       error("Cannot open file reactorcontrol.log for appending!")
  129.     end -- if logFile then
  130.   end -- if debugMode then
  131. end -- function printLog(printStr)
  132.  
  133. -- Return a list of all connected (including via wired modems) devices of "deviceType"
  134. local function getDevices(deviceType)
  135.   local deviceName = nil
  136.   local deviceIndex = 1
  137.   local deviceList, deviceNames = {}, {} -- Empty array, which grows as we need
  138.   local peripheralList = peripheral.getNames() -- Get table of connected peripherals
  139.  
  140.   deviceType = deviceType:lower() -- Make sure we're matching case here
  141.  
  142.   for peripheralIndex = 1, #peripheralList do
  143.     -- Log every device found
  144.     -- printLog("Found "..peripheral.getType(peripheralList[peripheralIndex]).."["..peripheralIndex.."] attached as \""..peripheralList[peripheralIndex].."\".")
  145.     if (string.lower(peripheral.getType(peripheralList[peripheralIndex])) == deviceType) then
  146.       -- Log devices found which match deviceType and which device index we give them
  147.       printLog("Found "..peripheral.getType(peripheralList[peripheralIndex]).."["..peripheralIndex.."] as index \"["..deviceIndex.."]\" attached as \""..peripheralList[peripheralIndex].."\".")
  148.       deviceNames[deviceIndex] = peripheralList[peripheralIndex]
  149.       deviceList[deviceIndex] = peripheral.wrap(peripheralList[peripheralIndex])
  150.       deviceIndex = deviceIndex + 1
  151.     end
  152.   end -- for peripheralIndex = 1, #peripheralList do
  153.  
  154.   return deviceList, deviceNames
  155. end -- function getDevices(deviceType)
  156.  
  157.  
  158. -- Initialize all Big Reactors - Reactors
  159. local function findReactors()
  160.   -- Empty out old list of reactors
  161.   newReactorList = {}
  162.  
  163.   printLog("Finding reactors...")
  164.   newReactorList, reactorNames = getDevices("BigReactors-Reactor")
  165.  
  166.   if #newReactorList == 0 then
  167.     printLog("No reactors found!")
  168.     error("Can't find any reactors!")
  169.   else  -- Placeholder
  170.     for reactorIndex = 1, #newReactorList do
  171.       local reactor = nil
  172.       reactor = newReactorList[reactorIndex]
  173.  
  174.       if not reactor then
  175.         printLog("reactorList["..reactorIndex.."] in findReactors() was not a valid Big Reactor")
  176.         return -- Invalid reactorIndex
  177.       end
  178.     end -- for reactorIndex = 1, #newReactorList do
  179.   end -- if #newReactorList == 0 then
  180.  
  181.   -- Overwrite old reactor list with the now updated list
  182.   reactorList = newReactorList
  183. end -- function findReactors()
  184.  
  185.  
  186. function main()
  187.   findReactors()
  188.   r = reactorList[1]
  189.   c = pid:new()
  190.   c.target = 50
  191.   c.minout = 0
  192.   c.maxout = 100
  193.   while true do
  194.     c.input = ((r.getEnergyStored() / 10000000) * 100)
  195.     c:compute()
  196.     print(c.output)
  197.     if c.output then
  198.       r.setAllControlRodLevels(100 - c.output) -- PID expects the increase of output value will cause increase of input
  199.     end
  200.     sleep(0.5)
  201.   end
  202. end
  203.  
  204. main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement