robathome icon

AutoMiner - Simple miner

robathome | PRO | 10/27/25 04:43:08 PM UTC (Edited) | 0 ⭐ | 400 👁️ | Never ⏰ | []
text |

13.4 KB

|

None

|

0 👍

/

0 👎

-- miner_startup.lua  — Flicker-free UI + debug + “mostly-stupid” miner
-- Axis-only mine vectors. No area/volume planning. Controller drives vectors.
-- Requires: CC:Tweaked turtle with wireless/Ender modem.
 -------------------- CONFIG --------------------
local PROTOCOL = "minesys:v1"
 -- Home / logistics (edit to your base)
local HOME       = { x=-924, y=99, z=-1093 }
local SHAFT_XZ   = { x=-924, z=-1092 }
local CHEST_FUEL = { x=-924, y=98, z=-1091 }
local CHEST_DUMP = { x=-924, y=99, z=-1091 }
 -- Fuel policy
local FUEL_MIN_BUFFER = 200
local FUEL_PER_STEP   = 1
local REFUEL_TARGET   = 3000
 -- Timing
local HEARTBEAT_SEC = 2.0
local UI_REFRESH    = 0.5
------------------------------------------------
 -- Peripherals
local modem = peripheral.find("modem", function(_,p) return p.isWireless() end)
assert(modem, "No wireless modem")
rednet.open(peripheral.getName(modem))
 -- UI window (double-buffered, no flicker)
local root = term.current()
local W,H = root.getSize()
local ui = window.create(root, 1, 1, W, H, true)
ui.setCursorBlink(false)
 local function uiBegin()
  ui.setVisible(false)
  ui.setBackgroundColor(colors.black)
  ui.setTextColor(colors.white)
  ui.clear()
  ui.setCursorPos(1,1)
end
local function uiEnd() ui.setVisible(true) end
 -- State
local BOOT = os.epoch("utc")
local function now() return os.epoch("utc") end
local function uptime()
  local s = math.floor((now()-BOOT)/1000)
  local h = math.floor(s/3600); s=s-3600*h
  local m = math.floor(s/60);   s=s-60*m
  return string.format("%02d:%02d:%02d", h,m,s)
end
 local S = {
  status="idle", extra=nil, facing="N",
  pos=nil, lastFix=nil, fuel=turtle.getFuelLevel(),
  abort=false, stop=false, uiOn=true, lastAck=nil
}
 -- GPS helpers
local function gpsPos()
  local x,y,z = gps.locate(5)
  if x then
    S.pos = { x = math.floor(x+0.5), y = math.floor(y+0.5), z = math.floor(z+0.5) }
    S.lastFix = now()
  end
  return x,y,z
end
local function fixAge()
  if not S.lastFix then return nil end
  return math.floor((now()-S.lastFix)/1000)
end
 -- Inventory stats
local function invStats()
  local empty, fuelAvail = 0, 0
  for i=1,16 do
    local cnt = turtle.getItemCount(i)
    if cnt==0 then empty = empty + 1
    else
      turtle.select(i)
      if turtle.refuel(0) then fuelAvail = fuelAvail + cnt end
    end
  end
  turtle.select(1)
  return { freeSlots=empty, freePct=(empty/16)*100, fuelAvail=fuelAvail }
end
 -- Heartbeat
local function hb()
  S.fuel = turtle.getFuelLevel()
  if not S.pos then gpsPos() end
  rednet.broadcast({
    t=now(), op="status", id=os.getComputerID(), label=os.getComputerLabel(),
    pos=S.pos, fuel=S.fuel, facing=S.facing, status=S.status,
    extra={ kind=S.extra and S.extra.kind or nil, data=S.extra, gps={fixAge=fixAge()} },
    inv=invStats()
  }, PROTOCOL)
end
local function report(status, extra) S.status=status or S.status; S.extra=extra; hb() end
 -- UI
