zodiak707

Reactor Control V1.3

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