Guest User

turbine

a guest
Sep 28th, 2016
78
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 17.31 KB | None | 0 0
  1. --
  2. -- Control turbines of a active cooled Big Reactor (http://big-reactors.com/).
  3. --
  4. -- Author: kla_sch
  5. --
  6. -- History:
  7. --     v0.1, 2014-12-29:
  8. --         - first version
  9. --
  10. --     v0.2, 2015-01-02:
  11. --         - Big Reactor since 0.3.4A: Feature to disengage coil to
  12. --           startup faster (see config value useDisengageCoils).
  13. --         - minor bugfixes
  14. --         - some code improvments (thanks to wieselkatze)
  15. --
  16. -- Save as "startup"
  17.  
  18.  
  19. --
  20. -- Constant values (configuration)
  21. -- ===============================
  22.  
  23.  
  24. -- Maximum loop time of controller (default: 0.5s)
  25. local loopT=0.5
  26.  
  27. -- Display loop time, of controller has been switched off (default: 1s).
  28. local displayT=1
  29.  
  30. -- Modem channel to listen for status requests. If it is set to 0,
  31. -- the remote status requests are disabled.
  32. -- The sender sould simply send "BR_turbine_get_state" to this channel. The
  33. -- turbine replies with status informations to the replay channel.
  34. local stateRequestChannel = 32768 -- Broadcast channel.
  35.  
  36. --
  37. -- Enable remote control.
  38. -- Set to "true" if you want to enable this feature.
  39. --
  40. local remoteControl = true
  41.  
  42. --
  43. -- Use disengaged coil for faster speedup. (Big Reactor >= 0.3.4A).
  44. -- Set this to "false" if you want to disable this feature.
  45. --
  46. local useDisengageCoils = true
  47.  
  48.  
  49. --
  50. -- Internal values:
  51. -- ================
  52.  
  53. -- File to save last known controller state:
  54. local saveFilename = "turbine_crtl.save"
  55.  
  56. local Kp, Kd, Ki, errSum, errOld -- global PID controller values
  57. local tSpeed -- target speed
  58. local turbine -- wraped turbine
  59. local maxFRate -- maximum float rate of turbine
  60. local floatRateChanged = false -- flag: true on float rate change
  61.  
  62.  
  63. --
  64. -- Find the first connected big turbine and return the wraped handle.
  65. --
  66. -- If no turbine was found this function terminate the program.
  67. --
  68. -- Return:
  69. --     Handle of first connected turbine found.
  70. --
  71. local function getTurbineHandle()
  72.    local pList = peripheral.getNames()
  73.    local i, name
  74.    for i, name in pairs(pList) do
  75.       if peripheral.getType(name) == "BigReactors-Turbine" then
  76.          return peripheral.wrap(name)
  77.       end
  78.    end
  79.  
  80.    error("No turbine connected: Exit program")
  81. end
  82.  
  83.  
  84. --
  85. -- Search for any modem and open it to recieve modem requests.
  86. --
  87. local function searchAndOpenModems()
  88.    if stateRequestChannel <= 0 then
  89.       return -- feature disabled
  90.    end
  91.  
  92.    local pList = peripheral.getNames()
  93.    local i, name
  94.    for i, name in pairs(pList) do
  95.       if peripheral.getType(name) == "modem" then
  96.          peripheral.call(name, "open", stateRequestChannel)
  97.          peripheral.call(name, "open", os.getComputerID())
  98.       end
  99.    end
  100. end
  101.  
  102.  
  103. --
  104. -- Saves current controller state
  105. --
  106. local function saveControllerState()
  107.    local tmpFilename = saveFilename .. ".tmp"
  108.    
  109.    fs.delete(tmpFilename)
  110.    local sFile = fs.open(tmpFilename, "w")
  111.    if sFile == nil then
  112.       error("cannot open status file for writing.")
  113.    end
  114.  
  115.    sFile.writeLine("V0.1")
  116.    sFile.writeLine(tostring(tSpeed))
  117.    sFile.writeLine(tostring(loopT))
  118.    sFile.writeLine(tostring(Kp))
  119.    sFile.writeLine(tostring(errSum))
  120.    sFile.writeLine(tostring(errOld))
  121.    sFile.writeLine("EOF")
  122.    sFile.close()
  123.  
  124.    fs.delete(saveFilename)
  125.    fs.move(tmpFilename, saveFilename)
  126. end
  127.  
  128.  
  129. --
  130. -- Initialize basic PID controller values
  131. --
  132. local function initControllerValues()
  133.    local Tn = loopT
  134.    local Tv = loopT * 10
  135.  
  136.    Ki = Kp / Tn
  137.    Kd = Kp * Tv
  138. end
  139.  
  140.  
  141. --
  142. -- Read number from file
  143. --
  144. -- Parameters:
  145. --     sFile - opened file to read from
  146. --
  147. -- Return
  148. --     the number of nil, if an error has occurred
  149. --
  150. local function readNumber(sFile)
  151.    local s = sFile.readLine()
  152.    if s == nil then
  153.       return nil
  154.    end
  155.    return tonumber(s)
  156. end
  157.  
  158. --
  159. -- Restore last known controller state
  160. --
  161. -- Returns:
  162. --     true, if the last saved state has successfully readed.
  163. local function restoreControllerState()
  164.    local tmpFilename = saveFilename .. ".tmp"
  165.    local sFile = fs.open(saveFilename, "r")
  166.    if sFile == nil and fs.exists(tmpFilename) then
  167.       fs.move(tmpFilename, saveFilename)
  168.       sFile = fs.open(saveFilename)
  169.    end
  170.  
  171.    if sFile == nil then
  172.       return false -- cannot read any file
  173.    end
  174.  
  175.    local version = sFile.readLine()
  176.    if version == nil then
  177.       sFile.close()
  178.       return false -- empty file
  179.    end
  180.    if version ~= "V0.1" then
  181.       sFile.close()
  182.       return false -- unknown version
  183.    end
  184.  
  185.    local tSpeedNum = readNumber(sFile)
  186.    if tSpeedNum == nil then
  187.       sFile.close()
  188.       return false -- cannot read target speed
  189.    end
  190.  
  191.    local loopTNum = readNumber(sFile)
  192.    if loopTNum == nil then
  193.       sFile.close()
  194.       return false -- cannot read loop speed
  195.    end
  196.  
  197.    local KpNum = readNumber(sFile)
  198.    if KpNum == nil then
  199.       sFile.close()
  200.       return false -- cannot read Kp
  201.    end
  202.  
  203.    local errSumNum = readNumber(sFile)
  204.    if errSumNum == nil then
  205.       sFile.close()
  206.       return false -- cannot read error sum
  207.    end
  208.  
  209.    local errOldNum = readNumber(sFile)
  210.    if errOldNum == nil then
  211.       sFile.close()
  212.       return false -- cannot read last error
  213.    end
  214.  
  215.    local eofStr = sFile.readLine()
  216.    if eofStr == nil or eofStr ~= "EOF" then
  217.       sFile.close()
  218.       return false -- EOF marker not found. File corrupted?
  219.    end
  220.  
  221.    sFile.close()
  222.  
  223.    -- Restore saved values
  224.    tSpeed = tSpeedNum
  225.    loopT = loopTNum
  226.    Kp = KpNum
  227.    errSum = errSumNum
  228.    errOld = errOldNum
  229.  
  230.    initControllerValues()
  231.  
  232.    if tSpeed == 0 then
  233.       turbine.setActive(false)
  234.    else
  235.       turbine.setActive(true)
  236.    end
  237.  
  238.    return true
  239. end
  240.  
  241.  
  242. --
  243. -- Write text with colors, if possible (advance monitor)
  244. --
  245. -- Parameters:
  246. --     mon   - handle of monitor
  247. --     color - text color
  248. --     text  - text to write
  249. --
  250. local function writeColor(mon, color, text)
  251.    if mon.isColor() then
  252.       mon.setTextColor(color)
  253.    end
  254.    mon.write(text)
  255.    if mon.isColor() then
  256.       mon.setTextColor(colors.white)
  257.    end
  258. end
  259.  
  260.  
  261. --
  262. -- Scale the monitor text size to needed size of output text.
  263. --
  264. -- This function try to scale the monitor text size, so that it is enoth for
  265. -- "optHeight" lines with "optWidth" characters. If it is not possible
  266. -- (text scale is not supported or the connected monitor is too small),
  267. -- it also accept "minHeight" lines and "minWidth" characters.
  268. --
  269. -- Parameters:
  270. --     mon        - handle of monitor.
  271. --     minWidth   - Minimum number of columns needed.
  272. --     optWidth   - Optimal number of columns desired.
  273. --     minHeight  - Minimum number of rows needed.
  274. --     optHeight  - Optimal number of rows desired.
  275. --
  276. -- Return:
  277. --     Size of monitor after scaling: width, height.
  278. --     If the monitor is too small, it returns nul,nil.
  279. --
  280. local function scaleMonitor(mon, minWidth, optWidth, minHeight, optHeight)
  281.    if mon.setTextScale ~= nil then
  282.        mon.setTextScale(1)
  283.    end
  284.  
  285.    local width, height = mon.getSize()
  286.  
  287.    if mon.setTextScale == nil then
  288.       -- Scale not available
  289.       if width < minWidth or height < minHeight then
  290.          return nil, nil -- too small
  291.       else
  292.          return width, height
  293.       end
  294.    end
  295.  
  296.    if width < optWidth or height < optHeight then
  297.       -- too small: try to scale down.
  298.       mon.setTextScale(0.5)
  299.  
  300.       width, height = mon.getSize()
  301.       if width < minWidth or height < minHeight then
  302.          return nil, nil -- still too small
  303.       end
  304.    else
  305.       -- Huge monitors? Try to scale up, if possible (max scale=5).
  306.       local scale = math.min(width / optWidth, height / optHeight, 5)
  307.       scale = math.floor(scale * 2) / 2 -- multiple of 0.5
  308.  
  309.       if scale > 1 then
  310.          mon.setTextScale(scale)
  311.          width, height = mon.getSize()
  312.       end
  313.    end
  314.  
  315.    return width, height
  316. end
  317.  
  318.  
  319. -- Display turbine status to a monitor
  320. --
  321. -- Parameters:
  322. --     mon     - Wraped handle of monitor
  323. --     turbine - Wraped handle of turbine.
  324. --     tSpeed  - Target speed.
  325. --
  326. local function displayStateOnMonitor(mon, turbine, tSpeed)
  327.  
  328.    -- scale it, if possible.
  329.    local width, height = scaleMonitor(mon, 15, 16, 5, 5)
  330.    
  331.    if width == nil or height == nil then
  332.       return -- Montitor is too small
  333.    end
  334.  
  335.    mon.clear()
  336.  
  337.    mon.setCursorPos(1,1)
  338.    mon.write("Turbine: ")
  339.    if tSpeed == 0 then
  340.       writeColor(mon, colors.red, "off")
  341.    else
  342.       writeColor(mon, colors.green, string.format("%d", tSpeed))
  343.       if width > 16 then
  344.          mon.write(" RPM")
  345.       end
  346.    end
  347.  
  348.    mon.setCursorPos(1,3)
  349.    local speed = math.floor(turbine.getRotorSpeed()*10+0.5)/10
  350.    mon.write("Speed: ")
  351.    if (speed == tSpeed) then
  352.       writeColor(mon, colors.green, speed)
  353.    else
  354.       writeColor(mon, colors.orange, speed)
  355.    end
  356.    if width > 16 then
  357.        mon.write(" RPM")
  358.    end
  359.  
  360.    local maxFlow = turbine.getFluidFlowRateMax()
  361.    local actFlow = turbine.getFluidFlowRate()
  362.    if width >= 16 then
  363.       -- bigger monitor
  364.        mon.setCursorPos(1,4)
  365.        mon.write("MFlow: " .. string.format("%d", maxFlow) .. " mB/t")
  366.  
  367.        mon.setCursorPos(1,5)
  368.        mon.write("AFlow: ")
  369.        if actFlow < maxFlow then
  370.           writeColor(mon, colors.red, string.format("%d", actFlow))
  371.        else
  372.           writeColor(mon, colors.green, string.format("%d", actFlow))
  373.        end
  374.        mon.write(" mB/t")
  375.    else
  376.       -- 1x1 monitor
  377.        mon.setCursorPos(1,4)
  378.        mon.write("Flow (act/max):")
  379.        mon.setCursorPos(1,5)
  380.        mon.write("(")
  381.  
  382.        if actFlow < maxFlow then
  383.           writeColor(mon, colors.red, string.format("%d",actFlow))
  384.        else
  385.           writeColor(mon, colors.green, string.format("%d",actFlow))
  386.        end
  387.  
  388.        mon.write("/")
  389.        mon.write(string.format("%d", maxFlow))
  390.        mon.write(" mB/t)")
  391.    end
  392.    
  393. end
  394.  
  395.  
  396. -- Display turbine status to any connected monitor and also to console.
  397. --
  398. -- Parameters:
  399. --     turbine - Wraped handle of turbine.
  400. --     tSpeed  - Target speed.
  401. --
  402. function displayState(turbine, tSpeed)
  403.   displayStateOnMonitor(term, turbine, tSpeed)
  404.   term.setCursorPos(1,7)
  405.   term.write("* Keys: [o]ff, [m]edium (900), [f]ast (1800)")
  406.   term.setCursorPos(1,8)
  407.  
  408.    local pList = peripheral.getNames()
  409.    local i, name
  410.    for i, name in pairs(pList) do
  411.       if peripheral.getType(name) == "monitor" then
  412.          -- found monitor as peripheral
  413.          displayStateOnMonitor(peripheral.wrap(name), turbine, tSpeed)
  414.       end
  415.    end  
  416. end
  417.  
  418. --
  419. -- Test the speedup time of the turbine.
  420. --
  421. -- Parameters:
  422. --     turbine - Wraped handle of turbine.
  423. --     loopT   - Loop timer.
  424. --     tSpeed  - Target speed
  425. local function testSpeedup(turbine, loopT, tSpeed)
  426.    turbine.setFluidFlowRateMax(maxFRate)
  427.  
  428.    if turbine.setInductorEngaged then
  429.       -- always engage coil
  430.       turbine.setInductorEngaged(true)
  431.    end
  432.    
  433.    local KpSum=0
  434.    local nKp=0
  435.    local oldSpeed=-1
  436.  
  437.    for i=0,5 do
  438.       displayState(turbine, tSpeed)
  439.       speed = turbine.getRotorSpeed()
  440.       if oldSpeed >= 0 then
  441.          KpSum = KpSum + (speed-oldSpeed)
  442.          nKp = nKp + 1
  443.       end
  444.       oldSpeed = speed
  445.       sleep(loopT)
  446.    end
  447.  
  448.    if KpSum * loopT / nKp > 5 then
  449.       -- Too fast: increase loop speed
  450.       loopT = 5 * nKp / KpSum
  451.       return 5, loopT
  452.    else
  453.       return (KpSum / nKp), loopT
  454.    end
  455. end
  456.  
  457.  
  458. --
  459. -- Main program
  460. --
  461.  
  462. sleep(2) -- wait for 2s
  463.  
  464. -- wrap turbine
  465. turbine = getTurbineHandle()
  466.  
  467. searchAndOpenModems() -- search and open any modem.
  468.  
  469. if restoreControllerState() == false then
  470.    -- restore of old values failed.
  471.    tSpeed = 0
  472.    Kp=0
  473. end
  474.  
  475.  
  476. maxFRate = turbine.getFluidFlowRateMaxMax()
  477. while true do
  478.    displayState(turbine, tSpeed)
  479.  
  480.    if tSpeed ~= 0 then
  481.       -- calculate PID controller
  482.       local speed = turbine.getRotorSpeed()
  483.  
  484.       err = tSpeed - speed
  485.       local errSumOld = errSum
  486.       errSum = errSum + err
  487.  
  488.       if useDisengageCoils
  489.          and floatRateChanged
  490.          and err > 0
  491.          and turbine.setInductorEngaged
  492.       then
  493.          -- Turbine startup: disengage coils
  494.          turbine.setInductorEngaged(false)
  495.       end
  496.  
  497.       if turbine.setInductorEngaged and err < 0 then
  498.          -- speed too fast: engage coils
  499.          turbine.setInductorEngaged(true)
  500.       end
  501.  
  502.       local p = Kp * err
  503.       local i = Ki * loopT * errSum
  504.       local d = Kd * (err - errOld) * loopT
  505.  
  506.       if i < 0 or i > maxFRate then
  507.          errSum=errSumOld -- error too heavy => reset to old value.
  508.          i = Ki * loopT * errSum
  509.       end
  510.  
  511.       local fRate = p + i + d
  512.       errOld = err
  513.  
  514.  
  515.       -- cut extreme flow rates.
  516.       if fRate < 0 then
  517.          fRate = 0
  518.       elseif fRate > maxFRate then
  519.          fRate = maxFRate
  520.       end
  521.  
  522.       turbine.setFluidFlowRateMax(fRate)
  523.  
  524.       tID = os.startTimer(loopT) -- Wait for loopT secounds.
  525.    else
  526.       -- Turbine switched off:
  527.       tID = os.startTimer(displayT) -- Wait for displayT secounds.
  528.    end
  529.  
  530.    saveControllerState()
  531.    floatRateChanged = false
  532.    repeat
  533.       -- Event loop
  534.       local evt, p1, p2, p3, p4, p5 = os.pullEvent()
  535.       if evt == "char" then
  536.          -- character typed
  537.          local oldTSpeed = tSpeed
  538.          if p1 == "o" then -- off
  539.             tSpeed = 0
  540.             turbine.setActive(false)
  541.             saveControllerState()
  542.          elseif p1 == "m" then -- medium speed = 900 RPM
  543.             turbine.setActive(true)
  544.             tSpeed = 900
  545.             floatRateChanged=true
  546.          elseif p1 == "f" then -- fast speed = 1800 RPM
  547.             turbine.setActive(true)
  548.             tSpeed = 1800
  549.             floatRateChanged=true
  550.          end
  551.  
  552.          if turbine.setInductorEngaged then
  553.             -- engage coil by default
  554.             turbine.setInductorEngaged(true)
  555.          end
  556.  
  557.  
  558.          if (p1 == "m" or p1 == "f") and tSpeed ~= oldTSpeed then
  559.             -- Initialize PID controller values
  560.             if Kp == 0 then
  561.                Kp, loopT = testSpeedup(turbine, loopT, tSpeed)
  562.             end
  563.  
  564.             initControllerValues()
  565.  
  566.             errSum = 0
  567.             errOld = 0
  568.  
  569.             saveControllerState()
  570.          end
  571.       elseif evt == "modem_message"
  572.          and stateRequestChannel >= 0
  573.          and stateRequestChannel == p2
  574.          and tostring(p4) == "BR_turbine_get_state"
  575.       then
  576.          -- Send status informations
  577.          local cLabel = os.getComputerLabel()
  578.          if cLabel == nil then
  579.             -- No label: use coputer number (ID) as tio create a name
  580.             cLabel = "ComputerID:" .. tostring(os.getComputerID())
  581.          end
  582.  
  583.          if turbine.getInductorEngaged ~= nil then
  584.             inductorEngaged = turbine.getInductorEngaged()
  585.          else
  586.             inductorEngaged = nil
  587.          end
  588.  
  589.          -- State structure
  590.          local rState = {
  591.             version = "Turbine V0.1", -- Version of data structure
  592.             label = cLabel,
  593.             computerId = os.getComputerID(),
  594.             remoteControl = remoteControl,
  595.             active = turbine.getActive(),
  596.             tSpeed = tSpeed,
  597.             energyStored = turbine.getEnergyStored(),
  598.             rotorSpeed = turbine.getRotorSpeed(),
  599.             inputAmount = turbine.getInputAmount(),
  600.             inputType = turbine.getInputType(),
  601.             outputAmount = turbine.getOutputAmount(),
  602.             outputType = turbine.getOutputType(),
  603.             fluidAmountMax = turbine.getFluidAmountMax(),
  604.             fluidFlowRate = turbine.getFluidFlowRate(),
  605.             fluidFlowRateMax = turbine.getFluidFlowRateMax(),
  606.             fluidFlowRateMaxMax = turbine.getFluidFlowRateMaxMax(),
  607.             energyProducedLastTick = turbine.getEnergyProducedLastTick(),
  608.             inductorEngaged = inductorEngaged
  609.          }
  610.  
  611.          peripheral.call(p1, "transmit", p3, stateRequestChannel,
  612.                          textutils.serialize(rState))
  613.       elseif evt == "modem_message"
  614.          and remoteControl
  615.          and p2 == os.getComputerID()
  616.          and string.sub(tostring(p4), 1, 21) == "BR_turbine_set_speed:"
  617.       then
  618.          -- Remote Request: Speed change
  619.          local oldTSpeed = tSpeed
  620.          tSpeed = tonumber(string.sub(tostring(p4), 22))
  621.          if (tSpeed == 0) then
  622.             turbine.setActive(false)
  623.             saveControllerState()            
  624.          else
  625.             turbine.setActive(true)
  626.             floatRateChanged=true
  627.          end
  628.  
  629.          if tSpeed ~= 0 and tSpeed ~= oldTSpeed then
  630.             -- Initialize PID controller values
  631.             if Kp == 0 then
  632.                Kp = testSpeedup(turbine, loopT, tSpeed)
  633.             end
  634.  
  635.             initControllerValues()
  636.  
  637.             errSum = 0
  638.             errOld = 0
  639.  
  640.             saveControllerState()
  641.          end
  642.  
  643.          if turbine.setInductorEngaged then
  644.             -- engage coil by default
  645.             turbine.setInductorEngaged(true)
  646.          end
  647.       elseif evt == "peripheral"
  648.          and peripheral.getType(p1) == "modem"
  649.          and stateRequestChannel >= 0
  650.       then
  651.          -- new modem connected
  652.          peripheral.call(p1, "open", stateRequestChannel)
  653.          peripheral.call(p1, "open", os.getComputerID())
  654.       end
  655.  
  656.       -- exit loop on timer event or float rate change
  657.    until (evt == "timer" and p1 == tID) or floatRateChanged
  658. end
  659.  
  660. --
  661. -- EOF
  662. --
Add Comment
Please, Sign In to add comment