local spinner = {"|","/","-","\\"}; local sp=1
local function drawUI()
  if not S.uiOn then return end
  uiBegin()
  local lbl = os.getComputerLabel() or ("id"..os.getComputerID())
  ui.write(("Miner %s  [%s]  %s"):format(lbl, spinner[sp], uptime()))
  sp = (sp % #spinner) + 1
   local pos = S.pos and ("("..S.pos.x..","..S.pos.y..","..S.pos.z..")") or "(?, ?, ?)"
  local fage = fixAge() and (fixAge().."s") or "no-fix"
   ui.setCursorPos(1,3); ui.write(("Pos: %s  GPS: %s"):format(pos, fage))
  ui.setCursorPos(1,4); ui.write(("Fuel: %s"):format(S.fuel or "?"))
   local inv = invStats()
  ui.setCursorPos(1,5); ui.write(("Inv: free %d/16  filled %d%%  fuelItems %d")
    :format(inv.freeSlots, 100-math.floor(inv.freePct+0.5), inv.fuelAvail))
   ui.setCursorPos(1,6); ui.write(("Facing: %s   Status: %s"):format(S.facing, S.status or "?"))
   if S.extra and S.extra.kind=="work" and S.extra.total and S.extra.step then
    local pct = math.floor(S.extra.step*100/S.extra.total+0.5)
    ui.setCursorPos(1,8); ui.write(("Work: %d/%d  (%d%%)"):format(S.extra.step, S.extra.total, pct))
  elseif S.extra and S.extra.kind then
    ui.setCursorPos(1,8); ui.write(("Extra: %s"):format(S.extra.kind))
  end
   ui.setCursorPos(1,10); ui.write(("Abort:%s  Stop:%s"):format(tostring(S.abort), tostring(S.stop)))
  local mname = peripheral.getName(modem)
  ui.setCursorPos(1,12); ui.write(("Modem: %s  Rednet: open"):format(mname))
  if S.lastAck then ui.setCursorPos(1,13); ui.write(("Last ACK: %s"):format(S.lastAck)) end
  uiEnd()
end
 -- Motion primitives
local DIRS={N=0,E=1,S=2,W=3}
local function setFacing(dir)
  if not DIRS[dir] then return false,"bad dir" end
  local cur=DIRS[S.facing] or 0; local tgt=DIRS[dir]
  local diff=(tgt-cur)%4
  if diff==1 then turtle.turnRight()
  elseif diff==2 then turtle.turnRight(); turtle.turnRight()
  elseif diff==3 then turtle.turnLeft() end
  S.facing=dir; report("idle",{kind="face",dir=dir}); return true
end
local function digForward() while turtle.detect() do turtle.dig(); sleep(0.05) end end
local function stepForward()
  local tries=0
  digForward()
  while not turtle.forward() do
    digForward(); tries=tries+1
    if tries>60 then return false,"blocked" end
    sleep(0.05)
  end
  return true
end
local function stepUp()
  while turtle.detectUp() do turtle.digUp(); sleep(0.05) end
  local tries=0
  while not turtle.up() do
    while turtle.detectUp() do turtle.digUp(); sleep(0.05) end
    tries=tries+1; if tries>60 then return false,"blocked up" end
    sleep(0.05)
  end
  return true
end
local function stepDown()
  while turtle.detectDown() do turtle.digDown(); sleep(0.05) end
  local tries=0
  while not turtle.down() do
    while turtle.detectDown() do turtle.digDown(); sleep(0.05) end
    tries=tries+1; if tries>60 then return false,"blocked down" end
    sleep(0.05)
  end
  return true
end
 local function moveAxis(axis, n)
  if n==0 then return true end
  local dir, steps = nil, math.abs(n)
  if axis=="x" then dir = n>0 and "E" or "W"
  elseif axis=="z" then dir = n>0 and "S" or "N"
  elseif axis=="y" then
    for i=1,steps do
      if S.abort then return false,"abort" end
      report("moving",{kind="move",axis="y",step=i,total=steps})
      local ok,err = (n>0) and stepUp() or stepDown()
      if not ok then return false,err end
      hb()
    end
    return true
  else return false,"bad axis" end
  assert(setFacing(dir))
  for i=1,steps do
    if S.abort then return false,"abort" end
    report("moving",{kind="move",axis=axis,step=i,total=steps})
    local ok,err = stepForward()
    if not ok then return false,err end
    hb()
  end
  return true
end
 -- Distance/fuel estimates to get home
local function distToHome()
  if not S.pos then return 999999 end
  local x,y,z = S.pos.x,S.pos.y,S.pos.z
  local d1 = math.abs(x-SHAFT_XZ.x) + math.abs(z-SHAFT_XZ.z)
  local d2 = math.abs(HOME.y - y)
  local d3 = math.abs(HOME.z - (-1092))
  return d1 + d2 + d3
end
local function fuelNeededToHome() return distToHome()*FUEL_PER_STEP + FUEL_MIN_BUFFER end
 -- Routing + base ops
local function gotoPos(tx,ty,tz, order)
  order = order or {"y","x","z"}
  if not S.pos then gpsPos() end
  if not S.pos then return false,"no gps" end
  local dx,dy,dz = tx - S.pos.x, ty - S.pos.y, tz - S.pos.z
  local map={x=dx,y=dy,z=dz}
  for _,ax in ipairs(order) do
    local ok,err = moveAxis(ax, map[ax]); if not ok then return false,err end
  end
  return true
end
local function faceTo(target)
  if not S.pos then return false,"no gps" end
  local dx, dz = target.x - S.pos.x, target.z - S.pos.z
  if math.abs(dx)>math.abs(dz) then return setFacing(dx>0 and "E" or "W") else return setFacing(dz>0 and "S" or "N") end
end
local function returnToBase()
  report("returning",{kind="service",stage="to_shaft"}); assert(gotoPos(SHAFT_XZ.x, S.pos.y, SHAFT_XZ.z, {"x","z"}))
  report("returning",{kind="service",stage="shaft"});    assert(gotoPos(SHAFT_XZ.x, HOME.y, SHAFT_XZ.z, {"y"}))
  report("returning",{kind="service",stage="home"});     assert(gotoPos(HOME.x, HOME.y, HOME.z, {"z"}))
  return true
end
local function dumpAll()
  assert(gotoPos(CHEST_DUMP.x, CHEST_DUMP.y, CHEST_DUMP.z, {"x","y","z"}))
  faceTo({x=CHEST_DUMP.x+1, z=CHEST_DUMP.z})
  for i=1,16 do
    local d=turtle.getItemDetail(i)
    if d then
      turtle.select(i)
      if turtle.refuel(0) then else turtle.drop() end
    end
  end
  turtle.select(1); return true
end
local function refuelFromChest()
  assert(gotoPos(CHEST_FUEL.x, CHEST_FUEL.y, CHEST_FUEL.z, {"x","y","z"}))
  faceTo({x=CHEST_FUEL.x+1, z=CHEST_FUEL.z})
  local pulls=0
  while turtle.getFuelLevel() < REFUEL_TARGET and pulls<64 do
    if turtle.suck() then
      for i=1,16 do turtle.select(i); if turtle.refuel(0) then turtle.refuel() end end
      turtle.select(1)
    else break end
    pulls=pulls+1
  end
  return true
end
local function serviceIfNeeded()
  local need = fuelNeededToHome()
  if S.fuel < need then
    report("service",{kind="low_fuel", need=need, have=S.fuel})
    assert(returnToBase()); assert(dumpAll()); assert(refuelFromChest()); assert(gotoPos(HOME.x, HOME.y, HOME.z))
    report("idle",{kind="serviced"}); return true
  end
  local inv = invStats()
  if inv.freeSlots <= 1 then
    report("service",{kind="dump"})
    assert(returnToBase()); assert(dumpAll()); assert(refuelFromChest()); assert(gotoPos(HOME.x, HOME.y, HOME.z))
    report("idle",{kind="serviced"}); return true
  end
  return false
end
 -- Mining (single axis only)
local function mineVector(axis, len, stopOnAir, stopOnDirt)
  len = tonumber(len) or 0
  if len==0 then return true end
  serviceIfNeeded()
   if axis=="y" then
    local steps=math.abs(len)
    for i=1,steps do
      if S.abort then return false,"abort" end
      report("ACTIVE",{kind="work",step=i,total=steps})
      local ok,err = (len>0) and stepUp() or stepDown()
      if not ok then return false,err end
      hb(); serviceIfNeeded()
    end
    report("idle"); return true
  end
   local dir = (axis=="x") and ((len>0) and "E" or "W") or (axis=="z") and ((len>0) and "S" or "N") or nil
  if not dir then return false,"bad axis" end
  assert(setFacing(dir))
  local steps = math.abs(len)
  for i=1,steps do
    if S.abort then return false,"abort" end
    if stopOnAir or stopOnDirt then
      local ok, data = turtle.inspect()
      if ok and stopOnDirt and data and data.name and data.name:find("dirt") then report("STOPPED",{kind="halt_dirt"}); return true end
      if (not ok) and stopOnAir then report("STOPPED",{kind="halt_air"}); return true end
    end
    report("ACTIVE",{kind="work",step=i,total=steps})
    digForward()
    local ok,err = stepForward()
    if not ok then return false,err end
    hb(); serviceIfNeeded()
  end
  report("idle"); return true
end
 -- Debug commands
local function dbg_echo(msg) return true, ("echo: "..tostring(msg)) end
local function dbg_uptime() return true, uptime() end
local function dbg_toggle_ui(on) S.uiOn = on~=false; return true, "ui:"..tostring(S.uiOn) end
local function dbg_where() return true, S.pos end
local function dbg_gps()
  local x,y,z = gps.locate(3)
  local ids = {rednet.lookup("gps")}
  local n=0 for _ in pairs(ids) do n=n+1 end
  return x~=nil, {x=x,y=y,z=z, fixAge=fixAge(), beacons=n}
end
local function dbg_net()
  local list={}
  for _,n in ipairs(peripheral.getNames()) do table.insert(list, n..":"..peripheral.getType(n)) end
  return true, {periph=list}
end
 -- Threads
local function uiThread() while true do drawUI(); sleep(UI_REFRESH) end end
local function hbThread() while true do sleep(HEARTBEAT_SEC); hb() end end
local function rxThread()
  while true do
    local sid,msg,proto = rednet.receive(PROTOCOL)
    if proto==PROTOCOL and type(msg)=="table" and msg.op=="cmd" then
      local ok,err=false,nil
      if     msg.act=="face"            then ok,err=setFacing(msg.dir)
      elseif msg.act=="up"              then ok,err=moveAxis("y", math.abs(msg.n or 1))
      elseif msg.act=="down"            then ok,err=moveAxis("y", -math.abs(msg.n or 1))
      elseif msg.act=="mine_vector"     then ok,err=mineVector(msg.axis, msg.len, msg.stopOnAir, msg.stopOnDirt)
      elseif msg.act=="return_service"  then ok,err=returnToBase()
      elseif msg.act=="dump"            then ok,err=dumpAll()
      elseif msg.act=="refuel"          then ok,err=refuelFromChest()
      elseif msg.act=="set_facing_tag"  then S.facing=msg.dir or S.facing; ok=true
      elseif msg.act=="set_pos"         then if msg.x and msg.y and msg.z then S.pos={x=msg.x,y=msg.y,z=msg.z}; ok=true else err="bad pos" end
      elseif msg.act=="abort"           then S.abort=true; ok=true
      elseif msg.act=="resume"          then S.abort=false; ok=true
      elseif msg.act=="stop"            then S.stop=true; ok=true
      -- DEBUG:
      elseif msg.act=="dbg_echo"        then ok,err=dbg_echo(msg.msg)
      elseif msg.act=="dbg_net"         then ok,err=dbg_net()
      elseif msg.act=="dbg_gps"         then ok,err=dbg_gps()
      elseif msg.act=="dbg_uptime"      then ok,err=dbg_uptime()
      elseif msg.act=="dbg_toggle_ui"   then ok,err=dbg_toggle_ui(msg.on)
      elseif msg.act=="dbg_where"       then ok,err=dbg_where()
      else err="unknown act" end
      S.lastAck = (ok and "ok") or ("err:"..tostring(err))
      rednet.send(sid,{op="ack", ok=ok, err=err, ref=msg.ref}, PROTOCOL)
      hb()
    end
  end
end
 report("idle",{kind="boot"})
parallel.waitForAny(uiThread, hbThread, rxThread)
 

Comments