-- fission_agent.lua
-- per-reactor agent for Mekanism fission (CC:T)
-- needs: ldb2, rnc2
-- hard safety: SCRAM >= 1400 C, never start >= 1000 C
os.loadAPI("ldb2")
os.loadAPI("rnc2")
local PROG = "fission-agent"
local MY_ID = rnc2.getId(PROG)
print("my id:", MY_ID)
-- timings
local HEARTBEAT_EVERY = 1.0 -- seconds
local CONTROLLER_TIMEOUT = 5.0 -- seconds
-- temps (hard rules)
local HARD_SCRAM_TEMP = 1400 -- >= this -> scram, unoverrideable
local SAFE_START_TEMP = 1000 -- must be lower than this to activate/start
-- "soft" safety defaults (controller may disable these)
local SOFT_MIN_COOLANT = 5 -- % if coolant below -> soft scram
local SOFT_MAX_WASTE = 95 -- % if waste above -> soft scram
local SOFT_MAX_HOT = 95 -- % if heated-coolant above -> soft scram
-- find the fission reactor logic adapter
local reactor = peripheral.find("fissionReactorLogicAdapter")
if not reactor then
error("fission_agent: no fissionReactorLogicAdapter found")
end
-- state
local controllerId = nil
local controllerLastSeen = 0
local softAutoScram = true -- controller can toggle this, but NOT the 1400C rule
local lastHeartbeat = 0
-- helpers -------------------------------------------------
local function safe(fn, ...)
if not fn then return nil end
local ok, res = pcall(fn, ...)
if ok then return res else return nil end
end
local function scram()
-- fission has scram()
if reactor.scram then
reactor.scram()
end
-- also zero burn to be sure
if reactor.setBurnRate then
reactor.setBurnRate(0)
end
end
local function canStartNow()
local temp = safe(reactor.getTemperature) or 0
return temp < SAFE_START_TEMP
end
local function start()
if not canStartNow() then
-- too hot, ignore start
return false
end
if reactor.activate then
reactor.activate()
end
return true
end
local function setBurn(b)
b = tonumber(b) or 0
if not canStartNow() then
-- refuse to set high burn while hot, but do allow 0
if b > 0 then
return false, "too hot"
end
end
local maxB = safe(reactor.getMaxBurnRate) or b
if b > maxB then b = maxB end
if b < 0 then b = 0 end
if reactor.setBurnRate then
reactor.setBurnRate(b)
end
return true, b
end
-- telemetry using ONLY your methods -----------------------
local function readTelemetry()
local temp = safe(reactor.getTemperature) or 0
local burn = safe(reactor.getBurnRate) or 0
local actualBurn = safe(reactor.getActualBurnRate) or burn
local coolPct = safe(reactor.getCoolantFilledPercentage) or 0
local hotPct = safe(reactor.getHeatedCoolantFilledPercentage) or 0
local fuelPct = safe(reactor.getFuelFilledPercentage) or 0
local wastePct = safe(reactor.getWasteFilledPercentage) or 0
local heating = safe(reactor.getHeatingRate) or 0
local status = safe(reactor.getStatus) or "unknown"
local formed = safe(reactor.isFormed) or false
local forced = safe(reactor.isForceDisabled) or false
return {
id = MY_ID,
temp = temp,
burn = burn,
actualBurn = actualBurn,
coolantPct = coolPct,
heatedCoolantPct = hotPct,
fuelPct = fuelPct,
wastePct = wastePct,
heatingRate = heating,
status = status,
formed = formed,
forceDisabled = forced,
softAutoScram = softAutoScram,
time = os.epoch("utc"),
}
end
-- decide soft-unsafe from telemetry
local function isSoftUnsafe(telem)
if not softAutoScram then return false end
if telem.coolantPct and telem.coolantPct > 0 and telem.coolantPct < SOFT_MIN_COOLANT then
return true
end
if telem.wastePct and telem.wastePct >= SOFT_MAX_WASTE then
return true
end
if telem.heatedCoolantPct and telem.heatedCoolantPct >= SOFT_MAX_HOT then
return true
end
return false
end
-- message handling -----------------------------------------
local function handleMessage(pkt)
if rnc2.autoPong(PROG, pkt) then return end
local b = pkt.body or {}
local t = b.type
-- controller discovery
if t == "discover" or t == "controller-hello" then
controllerId = pkt.from
controllerLastSeen = os.clock()
if b.disableSoftScram ~= nil then
softAutoScram = not b.disableSoftScram
end
-- answer
rnc2.send(PROG, pkt.from, {
type = "reactor-hello",
id = MY_ID,
role = "fission-reactor",
telem = readTelemetry(),
ver = "fission_agent/1.1"
})
return
end
-- controller heartbeat
if t == "controller-hb" then
controllerId = pkt.from
controllerLastSeen = os.clock()
if b.disableSoftScram ~= nil then
softAutoScram = not b.disableSoftScram
end
return
end
-- direct commands
if t == "cmd" then
controllerId = pkt.from
controllerLastSeen = os.clock()
if b.cmd == "on" then
local ok = start()
rnc2.send(PROG, pkt.from, { type="ack", cmd="on", ok=ok, reason= ok and nil or "temp>=1000" })
elseif b.cmd == "off" then
scram()
rnc2.send(PROG, pkt.from, { type="ack", cmd="off", ok=true })
elseif b.cmd == "setBurn" and b.value then
local ok, valOrReason = setBurn(b.value)
if ok then
rnc2.send(PROG, pkt.from, { type="ack", cmd="setBurn", ok=true, burn=valOrReason })
else
rnc2.send(PROG, pkt.from, { type="ack", cmd="setBurn", ok=false, reason=valOrReason })
end
elseif b.cmd == "setSoftScram" and b.value ~= nil then
softAutoScram = b.value and true or false
rnc2.send(PROG, pkt.from, { type="ack", cmd="setSoftScram", ok=true, softAutoScram=softAutoScram })
end
end
end
-- main loop ------------------------------------------------
print("fission_agent started, id=" .. MY_ID)
while true do
local now = os.clock()
-- controller timeout -> re-enable soft scram
if controllerId and (now - controllerLastSeen) > CONTROLLER_TIMEOUT then
controllerId = nil
softAutoScram = true
print("controller lost -> soft auto-scram re-enabled")
end
-- get telemetry (once per tick)
local telem = readTelemetry()
-- HARD safety (cannot be disabled)
if telem.temp and telem.temp >= HARD_SCRAM_TEMP then
if telem.status ~= "stopped" then
print(("HARD SCRAM at %.1f C"):format(telem.temp))
scram()
end
else
-- SOFT safety (controller can disable)
if isSoftUnsafe(telem) then
if telem.status ~= "stopped" then
print("SOFT SCRAM (coolant/waste/hot)")
scram()
end
end
end
-- Heartbeat (1/s)
if (now - lastHeartbeat) >= HEARTBEAT_EVERY then
lastHeartbeat = now
local hb = {
type = "hb",
id = MY_ID,
role = "fission-reactor",
telem = telem
}
if controllerId then
rnc2.send(PROG, controllerId, hb)
else
rnc2.broadcast(PROG, hb)
end
end
-- non-blocking recv
local pkt = rnc2.recv(PROG, 0.2)
if pkt then
handleMessage(pkt)
end
end