zodiak707

Reactor Control V1.3

Dec 19th, 2025 (edited)
38
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 26.98 KB | None | 0 0
  1. -------------------------
  2. -- Konfiguration
  3. -------------------------
  4. local version = 3.2
  5. local CFG = {
  6.     tickSeconds = 1.0,
  7.     -- Energie-Puffer in % (0..100)
  8.     targetBuffer = 60,
  9.     hysteresis = 4,
  10.     startAt = 35,
  11.     stopAt = 90,
  12.     -- Steuerstab-Limits
  13.     minRod = 0,
  14.     maxRod = 100,
  15.     -- Temperaturgrenzen
  16.     maxFuelTemp = 2000,
  17.     maxCasingTemp = 1500,
  18.     -- Fuel-Schutz
  19.     minFuelPercent = 2,
  20.     -- Anzeige
  21.     useMonitor = true,
  22.     monitorScale = 0.5,
  23.     -- WebSocket
  24.     wsEnabled = true,
  25.     wsUrl = "ws://lupus7x.de:7777/ws/ingest",
  26.     wsSendEveryTicks = 1,
  27.  
  28.     -- Redstone-Freigabe: Reaktor nur aktiv wenn Signal==15 ankommt
  29.     rsEnableGate = true,
  30.     rsEnableSides = { "top", "bottom" }, -- oben oder unten
  31.     rsEnableLevel = 15,                  -- analoger Level, muss exakt passen
  32.     rsGateScramRods = true               -- wenn nicht freigegeben: rods auf 100
  33. }
  34.  
  35. local lastActiveKnown = false
  36.  
  37. -------------------------
  38. -- Hilfsfunktionen
  39. -------------------------
  40. local function clamp(x, a, b)
  41.     if x < a then
  42.         return a
  43.     end
  44.     if x > b then
  45.         return b
  46.     end
  47.     return x
  48. end
  49.  
  50. local function round(x)
  51.     return math.floor(x + 0.5)
  52. end
  53.  
  54. local function safeCall(fn, default)
  55.     local ok, val = pcall(fn)
  56.     if ok then
  57.         return val
  58.     end
  59.     return default
  60. end
  61.  
  62. local function fmtPercent(x)
  63.     return tostring(round(x)) .. "%"
  64. end
  65.  
  66. -- RS Gate: TRUE wenn Freigabe anliegt (analog==15 an top oder bottom)
  67. local function rsIsEnabled()
  68.     if not CFG.rsEnableGate then return true end
  69.     for _, side in ipairs(CFG.rsEnableSides or {}) do
  70.         local lvl = redstone.getAnalogInput(side) or 0
  71.         if lvl == CFG.rsEnableLevel then
  72.             return true
  73.         end
  74.     end
  75.     return false
  76. end
  77.  
  78. local function getEnergyCapacity(reactor)
  79.     local candidates = {
  80.         "getEnergyCapacity",
  81.         "getEnergyStoredMax",
  82.         "getMaxEnergyStored",
  83.         "getEnergyBufferSize",
  84.         "getEnergyMax"
  85.     }
  86.  
  87.     for _, m in ipairs(candidates) do
  88.         if type(reactor[m]) == "function" then
  89.             local ok, v = pcall(reactor[m])
  90.             if ok and type(v) == "number" and v > 0 then
  91.                 return v, m
  92.             end
  93.         end
  94.     end
  95.  
  96.     return nil, nil
  97. end
  98.  
  99. local function findReactor()
  100.     local candidates = {
  101.         "BigReactors-Reactor",
  102.         "ExtremeReactors-Reactor",
  103.         "BiggerReactors_Reactor",
  104.         "Reactor"
  105.     }
  106.  
  107.     for _, t in ipairs(candidates) do
  108.         local p = peripheral.find(t)
  109.         if p then
  110.             return p, t
  111.         end
  112.     end
  113.  
  114.     for _, name in ipairs(peripheral.getNames()) do
  115.         local p = peripheral.wrap(name)
  116.         if
  117.             p and type(p.getEnergyStored) == "function" and type(p.setActive) == "function" and
  118.                 type(p.getNumberOfControlRods) == "function"
  119.          then
  120.             return p, peripheral.getType(name) or name
  121.         end
  122.     end
  123.  
  124.     return nil, nil
  125. end
  126.  
  127. local function findMonitor()
  128.     local m = peripheral.find("monitor")
  129.     if m then
  130.         pcall(
  131.             function()
  132.                 m.setTextScale(CFG.monitorScale)
  133.             end
  134.         )
  135.     end
  136.     return m
  137. end
  138.  
  139. local function setAllRods(reactor, level)
  140.     local n = reactor.getNumberOfControlRods()
  141.     for i = 0, n - 1 do
  142.         reactor.setControlRodLevel(i, level)
  143.     end
  144. end
  145.  
  146. local function getAvgRodLevel(reactor)
  147.     local n = reactor.getNumberOfControlRods()
  148.     if n <= 0 then
  149.         return 0
  150.     end
  151.     local sum = 0
  152.     for i = 0, n - 1 do
  153.         sum = sum + reactor.getControlRodLevel(i)
  154.     end
  155.     return sum / n
  156. end
  157.  
  158. local function getReactorActive(reactor, fallback)
  159.     local candidates = {"getActive", "getIsActive", "isActive"}
  160.     for _, m in ipairs(candidates) do
  161.         if type(reactor[m]) == "function" then
  162.             local ok, v = pcall(reactor[m])
  163.             if ok and type(v) == "boolean" then
  164.                 return v
  165.             end
  166.         end
  167.     end
  168.     return fallback
  169. end
  170.  
  171. -------------------------
  172. -- Self-Update (Pastebin)
  173. -------------------------
  174. local PASTEBIN_ID = "VeAGySQm"
  175. local STARTUP_FILE = "startup"
  176.  
  177. local function downloadPastebin(id)
  178.     if not http or type(http.get) ~= "function" then
  179.         return nil, "HTTP nicht verfügbar (in CC/Tweaked config HTTP aktivieren)."
  180.     end
  181.  
  182.     local url = "https://pastebin.com/raw/" .. tostring(id)
  183.     local h = http.get(url)
  184.     if not h then
  185.         return nil, "Download fehlgeschlagen (http.get nil). URL: " .. url
  186.     end
  187.  
  188.     local data = h.readAll()
  189.     h.close()
  190.  
  191.     if not data or data == "" then
  192.         return nil, "Leerer Download von Pastebin (ID ok?)."
  193.     end
  194.  
  195.     return data, nil
  196. end
  197.  
  198. local function writeFile(path, content)
  199.     local h = fs.open(path, "w")
  200.     if not h then
  201.         return false, "Kann Datei nicht öffnen: " .. tostring(path)
  202.     end
  203.     h.write(content)
  204.     h.close()
  205.     return true, nil
  206. end
  207.  
  208. local function selfUpdate()
  209.     -- Script holen, BEVOR wir startup anfassen (damit wir nicht ohne Datei dastehen)
  210.     local newCode, err = downloadPastebin(PASTEBIN_ID)
  211.     if not newCode then
  212.         return false, "Update abgebrochen: " .. tostring(err)
  213.     end
  214.  
  215.     -- alte startup löschen (wenn möglich)
  216.     if fs.exists(STARTUP_FILE) then
  217.         if fs.isDir(STARTUP_FILE) then
  218.             return false, "Update abgebrochen: 'startup' ist ein Ordner?!"
  219.         end
  220.         pcall(
  221.             function()
  222.                 fs.delete(STARTUP_FILE)
  223.             end
  224.         )
  225.     end
  226.  
  227.     -- neue startup schreiben
  228.     local ok, werr = writeFile(STARTUP_FILE, newCode)
  229.     if not ok then
  230.         return false, "Update fehlgeschlagen beim Schreiben: " .. tostring(werr)
  231.     end
  232.  
  233.     return true, "Update OK: neue startup installiert."
  234. end
  235.  
  236. -------------------------
  237. -- Persistenter Name
  238. -------------------------
  239. local NAME_FILE = "reactor_name.txt"
  240.  
  241. local function loadReactorName(defaultName)
  242.     if fs.exists(NAME_FILE) and not fs.isDir(NAME_FILE) then
  243.         local h = fs.open(NAME_FILE, "r")
  244.         local txt = h.readAll()
  245.         h.close()
  246.         txt = (txt and txt:gsub("^%s+", ""):gsub("%s+$", "")) or ""
  247.         if txt ~= "" then
  248.             return txt
  249.         end
  250.     end
  251.     return defaultName
  252. end
  253.  
  254. local function saveReactorName(name)
  255.     local h = fs.open(NAME_FILE, "w")
  256.     h.write(tostring(name or ""))
  257.     h.close()
  258. end
  259.  
  260. -------------------------
  261. -- WebSocket Client (Auto-Reconnect)
  262. -------------------------
  263. local ws = nil
  264. local wsLastErr = nil
  265.  
  266. local function wsConnect()
  267.     if not CFG.wsEnabled then
  268.         return
  269.     end
  270.     if not http or type(http.websocket) ~= "function" then
  271.         wsLastErr = "http.websocket nicht verfügbar (HTTP in CC config aktivieren)."
  272.         return
  273.     end
  274.     if ws then
  275.         return
  276.     end
  277.  
  278.     local ok, connOrErr =
  279.         pcall(
  280.         function()
  281.             return http.websocket(CFG.wsUrl)
  282.         end
  283.     )
  284.  
  285.     if ok and connOrErr then
  286.         ws = connOrErr
  287.         wsLastErr = nil
  288.     else
  289.         wsLastErr = tostring(connOrErr)
  290.         ws = nil
  291.     end
  292. end
  293.  
  294. local function wsClose()
  295.     if ws then
  296.         pcall(
  297.             function()
  298.                 ws.close()
  299.             end
  300.         )
  301.     end
  302.     ws = nil
  303. end
  304.  
  305. local function wsSend(tbl)
  306.     if not CFG.wsEnabled or not ws then
  307.         return
  308.     end
  309.     local msg = textutils.serializeJSON(tbl)
  310.     local ok =
  311.         pcall(
  312.         function()
  313.             ws.send(msg)
  314.         end
  315.     )
  316.     if not ok then
  317.         wsClose()
  318.     end
  319. end
  320.  
  321. -------------------------
  322. -- UI
  323. -------------------------
  324. local UI = {}
  325.  
  326. UI.tab = 1 -- 1=Reactor, 2=Status, 3=Temperatur, 4=Steuerung
  327.  
  328. local HAS_COLOR = (term.isColor and term.isColor()) or false
  329. local function setBG(t, c) if HAS_COLOR and t.setBackgroundColor then t.setBackgroundColor(c) end end
  330. local function setFG(t, c) if HAS_COLOR and t.setTextColor then t.setTextColor(c) end end
  331.  
  332. local function clampInt(x, a, b)
  333.   if x < a then return a end
  334.   if x > b then return b end
  335.   return x
  336. end
  337.  
  338. local function fill(t, x, y, w, h, ch, fg, bg)
  339.   ch = ch or " "
  340.   if bg then setBG(t, bg) end
  341.   if fg then setFG(t, fg) end
  342.   for yy = y, y + h - 1 do
  343.     t.setCursorPos(x, yy)
  344.     t.write(string.rep(ch, w))
  345.   end
  346. end
  347.  
  348. local function writeAt(t, x, y, s, fg, bg)
  349.   if x < 1 or y < 1 then return end
  350.   local w, h = t.getSize()
  351.   if y > h then return end
  352.   if x > w then return end
  353.   if bg then setBG(t, bg) end
  354.   if fg then setFG(t, fg) end
  355.   t.setCursorPos(x, y)
  356.   local maxLen = w - x + 1
  357.   if maxLen <= 0 then return end
  358.   if #s > maxLen then s = s:sub(1, maxLen) end
  359.   t.write(s)
  360. end
  361.  
  362. local function hr(t, y, fg, bg)
  363.   local w = t.getSize()
  364.   fill(t, 1, y, w, 1, " ", fg, bg)
  365. end
  366.  
  367. local function bar(t, x, y, w, pct, label, colFill, colEmpty, colText, bg)
  368.   pct = pct or 0
  369.   if w < 6 then
  370.     writeAt(t, x, y, tostring(math.floor(pct+0.5)) .. "%", colText, bg)
  371.     return
  372.   end
  373.   local inner = math.max(1, w - 2)
  374.   local filled = clampInt(math.floor((pct / 100) * inner + 0.5), 0, inner)
  375.  
  376.   writeAt(t, x, y, "[", colText, bg)
  377.   writeAt(t, x + w - 1, y, "]", colText, bg)
  378.  
  379.   if filled > 0 then
  380.     writeAt(t, x + 1, y, string.rep("=", filled), colFill, bg)
  381.   end
  382.   if inner - filled > 0 then
  383.     writeAt(t, x + 1 + filled, y, string.rep("-", inner - filled), colEmpty, bg)
  384.   end
  385.  
  386.   if label and label ~= "" then
  387.     local txt = label
  388.     if #txt > inner then txt = txt:sub(1, inner) end
  389.     local lx = x + 1 + math.floor((inner - #txt) / 2)
  390.     writeAt(t, lx, y, txt, colText, bg)
  391.   end
  392. end
  393.  
  394. local function pill(t, x, y, text, active, fgActive, bgActive, fgInactive, bgInactive)
  395.   if active then
  396.     writeAt(t, x, y, " " .. text .. " ", fgActive, bgActive)
  397.   else
  398.     writeAt(t, x, y, " " .. text .. " ", fgInactive, bgInactive)
  399.   end
  400.   return x + #text + 2
  401. end
  402.  
  403. local function drawHeaderAndTabs(t, state)
  404.   local w, h = t.getSize()
  405.  
  406.   local BG = HAS_COLOR and colors.black or nil
  407.   local TOP = HAS_COLOR and colors.blue or BG
  408.   local TXT = HAS_COLOR and colors.white or nil
  409.   local MUTED = HAS_COLOR and colors.lightGray or nil
  410.  
  411.   fill(t, 1, 1, w, 1, " ", TXT, TOP)
  412.   local title = (state.reactorName or "Reactor Control")
  413.   local right = "V" .. tostring(state.version)
  414.   local head = " " .. title
  415.   if #head + #right + 2 <= w then
  416.     head = head .. string.rep(" ", w - (#head + #right) - 1) .. right
  417.   end
  418.   writeAt(t, 1, 1, head, TXT, TOP)
  419.  
  420.   fill(t, 1, 2, w, 1, " ", TXT, BG)
  421.   local x = 2
  422.   local tabBG = HAS_COLOR and colors.gray or BG
  423.   local tabFG = HAS_COLOR and colors.white or nil
  424.   local offBG = BG
  425.   local offFG = MUTED
  426.  
  427.   x = pill(t, x, 2, "1 Reactor",      UI.tab == 1, tabFG, tabBG, offFG, offBG) + 1
  428.   x = pill(t, x, 2, "2 Status",       UI.tab == 2, tabFG, tabBG, offFG, offBG) + 1
  429.   x = pill(t, x, 2, "3 Temperatur", UI.tab == 3, tabFG, tabBG, offFG, offBG) + 1
  430.   x = pill(t, x, 2, "4 Steuerung",    UI.tab == 4, tabFG, tabBG, offFG, offBG)
  431.  
  432.   hr(t, 3, TXT, HAS_COLOR and colors.gray or BG)
  433.   return 4
  434. end
  435.  
  436. local function drawFooter(t, state)
  437.   local w, h = t.getSize()
  438.   local BG = HAS_COLOR and colors.black or nil
  439.   local FOOT = HAS_COLOR and colors.gray or BG
  440.   local TXT = HAS_COLOR and colors.white or nil
  441.   local MUTED = HAS_COLOR and colors.lightGray or nil
  442.  
  443.   fill(t, 1, h - 1, w, 2, " ", TXT, FOOT)
  444.   writeAt(t, 2, h - 1, "Tabs: 1-4 | Q quit | S SCRAM | R rename | U update", TXT, FOOT)
  445.   local hint = ("ID " .. tostring(os.getComputerID()) .. (os.getComputerLabel() and (" | " .. os.getComputerLabel()) or ""))
  446.   writeAt(t, w - #hint - 1, h, hint, MUTED, FOOT)
  447. end
  448.  
  449. local function pageReactor(t, state, x, y, w, h)
  450.   local BG = HAS_COLOR and colors.black or nil
  451.   local TXT = HAS_COLOR and colors.white or nil
  452.   local MUTED = HAS_COLOR and colors.lightGray or nil
  453.   local GOOD = HAS_COLOR and colors.lime or nil
  454.   local WARN = HAS_COLOR and colors.orange or nil
  455.   local BAD  = HAS_COLOR and colors.red or nil
  456.   local CYAN = HAS_COLOR and colors.cyan or nil
  457.  
  458.   local barW = math.max(10, w - 4)
  459.   local bufferColor = (state.bufferPct >= 80) and GOOD or ((state.bufferPct >= 40) and WARN or BAD)
  460.   local fuelColor   = (state.fuelPct   >= 10) and GOOD or ((state.fuelPct   >= 4)  and WARN or BAD)
  461.  
  462.   writeAt(t, x, y, "Energie & Fuel", CYAN, BG); y = y + 2
  463.   bar(t, x, y, barW, state.bufferPct, "Buffer " .. state.bufferTxt, bufferColor, MUTED, TXT, BG); y = y + 2
  464.   bar(t, x, y, barW, state.fuelPct,   "Fuel   " .. state.fuelTxt,   fuelColor,   MUTED, TXT, BG); y = y + 2
  465.  
  466.   writeAt(t, x, y, "RF/tick:", CYAN, BG)
  467.   writeAt(t, x + 12, y, state.rfTxt, TXT, BG); y = y + 1
  468.  
  469.   writeAt(t, x, y, "Energy:", CYAN, BG)
  470.   writeAt(t, x + 12, y, state.energyTxt, TXT, BG); y = y + 1
  471.  
  472.   if state.capMethod and state.capMethod ~= "" then
  473.     writeAt(t, x, y, "CapMeth:", CYAN, BG)
  474.     writeAt(t, x + 12, y, tostring(state.capMethod), MUTED, BG)
  475.   end
  476. end
  477.  
  478. local function pageStatus(t, state, x, y, w, h)
  479.   local BG = HAS_COLOR and colors.black or nil
  480.   local TXT = HAS_COLOR and colors.white or nil
  481.   local MUTED = HAS_COLOR and colors.lightGray or nil
  482.   local GOOD = HAS_COLOR and colors.lime or nil
  483.   local WARN = HAS_COLOR and colors.orange or nil
  484.   local BAD  = HAS_COLOR and colors.red or nil
  485.   local CYAN = HAS_COLOR and colors.cyan or nil
  486.  
  487.   local function badge(label, ok, yy, warnIfFalse)
  488.     local c
  489.     if ok then
  490.       c = GOOD
  491.     else
  492.       c = warnIfFalse and BAD or WARN
  493.     end
  494.     local sym = ok and "[OK]" or "[..]"
  495.     writeAt(t, x, yy, label, CYAN, BG)
  496.     writeAt(t, x + 18, yy, sym, c, BG)
  497.   end
  498.  
  499.   writeAt(t, x, y, "Systemstatus", CYAN, BG); y = y + 2
  500.   badge("Reaktor Active", state.active, y); y = y + 1
  501.   badge("RS Gate (15)", state.rsEnabled, y, true); y = y + 1
  502.  
  503.   local wsOK = (state.wsState == "connected")
  504.   badge("WebSocket", wsOK, y); y = y + 1
  505.   writeAt(t, x, y, "WS:", CYAN, BG)
  506.   writeAt(t, x + 18, y, state.wsState, MUTED, BG); y = y + 2
  507.  
  508.   local statusColor = (state.status:find("SCRAM")) and BAD or (state.status == "OK" and GOOD or WARN)
  509.   writeAt(t, x, y, "Controller:", CYAN, BG); y = y + 1
  510.   writeAt(t, x, y, tostring(state.status), statusColor, BG)
  511. end
  512.  
  513. local function pageTemps(t, state, x, y, w, h)
  514.   local BG = HAS_COLOR and colors.black or nil
  515.   local TXT = HAS_COLOR and colors.white or nil
  516.   local GOOD = HAS_COLOR and colors.lime or nil
  517.   local WARN = HAS_COLOR and colors.orange or nil
  518.   local BAD  = HAS_COLOR and colors.red or nil
  519.   local CYAN = HAS_COLOR and colors.cyan or nil
  520.  
  521.   local function tempLine(label, value, max, yy)
  522.     local c = (value <= max * 0.75) and GOOD or ((value <= max * 0.92) and WARN or BAD)
  523.     writeAt(t, x, yy, label, CYAN, BG)
  524.     writeAt(t, x + 18, yy, string.format("%4d C", value), c, BG)
  525.     writeAt(t, x + 28, yy, ("(max " .. tostring(max) .. ")"), TXT, BG)
  526.   end
  527.  
  528.   writeAt(t, x, y, "Temperatur", CYAN, BG); y = y + 2
  529.   tempLine("Fuel Temp",   state.fuelTemp,   state.maxFuelTemp,   y); y = y + 1
  530.   tempLine("Casing Temp", state.casingTemp, state.maxCasingTemp, y); y = y + 2
  531.   writeAt(t, x, y, "Hinweis: SCRAM bei Grenzwertüberschreitung.", TXT, BG)
  532. end
  533.  
  534. local function pageControl(t, state, x, y, w, h)
  535.   local BG = HAS_COLOR and colors.black or nil
  536.   local TXT = HAS_COLOR and colors.white or nil
  537.   local MUTED = HAS_COLOR and colors.lightGray or nil
  538.   local GOOD = HAS_COLOR and colors.lime or nil
  539.   local WARN = HAS_COLOR and colors.orange or nil
  540.   local BAD  = HAS_COLOR and colors.red or nil
  541.   local CYAN = HAS_COLOR and colors.cyan or nil
  542.  
  543.   local barW = math.max(10, w - 4)
  544.   local rodColor = (state.rod <= 20) and GOOD or ((state.rod <= 70) and WARN or BAD)
  545.  
  546.   writeAt(t, x, y, "Steuerung", CYAN, BG); y = y + 2
  547.   bar(t, x, y, barW, state.rod, "Rod " .. state.rodTxt, rodColor, MUTED, TXT, BG); y = y + 2
  548.  
  549.   writeAt(t, x, y, "TargetBuffer:", CYAN, BG)
  550.   writeAt(t, x + 18, y, tostring(state.targetBuffer) .. "%", TXT, BG); y = y + 1
  551.  
  552.   writeAt(t, x, y, "Hysterese:", CYAN, BG)
  553.   writeAt(t, x + 18, y, tostring(state.hysteresis) .. "%", TXT, BG); y = y + 1
  554.  
  555.   writeAt(t, x, y, "Start/Stop:", CYAN, BG)
  556.   writeAt(t, x + 18, y, tostring(state.startAt) .. "% / " .. tostring(state.stopAt) .. "%", TXT, BG); y = y + 2
  557.  
  558.   writeAt(t, x, y, "Rod: 0=raus, 100=rein", MUTED, BG)
  559. end
  560.  
  561. function UI.render(t, state)
  562.   local w, h = t.getSize()
  563.   local BG = HAS_COLOR and colors.black or nil
  564.   local TXT = HAS_COLOR and colors.white or nil
  565.  
  566.   setBG(t, BG); setFG(t, TXT)
  567.   t.clear()
  568.  
  569.   local contentY = drawHeaderAndTabs(t, state)
  570.  
  571.   local top = contentY
  572.   local bottom = h - 2
  573.   local contentH = bottom - top + 1
  574.  
  575.   if contentH < 4 then
  576.     writeAt(t, 1, top, "Monitor zu klein :(", TXT, BG)
  577.     drawFooter(t, state)
  578.     return
  579.   end
  580.  
  581.   local padX = 2
  582.   local x = padX
  583.   local y = top + 1
  584.   local cw = w - (padX * 2) + 1
  585.  
  586.   fill(t, 1, top, w, contentH, " ", TXT, BG)
  587.  
  588.   if UI.tab == 1 then
  589.     pageReactor(t, state, x, y, cw, contentH)
  590.   elseif UI.tab == 2 then
  591.     pageStatus(t, state, x, y, cw, contentH)
  592.   elseif UI.tab == 3 then
  593.     pageTemps(t, state, x, y, cw, contentH)
  594.   else
  595.     pageControl(t, state, x, y, cw, contentH)
  596.   end
  597.  
  598.   drawFooter(t, state)
  599. end
  600.  
  601. -------------------------
  602. -- Hauptlogik
  603. -------------------------
  604. local reactor, rType = findReactor()
  605. if not reactor then
  606.     print("Kein Reaktor-Peripheral gefunden.")
  607.     print("Tipps:")
  608.     print("- Computer direkt an den Reaktor stellen oder per Modem verbinden")
  609.     print("- Prüfen: peripheral.getNames()")
  610.     return
  611. end
  612.  
  613. local monitor = CFG.useMonitor and findMonitor() or nil
  614.  
  615. local reactorName = loadReactorName("Reactor")
  616. if reactorName == "Reactor" then
  617.     reactorName = loadReactorName("Reactor Control")
  618. end
  619.  
  620. local scrammed = false
  621. local lastRod = nil
  622. local tickCounter = 0
  623.  
  624. local function scram(reason)
  625.     scrammed = true
  626.     pcall(
  627.         function()
  628.             reactor.setActive(false)
  629.         end
  630.     )
  631.     pcall(
  632.         function()
  633.             setAllRods(reactor, 100)
  634.         end
  635.     )
  636.     return reason or "SCRAM"
  637. end
  638.  
  639. local function controlStep()
  640.     local active = getReactorActive(reactor, lastActiveKnown)
  641.     lastActiveKnown = active
  642.  
  643.     local eStored =
  644.         safeCall(
  645.         function()
  646.             return reactor.getEnergyStored()
  647.         end,
  648.         0
  649.     )
  650.     local eMax, capMethod = getEnergyCapacity(reactor)
  651.  
  652.     local bufferPct
  653.     if not eMax then
  654.         bufferPct = 0
  655.     else
  656.         bufferPct = (eStored / eMax) * 100
  657.     end
  658.     bufferPct = clamp(bufferPct, 0, 100)
  659.  
  660.     local fuelAmt =
  661.         safeCall(
  662.         function()
  663.             return reactor.getFuelAmount()
  664.         end,
  665.         0
  666.     )
  667.     local fuelMax =
  668.         safeCall(
  669.         function()
  670.             return reactor.getFuelAmountMax()
  671.         end,
  672.         1
  673.     )
  674.     local fuelPct = (fuelAmt / math.max(fuelMax, 1)) * 100
  675.  
  676.     local fuelTemp =
  677.         safeCall(
  678.         function()
  679.             return reactor.getFuelTemperature()
  680.         end,
  681.         0
  682.     )
  683.     local casingTemp =
  684.         safeCall(
  685.         function()
  686.             return reactor.getCasingTemperature()
  687.         end,
  688.         0
  689.     )
  690.  
  691.     local rfTick =
  692.         safeCall(
  693.         function()
  694.             return reactor.getEnergyProducedLastTick()
  695.         end,
  696.         0
  697.     )
  698.     local rodAvg =
  699.         safeCall(
  700.         function()
  701.             return getAvgRodLevel(reactor)
  702.         end,
  703.         0
  704.     )
  705.  
  706.     -- Safety
  707.     if fuelPct < CFG.minFuelPercent then
  708.         return bufferPct, fuelPct, fuelTemp, casingTemp, rfTick, rodAvg, active, scram("SCRAM: Fuel zu niedrig"), eStored, eMax, capMethod
  709.     end
  710.     if fuelTemp > CFG.maxFuelTemp then
  711.         return bufferPct, fuelPct, fuelTemp, casingTemp, rfTick, rodAvg, active, scram("SCRAM: FuelTemp zu hoch"), eStored, eMax, capMethod
  712.     end
  713.     if casingTemp > CFG.maxCasingTemp then
  714.         return bufferPct, fuelPct, fuelTemp, casingTemp, rfTick, rodAvg, active, scram("SCRAM: CasingTemp zu hoch"), eStored, eMax, capMethod
  715.     end
  716.  
  717.     scrammed = false
  718.  
  719.     -- RS Gate: nur wenn analog 15 an top oder bottom anliegt
  720.     local enabled = rsIsEnabled()
  721.     if not enabled then
  722.         pcall(function() reactor.setActive(false) end)
  723.         active = false
  724.         lastActiveKnown = false
  725.  
  726.         if CFG.rsGateScramRods then
  727.             pcall(function() setAllRods(reactor, 100) end)
  728.             lastRod = 100
  729.         end
  730.  
  731.         return bufferPct, fuelPct, fuelTemp, casingTemp, rfTick, rodAvg, active, "OFF (kein RS 15 top/bottom)", eStored, eMax, capMethod
  732.     end
  733.  
  734.     -- Start/Stop
  735.     if bufferPct <= CFG.startAt and not active then
  736.         pcall(
  737.             function()
  738.                 reactor.setActive(true)
  739.             end
  740.         )
  741.         active = true
  742.         lastActiveKnown = true
  743.     elseif bufferPct >= CFG.stopAt then
  744.         pcall(
  745.             function()
  746.                 reactor.setActive(false)
  747.             end
  748.         )
  749.         active = false
  750.         lastActiveKnown = false
  751.     end
  752.  
  753.     if not active then
  754.         return bufferPct, fuelPct, fuelTemp, casingTemp, rfTick, rodAvg, active, "OFF (Puffer hoch genug)", eStored, eMax, capMethod
  755.     end
  756.  
  757.     -- Hysterese
  758.     local low = CFG.targetBuffer - CFG.hysteresis
  759.     local high = CFG.targetBuffer + CFG.hysteresis
  760.  
  761.     local newRod = rodAvg
  762.  
  763.     if bufferPct < low then
  764.         local err = (low - bufferPct)
  765.         local step = clamp(math.floor(err * 0.6) + 1, 1, 10)
  766.         newRod = rodAvg - step
  767.     elseif bufferPct > high then
  768.         local err = (bufferPct - high)
  769.         local step = clamp(math.floor(err * 0.6) + 1, 1, 10)
  770.         newRod = rodAvg + step
  771.     end
  772.  
  773.     newRod = clamp(newRod, CFG.minRod, CFG.maxRod)
  774.  
  775.     if lastRod == nil or math.abs(newRod - lastRod) >= 1 then
  776.         pcall(
  777.             function()
  778.                 setAllRods(reactor, newRod)
  779.             end
  780.         )
  781.         lastRod = newRod
  782.     end
  783.  
  784.     return bufferPct, fuelPct, fuelTemp, casingTemp, rfTick, newRod, active, "OK", eStored, eMax, capMethod
  785. end
  786.  
  787. -- UI Loop
  788. local function uiLoop()
  789.     while true do
  790.         tickCounter = tickCounter + 1
  791.  
  792.         if CFG.wsEnabled and not ws then
  793.             wsConnect()
  794.         end
  795.  
  796.         local bufferPct, fuelPct, fuelTemp, casingTemp, rfTick, rod, active, status, eStored, eMax, capMethod =
  797.             controlStep()
  798.  
  799.         if CFG.wsEnabled and ws and (tickCounter % math.max(CFG.wsSendEveryTicks, 1) == 0) then
  800.             wsSend(
  801.                 {
  802.                     type = "reactor_telemetry_single",
  803.                     computerId = os.getComputerID(),
  804.                     label = os.getComputerLabel(),
  805.                     reactorName = reactorName,
  806.                     reactorType = rType,
  807.                     timeUtcMs = (os.epoch and os.epoch("utc")) or nil,
  808.                     active = active,
  809.                     status = status,
  810.                     bufferPct = bufferPct,
  811.                     fuelPct = fuelPct,
  812.                     fuelTemp = fuelTemp,
  813.                     casingTemp = casingTemp,
  814.                     rod = rod,
  815.                     rfTick = rfTick,
  816.                     energyStored = eStored,
  817.                     energyMax = eMax,
  818.                     energyCapMethod = capMethod
  819.                 }
  820.             )
  821.         end
  822.  
  823.         local state = {
  824.           version = version,
  825.           reactorName = reactorName,
  826.           active = active,
  827.           status = tostring(status),
  828.           rsEnabled = rsIsEnabled(),
  829.  
  830.           bufferPct = bufferPct,
  831.           fuelPct = fuelPct,
  832.           fuelTemp = round(fuelTemp),
  833.           casingTemp = round(casingTemp),
  834.  
  835.           rod = round(rod),
  836.           rfTick = round(rfTick),
  837.  
  838.           energyStored = eStored,
  839.           energyMax = eMax,
  840.           capMethod = capMethod,
  841.  
  842.           targetBuffer = CFG.targetBuffer,
  843.           startAt = CFG.startAt,
  844.           stopAt = CFG.stopAt,
  845.           maxFuelTemp = CFG.maxFuelTemp,
  846.           maxCasingTemp = CFG.maxCasingTemp,
  847.  
  848.           bufferTxt = fmtPercent(bufferPct),
  849.           fuelTxt = fmtPercent(fuelPct),
  850.           rodTxt = tostring(round(rod)) .. "%",
  851.           rfTxt = tostring(round(rfTick)),
  852.           energyTxt = (eMax and (tostring(math.floor(eStored)) .. "/" .. tostring(math.floor(eMax))) or tostring(math.floor(eStored))),
  853.           wsState = (CFG.wsEnabled and (ws and "connected" or "down") or "disabled"),
  854.  
  855.           hysteresis = CFG.hysteresis,
  856.         }
  857.  
  858.         UI.render(term, state)
  859.         if monitor then UI.render(monitor, state) end
  860.  
  861.         local timer = os.startTimer(CFG.tickSeconds)
  862.         while true do
  863.             local ev, p1 = os.pullEvent()
  864.             if ev == "timer" and p1 == timer then
  865.                 break
  866.             elseif ev == "websocket_closed" or ev == "websocket_failure" then
  867.                 wsClose()
  868.                 wsLastErr = (ev == "websocket_failure") and tostring(p1) or "websocket_closed"
  869.             elseif ev == "char" then
  870.                 local c = string.lower(p1)
  871.                 if c == "\t" then
  872.                   UI.tab = (UI.tab % 4) + 1
  873.                 elseif c == "1" then UI.tab = 1
  874.                 elseif c == "2" then UI.tab = 2
  875.                 elseif c == "3" then UI.tab = 3
  876.                 elseif c == "4" then UI.tab = 4
  877.                 elseif c == "q" then
  878.                     term.setCursorPos(1, 1)
  879.                     term.clear()
  880.                     print("Beendet.")
  881.                     wsClose()
  882.                     return
  883.                 elseif c == "s" then
  884.                     scram("SCRAM: Manuell")
  885.                 elseif c == "r" then
  886.                     term.setCursorPos(1, 1)
  887.                     term.clear()
  888.                     if monitor then
  889.                         monitor.setCursorPos(1, 1)
  890.                         monitor.clear()
  891.                     end
  892.  
  893.                     print("Neuer Reaktor-Name (leer = abbrechen):")
  894.                     write("> ")
  895.                     local newName = read()
  896.  
  897.                     newName = (newName and newName:gsub("^%s+", ""):gsub("%s+$", "")) or ""
  898.                     if newName ~= "" then
  899.                         reactorName = newName
  900.                         saveReactorName(reactorName)
  901.                     end
  902.                 elseif c == "u" then
  903.                     term.setCursorPos(1, 1)
  904.                     term.clear()
  905.                     if monitor then
  906.                         monitor.setCursorPos(1, 1)
  907.                         monitor.clear()
  908.                     end
  909.  
  910.                     print("Update wird geladen (Pastebin: " .. tostring(PASTEBIN_ID) .. ") ...")
  911.                     local ok, msg = selfUpdate()
  912.                     print(msg)
  913.  
  914.                     if ok then
  915.                         print("Neustart...")
  916.                         wsClose()
  917.                         os.sleep(1)
  918.                         os.reboot()
  919.                     else
  920.                         print("Beliebige Taste zum Fortfahren...")
  921.                         os.pullEvent("char")
  922.                     end
  923.                 end
  924.             end
  925.         end
  926.     end
  927. end
  928.  
  929. uiLoop()
  930.  
Advertisement
Add Comment
Please, Sign In to add comment