robathome

RoboMiner_controller

Oct 18th, 2025 (edited)
322
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.89 KB | None | 0 0
  1. -- controller_startup.lua
  2. -- Two-display controller: LEFT 4x4 list + MAP on the other monitor.
  3. -- Wireless Ender modem required. Supports pocket RPC and goto().
  4. -- Map shows controller, GPS nodes, and turtles with high-contrast colors.
  5.  
  6. -- ================= CONFIG =================
  7. local CONFIG = {
  8. repl = false, -- set true to enable inline REPL
  9. controllerPos = { x = -732, y = 66, z = -322 },
  10. gpsNodes = {
  11. {x=-715,y=68,z=-327},
  12. {x=-732,y=67,z=-344},
  13. {x=-762,y=69,z=-329},
  14. {x=-745,y=66,z=-312},
  15. {x=-725,y=72,z=-300},
  16. },
  17. map = {
  18. origin = { x = -769, z = -351 }, -- top-left world X/Z at map cell (1,1)
  19. scale = 1, -- blocks per pixel
  20. follow = false,
  21. }
  22. }
  23. -- ==========================================
  24.  
  25. local PROTOCOL = "minesys:v1" -- miners: status/acks/cmds
  26. local CTL_PROTO = "minesys:ctl" -- controller RPC (goto, clear, etc.)
  27.  
  28. -- ---------- Peripherals ----------
  29. local modem = peripheral.find("modem", function(_,p) return p.isWireless() end)
  30. if not modem then error("No wireless modem") end
  31. rednet.open(peripheral.getName(modem))
  32. pcall(function() rednet.host(CTL_PROTO, "miner_controller") end)
  33.  
  34. -- find two monitors; use the 4x4 merged as LIST, the other as MAP
  35. local mons = { peripheral.find("monitor") }
  36. if #mons < 2 then error("Need two monitors attached") end
  37. local function pickMonitors()
  38. local listMon, mapMon
  39. for _,m in ipairs(mons) do
  40. m.setTextScale(0.5)
  41. local w,h = m.getSize()
  42. if w==4 and h==4 then listMon = m else mapMon = m end
  43. end
  44. if not listMon then
  45. table.sort(mons, function(a,b) local aw,ah=a.getSize(); local bw,bh=b.getSize(); return aw*ah < bw*bh end)
  46. listMon, mapMon = mons[1], mons[2]
  47. end
  48. return listMon, mapMon
  49. end
  50. local monList, monMap = pickMonitors()
  51.  
  52. -- ---------- State ----------
  53. local function now() return os.epoch("utc") end
  54. local turtles = {} -- id-> {id,label,pos,fuel,facing,status,freeSlots,freePct,tSeen}
  55. local function count(t) local n=0 for _ in pairs(t) do n=n+1 end return n end
  56.  
  57. local function upsertStatus(m)
  58. local key = m.id or m.label
  59. if not key then return end
  60. local t = turtles[key] or {}
  61. t.id = m.id or t.id
  62. t.label = m.label or t.label or ("t"..tostring(m.id or ""))
  63. t.pos = m.pos or t.pos
  64. t.fuel = m.fuel or t.fuel
  65. t.facing = m.facing or t.facing
  66. t.status = m.status or t.status or "?"
  67. if m.extra and m.extra.inv then
  68. t.freeSlots = m.extra.inv.freeSlots
  69. t.freePct = m.extra.inv.freePct
  70. end
  71. t.tSeen = now()
  72. turtles[key] = t
  73. end
  74.  
  75. -- ---------- Commands to miners ----------
  76. local function sendCmd(target, tbl)
  77. tbl.op="cmd"; tbl.ref=math.random(1,1e9)
  78. local tgtID=nil
  79. for _,t in pairs(turtles) do
  80. if tostring(t.id)==tostring(target) or t.label==target then tgtID=t.id break end
  81. end
  82. if not tgtID then return false,"unknown target" end
  83. rednet.send(tgtID, tbl, PROTOCOL)
  84. local tm=os.startTimer(3)
  85. while true do
  86. local ev,a,b,c = os.pullEvent()
  87. if ev=="rednet_message" then
  88. local sid,msg,proto=a,b,c
  89. if proto==PROTOCOL and type(msg)=="table" and msg.op=="ack" and msg.ref==tbl.ref then
  90. return msg.ok, msg.err
  91. end
  92. elseif ev=="timer" and a==tm then return false,"ack timeout" end
  93. end
  94. end
  95. local function face(lbl,dir) return sendCmd(lbl,{act="face",dir=dir}) end
  96. local function forward(lbl,n) return sendCmd(lbl,{act="forward",n=n}) end
  97. local function up(lbl,n) return sendCmd(lbl,{act="up",n=n}) end
  98. local function down(lbl,n) return sendCmd(lbl,{act="down",n=n}) end
  99. local function clearArea(lbl,L,W,H,dir) return sendCmd(lbl,{act="clear_area",length=L,width=W,height=H,dir=dir}) end
  100. local function setFacingTag(lbl,dir) return sendCmd(lbl,{act="set_facing_tag",dir=dir}) end
  101.  
  102. -- Move along one axis only
  103. local function moveAxis(lbl, axis, delta)
  104. if delta==0 then return true end
  105. if axis=="x" then
  106. if delta>0 then assert(face(lbl,"E")) else assert(face(lbl,"W")) end
  107. return forward(lbl, math.abs(delta))
  108. elseif axis=="z" then
  109. if delta>0 then assert(face(lbl,"S")) else assert(face(lbl,"N")) end
  110. return forward(lbl, math.abs(delta))
  111. elseif axis=="y" then
  112. if delta>0 then return up(lbl, math.abs(delta)) else return down(lbl, math.abs(-delta)) end
  113. else
  114. return false,"bad axis"
  115. end
  116. end
  117.  
  118. -- goto composed of axis-only moves in selected order
  119. local function gotoXYZ(tgtLabel, tx, ty, tz, order)
  120. local t = nil
  121. for _,v in pairs(turtles) do if v.label==tgtLabel then t=v break end end
  122. if not t or not t.pos then return false,"unknown turtle or position" end
  123. local cx, cy, cz = t.pos.x, t.pos.y, t.pos.z
  124. local dx, dy, dz = tx-cx, ty-cy, tz-cz
  125. order = order or {"y","x","z"}
  126. local map = {x=dx,y=dy,z=dz}
  127. for _,ax in ipairs(order) do
  128. local ok,err = moveAxis(tgtLabel, ax, map[ax])
  129. if not ok then return false,err end
  130. end
  131. return true
  132. end
  133.  
  134. -- ---------- LIST (left 4x4) ----------
  135. local function drawList()
  136. local m = monList
  137. m.setBackgroundColor(colors.black); m.setTextColor(colors.white); m.clear()
  138. m.setCursorPos(1,1); m.write("T:"..count(turtles))
  139. -- Very compact 4x4: each turtle uses one row: Lbl St Fuel %Free
  140. -- Position is shown briefly in row 4 as the most recent turtle seen.
  141. local y = 2
  142. local lastPosTxt = nil
  143. for _,t in pairs(turtles) do
  144. if y>3 then break end
  145. m.setCursorPos(1,y)
  146. local lab = (t.label or "?"):sub(1,4)
  147. local st = (t.status or "?"):sub(1,4)
  148. local fu = t.fuel and tostring(t.fuel) or "?"
  149. local fp = t.freePct and string.format("%2d", math.floor(t.freePct+0.5)) or "--"
  150. local line = string.format("%-4s%-4s", lab, st)
  151. m.write(line)
  152. local w,_ = m.getSize()
  153. m.setCursorPos(w-3,y); m.write(string.format("%3s", fu)) -- fuel right
  154. m.setCursorPos(w-1,y); m.write(fp) -- % free
  155. if t.pos then lastPosTxt = ("x% d z% d"):format(t.pos.x, t.pos.z) end
  156. y = y + 1
  157. end
  158. if lastPosTxt then
  159. m.setCursorPos(1,4); m.write(lastPosTxt:sub(1,4)) -- hint of position due to tight space
  160. end
  161. end
  162.  
  163. -- ---------- MAP (top-down XZ) ----------
  164. local function worldToMap(x,z)
  165. local w,h = monMap.getSize()
  166. local ox, oz = CONFIG.map.origin.x, CONFIG.map.origin.z
  167. local s = CONFIG.map.scale
  168. local col = math.floor((x - ox)/s) + 1
  169. local row = math.floor((z - oz)/s) + 1
  170. if col < 1 or row < 1 or col > w or row > h then return nil,nil end
  171. return col,row
  172. end
  173.  
  174. local function maybeFollow()
  175. if not CONFIG.map.follow then return end
  176. for _,t in pairs(turtles) do
  177. if t.pos then
  178. CONFIG.map.origin.x = t.pos.x - 10*CONFIG.map.scale
  179. CONFIG.map.origin.z = t.pos.z - 6*CONFIG.map.scale
  180. return
  181. end
  182. end
  183. end
  184.  
  185. local function drawMap()
  186. maybeFollow()
  187. local m = monMap
  188. m.setBackgroundColor(colors.black); m.clear()
  189. local w,h = m.getSize()
  190.  
  191. -- High-contrast border and grid
  192. m.setTextColor(colors.lightGray)
  193. for x=1,w do m.setCursorPos(x,1); m.write("─"); m.setCursorPos(x,h); m.write("─") end
  194. for y=1,h do m.setCursorPos(1,y); m.write("│"); m.setCursorPos(w,y); m.write("│") end
  195. m.setCursorPos(1,1); m.write("┌"); m.setCursorPos(w,1); m.write("┐")
  196. m.setCursorPos(1,h); m.write("└"); m.setCursorPos(w,h); m.write("┘")
  197.  
  198. -- faint grid points every 4 cells
  199. for r=2,h-1 do
  200. for c=2,w-1 do
  201. if ((c-2) % 4 == 0) and ((r-2) % 4 == 0) then
  202. m.setCursorPos(c,r); m.setTextColor(colors.gray); m.write(".")
  203. end
  204. end
  205. end
  206.  
  207. -- Legend
  208. m.setCursorPos(3,1); m.setTextColor(colors.white); m.write("MAP C/G/T")
  209.  
  210. -- Controller
  211. local cx,cz = CONFIG.controllerPos.x, CONFIG.controllerPos.z
  212. local mc, mr = worldToMap(cx, cz)
  213. if mc then m.setCursorPos(mc,mr); m.setTextColor(colors.cyan); m.write("C") end
  214.  
  215. -- GPS nodes
  216. for _,g in ipairs(CONFIG.gpsNodes) do
  217. local gc,gr = worldToMap(g.x, g.z)
  218. if gc then m.setCursorPos(gc,gr); m.setTextColor(colors.magenta); m.write("G") end
  219. end
  220.  
  221. -- Turtles (lime good, yellow low space, red near full)
  222. for _,t in pairs(turtles) do
  223. if t.pos then
  224. local tc,tr = worldToMap(t.pos.x, t.pos.z)
  225. if tc then
  226. local col = colors.lime
  227. if t.freePct then
  228. if t.freePct < 10 then col = colors.red
  229. elseif t.freePct < 30 then col = colors.yellow
  230. else col = colors.lime end
  231. end
  232. m.setCursorPos(tc,tr); m.setTextColor(col); m.write("T")
  233. end
  234. end
  235. end
  236. end
  237.  
  238. -- ---------- Controller RPC server (for pocket 'goto', etc.) ----------
  239. local function ctlServer()
  240. while true do
  241. local sid, msg, proto = rednet.receive(CTL_PROTO)
  242. if proto==CTL_PROTO and type(msg)=="table" and msg.op=="req" then
  243. local ok, err
  244. if msg.act=="goto" then
  245. ok, err = gotoXYZ(msg.label, msg.x, msg.y, msg.z, msg.order)
  246. elseif msg.act=="clear" then
  247. ok, err = clearArea(msg.label, msg.L, msg.W, msg.H, msg.dir)
  248. elseif msg.act=="face" then
  249. ok, err = face(msg.label, msg.dir)
  250. elseif msg.act=="fwd" then
  251. ok, err = forward(msg.label, msg.n)
  252. elseif msg.act=="up" then
  253. ok, err = up(msg.label, msg.n)
  254. elseif msg.act=="dn" then
  255. ok, err = down(msg.label, msg.n)
  256. else
  257. ok, err = false, "unknown act"
  258. end
  259. rednet.send(sid, {op="resp", ref=msg.ref, ok=ok, err=err}, CTL_PROTO)
  260. end
  261. end
  262. end
  263.  
  264. -- ---------- Networking + UI ----------
  265. local function netUiThread()
  266. while true do
  267. local id,msg,proto = rednet.receive(PROTOCOL, 0.2)
  268. if id and proto==PROTOCOL and type(msg)=="table" and msg.op=="status" then
  269. upsertStatus(msg)
  270. end
  271. drawList()
  272. drawMap()
  273. end
  274. end
  275.  
  276. -- Optional inline REPL for manual driving
  277. local function replThread()
  278. term.setCursorPos(1,1); print("Controller REPL. help for cmds.")
  279. while true do
  280. io.write("> ")
  281. local line = read()
  282. local a,b,c,d,e,f = line:match("^(%S+)%s*(%S*)%s*(%S*)%s*(%S*)%s*(%S*)%s*(%S*)")
  283. if a=="help" then
  284. print("fac <label> <N|E|S|W>")
  285. print("fwd <label> <n>")
  286. print("up <label> <n>")
  287. print("dn <label> <n>")
  288. print("clear <label> <L> <W> <H> <N|E|S|W>")
  289. print("tagface <label> <N|E|S|W>")
  290. print("goto <label> <x> <y> <z> [order yxz|xyz]")
  291. elseif a=="fac" then print(face(b,c))
  292. elseif a=="fwd" then print(forward(b,tonumber(c)))
  293. elseif a=="up" then print(up(b,tonumber(c)))
  294. elseif a=="dn" then print(down(b,tonumber(c)))
  295. elseif a=="clear" then print(clearArea(b,tonumber(c),tonumber(d),tonumber(e),f))
  296. elseif a=="tagface" then print(setFacingTag(b,c))
  297. elseif a=="goto" then
  298. local order = (f and #f==3) and {f:sub(1,1),f:sub(2,2),f:sub(3,3)} or nil
  299. print(gotoXYZ(b, tonumber(c), tonumber(d), tonumber(e), order))
  300. else print("unknown") end
  301. end
  302. end
  303.  
  304. if CONFIG.repl then
  305. parallel.waitForAny(netUiThread, ctlServer, replThread)
  306. else
  307. parallel.waitForAny(netUiThread, ctlServer)
  308. end
  309.  
  310.  
Advertisement
Add Comment
Please, Sign In to add comment