Advertisement
iMajesticButter

Untitled

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