robathome icon

RoboMiner_controller

robathome | PRO | 10/18/25 07:52:33 PM UTC (Edited) | 0 ⭐ | 244 👁️ | Never ⏰ | []
text |

10.89 KB

|

None

|

0 👍

/

0 👎

-- controller_startup.lua
-- Two-display controller: LEFT 4x4 list + MAP on the other monitor.
-- Wireless Ender modem required. Supports pocket RPC and goto().
-- Map shows controller, GPS nodes, and turtles with high-contrast colors.
 -- ================= CONFIG =================
local CONFIG = {
  repl = false, -- set true to enable inline REPL
  controllerPos = { x = -732, y = 66, z = -322 },
  gpsNodes = {
    {x=-715,y=68,z=-327},
    {x=-732,y=67,z=-344},
    {x=-762,y=69,z=-329},
    {x=-745,y=66,z=-312},
    {x=-725,y=72,z=-300},
  },
  map = {
    origin = { x = -769, z = -351 }, -- top-left world X/Z at map cell (1,1)
    scale  = 1,                      -- blocks per pixel
    follow = false,
  }
}
-- ==========================================
 local PROTOCOL   = "minesys:v1"   -- miners: status/acks/cmds
local CTL_PROTO  = "minesys:ctl"  -- controller RPC (goto, clear, etc.)
 -- ---------- Peripherals ----------
local modem = peripheral.find("modem", function(_,p) return p.isWireless() end)
if not modem then error("No wireless modem") end
rednet.open(peripheral.getName(modem))
pcall(function() rednet.host(CTL_PROTO, "miner_controller") end)
 -- find two monitors; use the 4x4 merged as LIST, the other as MAP
local mons = { peripheral.find("monitor") }
if #mons < 2 then error("Need two monitors attached") end
local function pickMonitors()
  local listMon, mapMon
  for _,m in ipairs(mons) do
    m.setTextScale(0.5)
    local w,h = m.getSize()
    if w==4 and h==4 then listMon = m else mapMon = m end
  end
  if not listMon then
    table.sort(mons, function(a,b) local aw,ah=a.getSize(); local bw,bh=b.getSize(); return aw*ah < bw*bh end)
    listMon, mapMon = mons[1], mons[2]
  end
  return listMon, mapMon
end
local monList, monMap = pickMonitors()
 -- ---------- State ----------
local function now() return os.epoch("utc") end
local turtles = {} -- id-> {id,label,pos,fuel,facing,status,freeSlots,freePct,tSeen}
local function count(t) local n=0 for _ in pairs(t) do n=n+1 end return n end
 local function upsertStatus(m)
  local key = m.id or m.label
  if not key then return end
  local t = turtles[key] or {}
  t.id     = m.id or t.id
  t.label  = m.label or t.label or ("t"..tostring(m.id or ""))
  t.pos    = m.pos or t.pos
  t.fuel   = m.fuel or t.fuel
  t.facing = m.facing or t.facing
  t.status = m.status or t.status or "?"
  if m.extra and m.extra.inv then
    t.freeSlots = m.extra.inv.freeSlots
    t.freePct   = m.extra.inv.freePct
  end
  t.tSeen  = now()
  turtles[key] = t
end
 -- ---------- Commands to miners ----------
local function sendCmd(target, tbl)
  tbl.op="cmd"; tbl.ref=math.random(1,1e9)
  local tgtID=nil
  for _,t in pairs(turtles) do
    if tostring(t.id)==tostring(target) or t.label==target then tgtID=t.id break end
  end
  if not tgtID then return false,"unknown target" end
  rednet.send(tgtID, tbl, PROTOCOL)
  local tm=os.startTimer(3)
  while true do
    local ev,a,b,c = os.pullEvent()
    if ev=="rednet_message" then
      local sid,msg,proto=a,b,c
      if proto==PROTOCOL and type(msg)=="table" and msg.op=="ack" and msg.ref==tbl.ref then
        return msg.ok, msg.err
      end
    elseif ev=="timer" and a==tm then return false,"ack timeout" end
  end
end
local function face(lbl,dir) return sendCmd(lbl,{act="face",dir=dir}) end
local function forward(lbl,n) return sendCmd(lbl,{act="forward",n=n}) end
local function up(lbl,n) return sendCmd(lbl,{act="up",n=n}) end
local function down(lbl,n) return sendCmd(lbl,{act="down",n=n}) end
local function clearArea(lbl,L,W,H,dir) return sendCmd(lbl,{act="clear_area",length=L,width=W,height=H,dir=dir}) end
local function setFacingTag(lbl,dir) return sendCmd(lbl,{act="set_facing_tag",dir=dir}) end
 -- Move along one axis only
