robathome

AutoMiner - Simple miner

Oct 25th, 2025 (edited)
236
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 13.40 KB | None | 0 0
  1. -- miner_startup.lua — Flicker-free UI + debug + “mostly-stupid” miner
  2. -- Axis-only mine vectors. No area/volume planning. Controller drives vectors.
  3. -- Requires: CC:Tweaked turtle with wireless/Ender modem.
  4.  
  5. -------------------- CONFIG --------------------
  6. local PROTOCOL = "minesys:v1"
  7.  
  8. -- Home / logistics (edit to your base)
  9. local HOME = { x=-924, y=99, z=-1093 }
  10. local SHAFT_XZ = { x=-924, z=-1092 }
  11. local CHEST_FUEL = { x=-924, y=98, z=-1091 }
  12. local CHEST_DUMP = { x=-924, y=99, z=-1091 }
  13.  
  14. -- Fuel policy
  15. local FUEL_MIN_BUFFER = 200
  16. local FUEL_PER_STEP = 1
  17. local REFUEL_TARGET = 3000
  18.  
  19. -- Timing
  20. local HEARTBEAT_SEC = 2.0
  21. local UI_REFRESH = 0.5
  22. ------------------------------------------------
  23.  
  24. -- Peripherals
  25. local modem = peripheral.find("modem", function(_,p) return p.isWireless() end)
  26. assert(modem, "No wireless modem")
  27. rednet.open(peripheral.getName(modem))
  28.  
  29. -- UI window (double-buffered, no flicker)
  30. local root = term.current()
  31. local W,H = root.getSize()
  32. local ui = window.create(root, 1, 1, W, H, true)
  33. ui.setCursorBlink(false)
  34.  
  35. local function uiBegin()
  36. ui.setVisible(false)
  37. ui.setBackgroundColor(colors.black)
  38. ui.setTextColor(colors.white)
  39. ui.clear()
  40. ui.setCursorPos(1,1)
  41. end
  42. local function uiEnd() ui.setVisible(true) end
  43.  
  44. -- State
  45. local BOOT = os.epoch("utc")
  46. local function now() return os.epoch("utc") end
  47. local function uptime()
  48. local s = math.floor((now()-BOOT)/1000)
  49. local h = math.floor(s/3600); s=s-3600*h
  50. local m = math.floor(s/60); s=s-60*m
  51. return string.format("%02d:%02d:%02d", h,m,s)
  52. end
  53.  
  54. local S = {
  55. status="idle", extra=nil, facing="N",
  56. pos=nil, lastFix=nil, fuel=turtle.getFuelLevel(),
  57. abort=false, stop=false, uiOn=true, lastAck=nil
  58. }
  59.  
  60. -- GPS helpers
  61. local function gpsPos()
  62. local x,y,z = gps.locate(5)
  63. if x then
  64. S.pos = { x = math.floor(x+0.5), y = math.floor(y+0.5), z = math.floor(z+0.5) }
  65. S.lastFix = now()
  66. end
  67. return x,y,z
  68. end
  69. local function fixAge()
  70. if not S.lastFix then return nil end
  71. return math.floor((now()-S.lastFix)/1000)
  72. end
  73.  
  74. -- Inventory stats
  75. local function invStats()
  76. local empty, fuelAvail = 0, 0
  77. for i=1,16 do
  78. local cnt = turtle.getItemCount(i)
  79. if cnt==0 then empty = empty + 1
  80. else
  81. turtle.select(i)
  82. if turtle.refuel(0) then fuelAvail = fuelAvail + cnt end
  83. end
  84. end
  85. turtle.select(1)
  86. return { freeSlots=empty, freePct=(empty/16)*100, fuelAvail=fuelAvail }
  87. end
  88.  
  89. -- Heartbeat
  90. local function hb()
  91. S.fuel = turtle.getFuelLevel()
  92. if not S.pos then gpsPos() end
  93. rednet.broadcast({
  94. t=now(), op="status", id=os.getComputerID(), label=os.getComputerLabel(),
  95. pos=S.pos, fuel=S.fuel, facing=S.facing, status=S.status,
  96. extra={ kind=S.extra and S.extra.kind or nil, data=S.extra, gps={fixAge=fixAge()} },
  97. inv=invStats()
  98. }, PROTOCOL)
  99. end
  100. local function report(status, extra) S.status=status or S.status; S.extra=extra; hb() end
  101.  
  102. -- UI
  103. local spinner = {"|","/","-","\\"}; local sp=1
  104. local function drawUI()
  105. if not S.uiOn then return end
  106. uiBegin()
  107. local lbl = os.getComputerLabel() or ("id"..os.getComputerID())
  108. ui.write(("Miner %s [%s] %s"):format(lbl, spinner[sp], uptime()))
  109. sp = (sp % #spinner) + 1
  110.  
  111. local pos = S.pos and ("("..S.pos.x..","..S.pos.y..","..S.pos.z..")") or "(?, ?, ?)"
  112. local fage = fixAge() and (fixAge().."s") or "no-fix"
  113.  
  114. ui.setCursorPos(1,3); ui.write(("Pos: %s GPS: %s"):format(pos, fage))
  115. ui.setCursorPos(1,4); ui.write(("Fuel: %s"):format(S.fuel or "?"))
  116.  
  117. local inv = invStats()
  118. ui.setCursorPos(1,5); ui.write(("Inv: free %d/16 filled %d%% fuelItems %d")
  119. :format(inv.freeSlots, 100-math.floor(inv.freePct+0.5), inv.fuelAvail))
  120.  
  121. ui.setCursorPos(1,6); ui.write(("Facing: %s Status: %s"):format(S.facing, S.status or "?"))
  122.  
  123. if S.extra and S.extra.kind=="work" and S.extra.total and S.extra.step then
  124. local pct = math.floor(S.extra.step*100/S.extra.total+0.5)
  125. ui.setCursorPos(1,8); ui.write(("Work: %d/%d (%d%%)"):format(S.extra.step, S.extra.total, pct))
  126. elseif S.extra and S.extra.kind then
  127. ui.setCursorPos(1,8); ui.write(("Extra: %s"):format(S.extra.kind))
  128. end
  129.  
  130. ui.setCursorPos(1,10); ui.write(("Abort:%s Stop:%s"):format(tostring(S.abort), tostring(S.stop)))
  131. local mname = peripheral.getName(modem)
  132. ui.setCursorPos(1,12); ui.write(("Modem: %s Rednet: open"):format(mname))
  133. if S.lastAck then ui.setCursorPos(1,13); ui.write(("Last ACK: %s"):format(S.lastAck)) end
  134. uiEnd()
  135. end
  136.  
  137. -- Motion primitives
  138. local DIRS={N=0,E=1,S=2,W=3}
  139. local function setFacing(dir)
  140. if not DIRS[dir] then return false,"bad dir" end
  141. local cur=DIRS[S.facing] or 0; local tgt=DIRS[dir]
  142. local diff=(tgt-cur)%4
  143. if diff==1 then turtle.turnRight()
  144. elseif diff==2 then turtle.turnRight(); turtle.turnRight()
  145. elseif diff==3 then turtle.turnLeft() end
  146. S.facing=dir; report("idle",{kind="face",dir=dir}); return true
  147. end
  148. local function digForward() while turtle.detect() do turtle.dig(); sleep(0.05) end end
  149. local function stepForward()
  150. local tries=0
  151. digForward()
  152. while not turtle.forward() do
  153. digForward(); tries=tries+1
  154. if tries>60 then return false,"blocked" end
  155. sleep(0.05)
  156. end
  157. return true
  158. end
  159. local function stepUp()
  160. while turtle.detectUp() do turtle.digUp(); sleep(0.05) end
  161. local tries=0
  162. while not turtle.up() do
  163. while turtle.detectUp() do turtle.digUp(); sleep(0.05) end
  164. tries=tries+1; if tries>60 then return false,"blocked up" end
  165. sleep(0.05)
  166. end
  167. return true
  168. end
  169. local function stepDown()
  170. while turtle.detectDown() do turtle.digDown(); sleep(0.05) end
  171. local tries=0
  172. while not turtle.down() do
  173. while turtle.detectDown() do turtle.digDown(); sleep(0.05) end
  174. tries=tries+1; if tries>60 then return false,"blocked down" end
  175. sleep(0.05)
  176. end
  177. return true
  178. end
  179.  
  180. local function moveAxis(axis, n)
  181. if n==0 then return true end
  182. local dir, steps = nil, math.abs(n)
  183. if axis=="x" then dir = n>0 and "E" or "W"
  184. elseif axis=="z" then dir = n>0 and "S" or "N"
  185. elseif axis=="y" then
  186. for i=1,steps do
  187. if S.abort then return false,"abort" end
  188. report("moving",{kind="move",axis="y",step=i,total=steps})
  189. local ok,err = (n>0) and stepUp() or stepDown()
  190. if not ok then return false,err end
  191. hb()
  192. end
  193. return true
  194. else return false,"bad axis" end
  195. assert(setFacing(dir))
  196. for i=1,steps do
  197. if S.abort then return false,"abort" end
  198. report("moving",{kind="move",axis=axis,step=i,total=steps})
  199. local ok,err = stepForward()
  200. if not ok then return false,err end
  201. hb()
  202. end
  203. return true
  204. end
  205.  
  206. -- Distance/fuel estimates to get home
  207. local function distToHome()
  208. if not S.pos then return 999999 end
  209. local x,y,z = S.pos.x,S.pos.y,S.pos.z
  210. local d1 = math.abs(x-SHAFT_XZ.x) + math.abs(z-SHAFT_XZ.z)
  211. local d2 = math.abs(HOME.y - y)
  212. local d3 = math.abs(HOME.z - (-1092))
  213. return d1 + d2 + d3
  214. end
  215. local function fuelNeededToHome() return distToHome()*FUEL_PER_STEP + FUEL_MIN_BUFFER end
  216.  
  217. -- Routing + base ops
  218. local function gotoPos(tx,ty,tz, order)
  219. order = order or {"y","x","z"}
  220. if not S.pos then gpsPos() end
  221. if not S.pos then return false,"no gps" end
  222. local dx,dy,dz = tx - S.pos.x, ty - S.pos.y, tz - S.pos.z
  223. local map={x=dx,y=dy,z=dz}
  224. for _,ax in ipairs(order) do
  225. local ok,err = moveAxis(ax, map[ax]); if not ok then return false,err end
  226. end
  227. return true
  228. end
  229. local function faceTo(target)
  230. if not S.pos then return false,"no gps" end
  231. local dx, dz = target.x - S.pos.x, target.z - S.pos.z
  232. 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
  233. end
  234. local function returnToBase()
  235. report("returning",{kind="service",stage="to_shaft"}); assert(gotoPos(SHAFT_XZ.x, S.pos.y, SHAFT_XZ.z, {"x","z"}))
  236. report("returning",{kind="service",stage="shaft"}); assert(gotoPos(SHAFT_XZ.x, HOME.y, SHAFT_XZ.z, {"y"}))
  237. report("returning",{kind="service",stage="home"}); assert(gotoPos(HOME.x, HOME.y, HOME.z, {"z"}))
  238. return true
  239. end
  240. local function dumpAll()
  241. assert(gotoPos(CHEST_DUMP.x, CHEST_DUMP.y, CHEST_DUMP.z, {"x","y","z"}))
  242. faceTo({x=CHEST_DUMP.x+1, z=CHEST_DUMP.z})
  243. for i=1,16 do
  244. local d=turtle.getItemDetail(i)
  245. if d then
  246. turtle.select(i)
  247. if turtle.refuel(0) then else turtle.drop() end
  248. end
  249. end
  250. turtle.select(1); return true
  251. end
  252. local function refuelFromChest()
  253. assert(gotoPos(CHEST_FUEL.x, CHEST_FUEL.y, CHEST_FUEL.z, {"x","y","z"}))
  254. faceTo({x=CHEST_FUEL.x+1, z=CHEST_FUEL.z})
  255. local pulls=0
  256. while turtle.getFuelLevel() < REFUEL_TARGET and pulls<64 do
  257. if turtle.suck() then
  258. for i=1,16 do turtle.select(i); if turtle.refuel(0) then turtle.refuel() end end
  259. turtle.select(1)
  260. else break end
  261. pulls=pulls+1
  262. end
  263. return true
  264. end
  265. local function serviceIfNeeded()
  266. local need = fuelNeededToHome()
  267. if S.fuel < need then
  268. report("service",{kind="low_fuel", need=need, have=S.fuel})
  269. assert(returnToBase()); assert(dumpAll()); assert(refuelFromChest()); assert(gotoPos(HOME.x, HOME.y, HOME.z))
  270. report("idle",{kind="serviced"}); return true
  271. end
  272. local inv = invStats()
  273. if inv.freeSlots <= 1 then
  274. report("service",{kind="dump"})
  275. assert(returnToBase()); assert(dumpAll()); assert(refuelFromChest()); assert(gotoPos(HOME.x, HOME.y, HOME.z))
  276. report("idle",{kind="serviced"}); return true
  277. end
  278. return false
  279. end
  280.  
  281. -- Mining (single axis only)
  282. local function mineVector(axis, len, stopOnAir, stopOnDirt)
  283. len = tonumber(len) or 0
  284. if len==0 then return true end
  285. serviceIfNeeded()
  286.  
  287. if axis=="y" then
  288. local steps=math.abs(len)
  289. for i=1,steps do
  290. if S.abort then return false,"abort" end
  291. report("ACTIVE",{kind="work",step=i,total=steps})
  292. local ok,err = (len>0) and stepUp() or stepDown()
  293. if not ok then return false,err end
  294. hb(); serviceIfNeeded()
  295. end
  296. report("idle"); return true
  297. end
  298.  
  299. local dir = (axis=="x") and ((len>0) and "E" or "W") or (axis=="z") and ((len>0) and "S" or "N") or nil
  300. if not dir then return false,"bad axis" end
  301. assert(setFacing(dir))
  302. local steps = math.abs(len)
  303. for i=1,steps do
  304. if S.abort then return false,"abort" end
  305. if stopOnAir or stopOnDirt then
  306. local ok, data = turtle.inspect()
  307. if ok and stopOnDirt and data and data.name and data.name:find("dirt") then report("STOPPED",{kind="halt_dirt"}); return true end
  308. if (not ok) and stopOnAir then report("STOPPED",{kind="halt_air"}); return true end
  309. end
  310. report("ACTIVE",{kind="work",step=i,total=steps})
  311. digForward()
  312. local ok,err = stepForward()
  313. if not ok then return false,err end
  314. hb(); serviceIfNeeded()
  315. end
  316. report("idle"); return true
  317. end
  318.  
  319. -- Debug commands
  320. local function dbg_echo(msg) return true, ("echo: "..tostring(msg)) end
  321. local function dbg_uptime() return true, uptime() end
  322. local function dbg_toggle_ui(on) S.uiOn = on~=false; return true, "ui:"..tostring(S.uiOn) end
  323. local function dbg_where() return true, S.pos end
  324. local function dbg_gps()
  325. local x,y,z = gps.locate(3)
  326. local ids = {rednet.lookup("gps")}
  327. local n=0 for _ in pairs(ids) do n=n+1 end
  328. return x~=nil, {x=x,y=y,z=z, fixAge=fixAge(), beacons=n}
  329. end
  330. local function dbg_net()
  331. local list={}
  332. for _,n in ipairs(peripheral.getNames()) do table.insert(list, n..":"..peripheral.getType(n)) end
  333. return true, {periph=list}
  334. end
  335.  
  336. -- Threads
  337. local function uiThread() while true do drawUI(); sleep(UI_REFRESH) end end
  338. local function hbThread() while true do sleep(HEARTBEAT_SEC); hb() end end
  339. local function rxThread()
  340. while true do
  341. local sid,msg,proto = rednet.receive(PROTOCOL)
  342. if proto==PROTOCOL and type(msg)=="table" and msg.op=="cmd" then
  343. local ok,err=false,nil
  344. if msg.act=="face" then ok,err=setFacing(msg.dir)
  345. elseif msg.act=="up" then ok,err=moveAxis("y", math.abs(msg.n or 1))
  346. elseif msg.act=="down" then ok,err=moveAxis("y", -math.abs(msg.n or 1))
  347. elseif msg.act=="mine_vector" then ok,err=mineVector(msg.axis, msg.len, msg.stopOnAir, msg.stopOnDirt)
  348. elseif msg.act=="return_service" then ok,err=returnToBase()
  349. elseif msg.act=="dump" then ok,err=dumpAll()
  350. elseif msg.act=="refuel" then ok,err=refuelFromChest()
  351. elseif msg.act=="set_facing_tag" then S.facing=msg.dir or S.facing; ok=true
  352. 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
  353. elseif msg.act=="abort" then S.abort=true; ok=true
  354. elseif msg.act=="resume" then S.abort=false; ok=true
  355. elseif msg.act=="stop" then S.stop=true; ok=true
  356. -- DEBUG:
  357. elseif msg.act=="dbg_echo" then ok,err=dbg_echo(msg.msg)
  358. elseif msg.act=="dbg_net" then ok,err=dbg_net()
  359. elseif msg.act=="dbg_gps" then ok,err=dbg_gps()
  360. elseif msg.act=="dbg_uptime" then ok,err=dbg_uptime()
  361. elseif msg.act=="dbg_toggle_ui" then ok,err=dbg_toggle_ui(msg.on)
  362. elseif msg.act=="dbg_where" then ok,err=dbg_where()
  363. else err="unknown act" end
  364. S.lastAck = (ok and "ok") or ("err:"..tostring(err))
  365. rednet.send(sid,{op="ack", ok=ok, err=err, ref=msg.ref}, PROTOCOL)
  366. hb()
  367. end
  368. end
  369. end
  370.  
  371. report("idle",{kind="boot"})
  372. parallel.waitForAny(uiThread, hbThread, rxThread)
  373.  
Advertisement
Add Comment
Please, Sign In to add comment