kovakovi2000

fission_agent

Oct 31st, 2025 (edited)
604
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 8.05 KB | None | 0 0
  1. -- fission_agent.lua
  2. -- Mekanism fission agent
  3. -- HARD: SCRAM >= 1400 C
  4. -- HARD: do NOT start if temp >= 1000 C
  5. -- persists desired run/burn in ldb3
  6. -- comms: rnc3
  7. -- remote cmd: on/off/setBurn/setSoftScram/restart
  8.  
  9. local HAVE_LDB = pcall(os.loadAPI, "ldb3")
  10. os.loadAPI("rnc3")
  11.  
  12. local PROG  = "fission-agent"
  13. local MY_ID = rnc3.getId(PROG)
  14. print("agent id:", MY_ID)
  15.  
  16. -- constants -------------------------------------------------
  17. local HEARTBEAT_EVERY    = 1.0
  18. local CONTROLLER_TIMEOUT = 5.0
  19.  
  20. local HARD_SCRAM_TEMP = 1400
  21. local SAFE_START_TEMP = 1000
  22.  
  23. local SOFT_MIN_COOLANT = 5
  24. local SOFT_MAX_WASTE   = 95
  25. local SOFT_MAX_HOT     = 95
  26.  
  27. local DB_NAME = "fission-agent"   -- for ldb3
  28.  
  29. -- reactor ---------------------------------------------------
  30. local reactor = peripheral.find("fissionReactorLogicAdapter")
  31. if not reactor then
  32.   error("fission_agent: no fissionReactorLogicAdapter found")
  33. end
  34.  
  35. -- state -----------------------------------------------------
  36. local controllerId       = nil
  37. local controllerLastSeen = 0
  38. local softAutoScram      = true
  39. local lastHeartbeat      = 0
  40.  
  41. -- this is what we SAVE
  42. -- desired.run  = true/false
  43. -- desired.burn = number
  44. local desired = {
  45.   run  = false,
  46.   burn = 0,
  47. }
  48.  
  49. -- load persisted desired
  50. if HAVE_LDB then
  51.   local d = ldb3.get(DB_NAME, "desired")
  52.   if type(d) == "table" then
  53.     desired = d
  54.   end
  55. end
  56.  
  57. local function saveDesired()
  58.   if HAVE_LDB then
  59.     ldb3.set(DB_NAME, "desired", desired)
  60.   end
  61. end
  62.  
  63. -- helpers ---------------------------------------------------
  64. local function safe(fn, ...)
  65.   if not fn then return nil end
  66.   local ok, r = pcall(fn, ...)
  67.   if ok then return r end
  68.   return nil
  69. end
  70.  
  71. local function scram()
  72.   if reactor.scram then reactor.scram() end
  73.   if reactor.setBurnRate then reactor.setBurnRate(0) end
  74. end
  75.  
  76. local function canStartNow()
  77.   local t = safe(reactor.getTemperature) or 0
  78.   return t < SAFE_START_TEMP
  79. end
  80.  
  81. local function startFromDesired()
  82.   if desired.run and canStartNow() then
  83.     if reactor.activate then reactor.activate() end
  84.     if reactor.setBurnRate then
  85.       local maxB = safe(reactor.getMaxBurnRate) or desired.burn or 0
  86.       local b = desired.burn or 0
  87.       if b > maxB then b = maxB end
  88.       reactor.setBurnRate(b)
  89.     end
  90.     return true
  91.   end
  92.   return false
  93. end
  94.  
  95. local function setBurn(b)
  96.   b = tonumber(b) or 0
  97.   local t = safe(reactor.getTemperature) or 0
  98.   if t >= SAFE_START_TEMP and b > 0 then
  99.     return false, "temp>=1000"
  100.   end
  101.   local maxB = safe(reactor.getMaxBurnRate) or b
  102.   if b > maxB then b = maxB end
  103.   if b < 0 then b = 0 end
  104.   if reactor.setBurnRate then reactor.setBurnRate(b) end
  105.   desired.burn = b
  106.   if b > 0 then
  107.     desired.run = true
  108.   end
  109.   saveDesired()
  110.   return true, b
  111. end
  112.  
  113. -- telemetry: ONLY your methods --------------------------------
  114. local function readTelemetry()
  115.   local temp       = safe(reactor.getTemperature) or 0
  116.   local burn       = safe(reactor.getBurnRate) or 0
  117.   local actualBurn = safe(reactor.getActualBurnRate) or burn
  118.   local coolPct    = safe(reactor.getCoolantFilledPercentage) or 0
  119.   local hotPct     = safe(reactor.getHeatedCoolantFilledPercentage) or 0
  120.   local fuelPct    = safe(reactor.getFuelFilledPercentage) or 0
  121.   local wastePct   = safe(reactor.getWasteFilledPercentage) or 0
  122.   local heating    = safe(reactor.getHeatingRate) or 0
  123.   local rawStatus  = safe(reactor.getStatus)
  124.  
  125.   -- normalize: your getStatus was weird, so we use burn as truth
  126.   local status
  127.   if burn and burn > 0 then
  128.     status = "active"
  129.   else
  130.     status = "stopped"
  131.   end
  132.   -- if rawStatus was a nice string, keep it in case you want to log
  133.   return {
  134.     id               = MY_ID,
  135.     temp             = temp,
  136.     burn             = burn,
  137.     actualBurn       = actualBurn,
  138.     coolantPct       = coolPct,
  139.     heatedCoolantPct = hotPct,
  140.     fuelPct          = fuelPct,
  141.     wastePct         = wastePct,
  142.     heatingRate      = heating,
  143.     status           = status,
  144.     rawStatus        = rawStatus,
  145.     softAutoScram    = softAutoScram,
  146.     desiredRun       = desired.run,
  147.     desiredBurn      = desired.burn,
  148.     time             = os.epoch("utc"),
  149.     label            = os.getComputerLabel(),
  150.   }
  151. end
  152.  
  153. local function isSoftUnsafe(t)
  154.   if not softAutoScram then return false end
  155.   if t.coolantPct and t.coolantPct > 0 and t.coolantPct < SOFT_MIN_COOLANT then return true end
  156.   if t.wastePct   and t.wastePct   >= SOFT_MAX_WASTE   then return true end
  157.   if t.heatedCoolantPct and t.heatedCoolantPct >= SOFT_MAX_HOT then return true end
  158.   return false
  159. end
  160.  
  161. -- handle incoming pkt -------------------------------------------
  162. local function handlePacket(pkt)
  163.   if rnc3.autoPong(PROG, pkt) then return end
  164.   local b = pkt.body or {}
  165.   local t = b.type
  166.  
  167.   if t == "discover" or t == "controller-hello" then
  168.     controllerId       = pkt.from
  169.     controllerLastSeen = os.clock()
  170.     if b.disableSoftScram ~= nil then
  171.       softAutoScram = not b.disableSoftScram
  172.     end
  173.     rnc3.send(PROG, pkt.from, {
  174.       type  = "reactor-hello",
  175.       id    = MY_ID,
  176.       role  = "fission-reactor",
  177.       telem = readTelemetry(),
  178.       ver   = "fission_agent/1.4"
  179.     })
  180.     return
  181.   end
  182.  
  183.   if t == "controller-hb" then
  184.     controllerId       = pkt.from
  185.     controllerLastSeen = os.clock()
  186.     if b.disableSoftScram ~= nil then
  187.       softAutoScram = not b.disableSoftScram
  188.     end
  189.     return
  190.   end
  191.  
  192.   if t == "cmd" then
  193.     controllerId       = pkt.from
  194.     controllerLastSeen = os.clock()
  195.     local cmd = b.cmd
  196.  
  197.     if cmd == "on" then
  198.       desired.run = true
  199.       -- don't force start if too hot; main loop will do it
  200.       saveDesired()
  201.       startFromDesired()
  202.       rnc3.send(PROG, pkt.from, {type="ack", cmd="on", ok=true})
  203.  
  204.     elseif cmd == "off" then
  205.       desired.run = false
  206.       saveDesired()
  207.       scram()
  208.       rnc3.send(PROG, pkt.from, {type="ack", cmd="off", ok=true})
  209.  
  210.     elseif cmd == "setBurn" and b.value then
  211.       local ok, valOrReason = setBurn(b.value)
  212.       rnc3.send(PROG, pkt.from, {
  213.         type="ack", cmd="setBurn", ok=ok,
  214.         burn = ok and valOrReason or nil,
  215.         reason = ok and nil or valOrReason
  216.       })
  217.  
  218.     elseif cmd == "setSoftScram" and b.value ~= nil then
  219.       softAutoScram = b.value and true or false
  220.       rnc3.send(PROG, pkt.from, {type="ack", cmd="setSoftScram", ok=true})
  221.  
  222.     elseif cmd == "restart" or cmd == "reboot" then
  223.       -- SAVE CURRENT DESIRED, then reboot
  224.       saveDesired()
  225.       scram()
  226.       rnc3.send(PROG, pkt.from, {type="ack", cmd=cmd, ok=true})
  227.       sleep(0.1)
  228.       os.reboot()
  229.     end
  230.   end
  231. end
  232.  
  233. -- try to honor desired right after boot ---------------------------
  234. startFromDesired()
  235.  
  236. -- main loop -------------------------------------------------------
  237. while true do
  238.   local now = os.clock()
  239.  
  240.   -- controller timeout -> re-enable soft scram
  241.   if controllerId and (now - controllerLastSeen) > CONTROLLER_TIMEOUT then
  242.     controllerId  = nil
  243.     softAutoScram = true
  244.   end
  245.  
  246.   local telem = readTelemetry()
  247.  
  248.   -- HARD safety
  249.   if telem.temp >= HARD_SCRAM_TEMP then
  250.     if telem.status ~= "stopped" then
  251.       scram()
  252.       -- once HARD fired, we should not auto-start -> desired.run = false
  253.       desired.run = false
  254.       saveDesired()
  255.     end
  256.   else
  257.     -- SOFT safety
  258.     if isSoftUnsafe(telem) and telem.status ~= "stopped" then
  259.       scram()
  260.       -- also clear desired.run so we don't flap
  261.       desired.run = false
  262.       saveDesired()
  263.     end
  264.   end
  265.  
  266.   -- try to start later if desired.run is true but we couldn't start earlier
  267.   if desired.run then
  268.     if telem.status == "stopped" and canStartNow() then
  269.       startFromDesired()
  270.     end
  271.   end
  272.  
  273.   -- heartbeat
  274.   if (now - lastHeartbeat) >= HEARTBEAT_EVERY then
  275.     lastHeartbeat = now
  276.     local hb = {
  277.       type  = "hb",
  278.       id    = MY_ID,
  279.       role  = "fission-reactor",
  280.       telem = telem
  281.     }
  282.     if controllerId then
  283.       rnc3.send(PROG, controllerId, hb)
  284.     else
  285.       rnc3.broadcast(PROG, hb)
  286.     end
  287.   end
  288.  
  289.   local pkt = rnc3.recv(PROG, 0.2)
  290.   if pkt then handlePacket(pkt) end
  291. end
  292.  
Advertisement
Add Comment
Please, Sign In to add comment