local function moveAxis(lbl, axis, delta)
  if delta==0 then return true end
  if axis=="x" then
    if delta>0 then assert(face(lbl,"E")) else assert(face(lbl,"W")) end
    return forward(lbl, math.abs(delta))
  elseif axis=="z" then
    if delta>0 then assert(face(lbl,"S")) else assert(face(lbl,"N")) end
    return forward(lbl, math.abs(delta))
  elseif axis=="y" then
    if delta>0 then return up(lbl, math.abs(delta)) else return down(lbl, math.abs(-delta)) end
  else
    return false,"bad axis"
  end
end
 -- goto composed of axis-only moves in selected order
local function gotoXYZ(tgtLabel, tx, ty, tz, order)
  local t = nil
  for _,v in pairs(turtles) do if v.label==tgtLabel then t=v break end end
  if not t or not t.pos then return false,"unknown turtle or position" end
  local cx, cy, cz = t.pos.x, t.pos.y, t.pos.z
  local dx, dy, dz = tx-cx, ty-cy, tz-cz
  order = order or {"y","x","z"}
  local map = {x=dx,y=dy,z=dz}
  for _,ax in ipairs(order) do
    local ok,err = moveAxis(tgtLabel, ax, map[ax])
    if not ok then return false,err end
  end
  return true
end
 -- ---------- LIST (left 4x4) ----------
local function drawList()
  local m = monList
  m.setBackgroundColor(colors.black); m.setTextColor(colors.white); m.clear()
  m.setCursorPos(1,1); m.write("T:"..count(turtles))
  -- Very compact 4x4: each turtle uses one row: Lbl St Fuel %Free
  -- Position is shown briefly in row 4 as the most recent turtle seen.
  local y = 2
  local lastPosTxt = nil
  for _,t in pairs(turtles) do
    if y>3 then break end
    m.setCursorPos(1,y)
    local lab = (t.label or "?"):sub(1,4)
    local st  = (t.status or "?"):sub(1,4)
    local fu  = t.fuel and tostring(t.fuel) or "?"
    local fp  = t.freePct and string.format("%2d", math.floor(t.freePct+0.5)) or "--"
    local line = string.format("%-4s%-4s", lab, st)
    m.write(line)
    local w,_ = m.getSize()
    m.setCursorPos(w-3,y); m.write(string.format("%3s", fu))  -- fuel right
    m.setCursorPos(w-1,y); m.write(fp)                        -- % free
    if t.pos then lastPosTxt = ("x% d z% d"):format(t.pos.x, t.pos.z) end
    y = y + 1
  end
  if lastPosTxt then
    m.setCursorPos(1,4); m.write(lastPosTxt:sub(1,4)) -- hint of position due to tight space
  end
end
 -- ---------- MAP (top-down XZ) ----------
local function worldToMap(x,z)
  local w,h = monMap.getSize()
  local ox, oz = CONFIG.map.origin.x, CONFIG.map.origin.z
  local s = CONFIG.map.scale
  local col = math.floor((x - ox)/s) + 1
  local row = math.floor((z - oz)/s) + 1
  if col < 1 or row < 1 or col > w or row > h then return nil,nil end
  return col,row
end
 local function maybeFollow()
  if not CONFIG.map.follow then return end
  for _,t in pairs(turtles) do
    if t.pos then
      CONFIG.map.origin.x = t.pos.x - 10*CONFIG.map.scale
      CONFIG.map.origin.z = t.pos.z - 6*CONFIG.map.scale
      return
    end
  end
