Archives
Trending
Support
Login
clear text
XML
Django
JavaScript
MATLAB
C
C++
C#
Python
SQL
Shell
Bash
Markdown
YAML
JSON
HTML
CSS
PHP
Java
Ruby
Go
Rust
Swift
Kotlin
Arduino
TypeScript
Perl
Autohotkey
Lua
SQF
R
Scala
Haskell
Groovy
Dart
Clojure
VB.NET
Objective-C
PowerShell
Bash
CoffeeScript
Verilog
-- controller.lua -- multi-reactor monitor/controller for rnc3 + fission_agent -- listens on BOTH controller and agent ports -- logs to debug.log os.loadAPI("rnc3") -- config --------------------------------------------------- local CTRL_PROG = "fission-controller" local AGENT_PROG = "fission-agent" -- this must match the program name in the agent local DISCOVER_EVERY = 3 local REDRAW_EVERY = 0.4 local OFFLINE_AFTER = 10 -- ids / ports ---------------------------------------------- local CTRL_ID = rnc3.getId(CTRL_PROG) local CTRL_PORT = rnc3.getPort(CTRL_PROG) local AGENT_PORT = rnc3.getPort(AGENT_PROG) -- logging -------------------------------------------------- local function log(msg) local h = fs.open("debug.log", "a") h.writeLine(("[%d] %s"):format(os.epoch("utc"), msg)) h.close() end log("=== controller start ===") log("CTRL_ID=" .. CTRL_ID) log("CTRL_PORT=" .. tostring(CTRL_PORT)) log("AGENT_PORT=" .. tostring(AGENT_PORT)) -- open BOTH ports on all modems (rnc3 already opened CTRL_PORT, but we force AGENT_PORT too) do local ms = { peripheral.find("modem") } for _, m in ipairs(ms) do if not m.isOpen(AGENT_PORT) then m.open(AGENT_PORT) log("opened AGENT_PORT on modem " .. peripheral.getName(m)) end if not m.isOpen(CTRL_PORT) then m.open(CTRL_PORT) log("opened CTRL_PORT on modem " .. peripheral.getName(m)) end end end -- monitor -------------------------------------------------- local mon = peripheral.find("monitor") if not mon then log("ERROR: no monitor attached") error("no monitor attached") end mon.setTextScale(1) mon.setBackgroundColor(colors.black) mon.setTextColor(colors.white) mon.clear() -- state: reactors[id] = {id=..., telem=..., lastSeen=..., row=...} local reactors = {} local lastDiscover = 0 local lastRedraw = 0 -- helpers -------------------------------------------------- local function shortId(id, max) max = max or 12 if #id <= max then return id end return id:sub(1, max-3) .. "..." end local function draw() local w, h = mon.getSize() mon.setBackgroundColor(colors.black) mon.setTextColor(colors.white) mon.clear() mon.setCursorPos(1,1) mon.write("FISSION CTRL") local list = {} for _, r in pairs(reactors) do table.insert(list, r) end table.sort(list, function(a,b) return a.id < b.id end) local row = 2 for _, r in ipairs(list) do if row > h then break end local online = (os.clock() - r.lastSeen) < OFFLINE_AFTER local t = r.telem or {} local temp = t.temp or 0 local burn = t.burn or 0 local status = t.status or "?" r.row = row mon.setCursorPos(1, row) if online then mon.setTextColor(colors.white) else mon.setTextColor(colors.gray) end local isStopped = (status == "stopped" or status == "not ready") local btnText = isStopped and "[ON]" or "[OFF]" local idPart = shortId(r.id, math.floor(w * 0.35)) local tempPart = (" T:%d"):format(temp) local burnPart = (" B:%d"):format(burn) local text = idPart .. tempPart .. burnPart local maxText = w - #btnText if #text > maxText then text = text:sub(1, maxText) end mon.write(text) local btnX = w - #btnText + 1 if btnX < 1 then btnX = 1 end mon.setCursorPos(btnX, row) if isStopped then mon.setTextColor(colors.lime) else mon.setTextColor(colors.red) end mon.write(btnText) row = row + 1 end end local function reactorAtY(y) for _, r in pairs(reactors) do if r.row == y then return r end end return nil end local function sendDiscover() -- send ON AGENT PORT (i.e. from controller prog, but to agents) rnc3.send(CTRL_PROG, "*", { type = "discover", disableSoftScram = true }) log("sent discover") end local function sendControllerHB() for id, _ in pairs(reactors) do rnc3.send(CTRL_PROG, id, { type = "controller-hb", disableSoftScram = true }) end end -- packet handler ------------------------------------------- local function handlePacket(pkt, viaPort) local b = pkt.body or {} local t = b.type log("got pkt from " .. tostring(pkt.from) .. " viaPort=" .. tostring(viaPort) .. " type=" .. tostring(t)) if t == "reactor-hello" or t == "hb" then local rid = pkt.from if not reactors[rid] then reactors[rid] = { id = rid } log("new reactor: " .. rid) end reactors[rid].lastSeen = os.clock() reactors[rid].telem = b.telem or reactors[rid].telem -- keep them warm with controller-hb rnc3.send(CTRL_PROG, rid, { type = "controller-hb", disableSoftScram = true }) end end -- main loop ------------------------------------------------ while true do local now = os.clock() if now - lastDiscover >= DISCOVER_EVERY then sendDiscover() lastDiscover = now end if now - lastRedraw >= REDRAW_EVERY then draw() lastRedraw = now end local ev, p1, p2, p3, p4, p5 = os.pullEvent() if ev == "modem_message" then local channel = p2 local data = p4 -- we only care if it's on OUR controller port OR on the agent port if channel == CTRL_PORT or channel == AGENT_PORT then log("modem_message on channel " .. channel .. ", len=" .. (data and #tostring(data) or 0)) local ok, pkt = pcall(textutils.unserialize, data) if ok and type(pkt) == "table" then -- make sure it's actually meant for us (to = * or to = our id) local me = CTRL_ID if (not pkt.to) or pkt.to == "*" or pkt.to == me then handlePacket(pkt, channel) else log("pkt ignored: to=" .. tostring(pkt.to)) end else log("failed to unserialize modem message") end end elseif ev == "monitor_touch" then local side, x, y = p1, p2, p3 log(("monitor_touch x=%d y=%d"):format(x,y)) local r = reactorAtY(y) if r then local telem = r.telem or {} local status = telem.status or "unknown" if status == "stopped" or status == "not ready" then log("sending ON to " .. r.id) rnc3.send(CTRL_PROG, r.id, { type = "cmd", cmd = "on" }) else log("sending OFF to " .. r.id) rnc3.send(CTRL_PROG, r.id, { type = "cmd", cmd = "off" }) end lastRedraw = 0 end elseif ev == "terminate" then log("terminated by user") mon.clear() mon.setCursorPos(1,1) mon.write("CTRL stopped") break end -- send controller-hb each loop (cheap) sendControllerHB() end
Mark as private (unlisted)
for 30 minutes
for 6 hours
for 1 day
for 1 week
for 1 month
for 1 year
everlasting (like CCCP)