end
 local function drawMap()
  maybeFollow()
  local m = monMap
  m.setBackgroundColor(colors.black); m.clear()
  local w,h = m.getSize()
   -- High-contrast border and grid
  m.setTextColor(colors.lightGray)
  for x=1,w do m.setCursorPos(x,1); m.write("─"); m.setCursorPos(x,h); m.write("─") end
  for y=1,h do m.setCursorPos(1,y); m.write("│"); m.setCursorPos(w,y); m.write("│") end
  m.setCursorPos(1,1); m.write("┌"); m.setCursorPos(w,1); m.write("┐")
  m.setCursorPos(1,h); m.write("└"); m.setCursorPos(w,h); m.write("┘")
   -- faint grid points every 4 cells
  for r=2,h-1 do
    for c=2,w-1 do
      if ((c-2) % 4 == 0) and ((r-2) % 4 == 0) then
        m.setCursorPos(c,r); m.setTextColor(colors.gray); m.write(".")
      end
    end
  end
   -- Legend
  m.setCursorPos(3,1); m.setTextColor(colors.white); m.write("MAP  C/G/T")
   -- Controller
  local cx,cz = CONFIG.controllerPos.x, CONFIG.controllerPos.z
  local mc, mr = worldToMap(cx, cz)
  if mc then m.setCursorPos(mc,mr); m.setTextColor(colors.cyan); m.write("C") end
   -- GPS nodes
  for _,g in ipairs(CONFIG.gpsNodes) do
    local gc,gr = worldToMap(g.x, g.z)
    if gc then m.setCursorPos(gc,gr); m.setTextColor(colors.magenta); m.write("G") end
  end
   -- Turtles (lime good, yellow low space, red near full)
  for _,t in pairs(turtles) do
    if t.pos then
      local tc,tr = worldToMap(t.pos.x, t.pos.z)
      if tc then
        local col = colors.lime
        if t.freePct then
          if t.freePct < 10 then col = colors.red
          elseif t.freePct < 30 then col = colors.yellow
          else col = colors.lime end
        end
        m.setCursorPos(tc,tr); m.setTextColor(col); m.write("T")
      end
    end
  end
end
 -- ---------- Controller RPC server (for pocket 'goto', etc.) ----------
local function ctlServer()
  while true do
    local sid, msg, proto = rednet.receive(CTL_PROTO)
    if proto==CTL_PROTO and type(msg)=="table" and msg.op=="req" then
      local ok, err
      if msg.act=="goto" then
        ok, err = gotoXYZ(msg.label, msg.x, msg.y, msg.z, msg.order)
      elseif msg.act=="clear" then
        ok, err = clearArea(msg.label, msg.L, msg.W, msg.H, msg.dir)
      elseif msg.act=="face" then
        ok, err = face(msg.label, msg.dir)
      elseif msg.act=="fwd" then
        ok, err = forward(msg.label, msg.n)
      elseif msg.act=="up" then
        ok, err = up(msg.label, msg.n)
      elseif msg.act=="dn" then
        ok, err = down(msg.label, msg.n)
      else
        ok, err = false, "unknown act"
      end
      rednet.send(sid, {op="resp", ref=msg.ref, ok=ok, err=err}, CTL_PROTO)
    end
  end
end
 -- ---------- Networking + UI ----------
local function netUiThread()
  while true do
    local id,msg,proto = rednet.receive(PROTOCOL, 0.2)
    if id and proto==PROTOCOL and type(msg)=="table" and msg.op=="status" then
      upsertStatus(msg)
    end
    drawList()
    drawMap()
  end
end
 -- Optional inline REPL for manual driving
local function replThread()
  term.setCursorPos(1,1); print("Controller REPL. help for cmds.")
  while true do
    io.write("> ")
    local line = read()
    local a,b,c,d,e,f = line:match("^(%S+)%s*(%S*)%s*(%S*)%s*(%S*)%s*(%S*)%s*(%S*)")
    if a=="help" then
      print("fac <label> <N|E|S|W>")
      print("fwd <label> <n>")
      print("up  <label> <n>")
      print("dn  <label> <n>")
      print("clear <label> <L> <W> <H> <N|E|S|W>")
      print("tagface <label> <N|E|S|W>")
      print("goto <label> <x> <y> <z> [order yxz|xyz]")
    elseif a=="fac" then print(face(b,c))
    elseif a=="fwd" then print(forward(b,tonumber(c)))
    elseif a=="up" then print(up(b,tonumber(c)))
    elseif a=="dn" then print(down(b,tonumber(c)))
    elseif a=="clear" then print(clearArea(b,tonumber(c),tonumber(d),tonumber(e),f))
    elseif a=="tagface" then print(setFacingTag(b,c))
    elseif a=="goto" then
      local order = (f and #f==3) and {f:sub(1,1),f:sub(2,2),f:sub(3,3)} or nil
      print(gotoXYZ(b, tonumber(c), tonumber(d), tonumber(e), order))
    else print("unknown") end
  end
end
 if CONFIG.repl then
  parallel.waitForAny(netUiThread, ctlServer, replThread)
else
  parallel.waitForAny(netUiThread, ctlServer)
end
  

Comments