SHOW:
|
|
- or go back to the newest paste.
1 | -------------------------------------------------------------------------------- | |
2 | - | -- Mining Robot with Remote Commands and Configuration |
2 | + | -- Wireless Robot Control Program (Handheld) with Configuration UI |
3 | -- | |
4 | - | -- On startup, the turtle (named "Minerobot <ID>") connects to rednet and waits |
4 | + | -- This program runs on your handheld remote computer. It: |
5 | - | -- for configuration from the handheld. The configuration sets the mine size |
5 | + | |
6 | - | -- (width and height) and the turtle’s starting facing direction. |
6 | + | -- 1. Listens for robots to check in (they send "checkin:<name>"). |
7 | -- 2. Displays a main list of robots as colored buttons. | |
8 | - | -- After configuration, the turtle responds to these remote commands: |
8 | + | -- 3. When a robot button is touched, shows a submenu with options: |
9 | - | -- "stats" - Sends its current coordinates. |
9 | + | -- Stats, Start, Stop, Home, Config. |
10 | - | -- "start" - Begins mining with the configured parameters. |
10 | + | -- 4. The Stats option sends "stats" and waits for a reply. |
11 | - | -- "stop" - Stops mining after finishing the current layer. |
11 | + | -- 5. The Config option brings up a configuration screen that lets you |
12 | - | -- "home" - Returns to the starting position. |
12 | + | -- adjust the mine width, mine height, and starting facing direction. |
13 | - | -- "config:<width>:<height>:<facing>" - Updates configuration. |
13 | + | -- (Facing: 0=East, 1=South, 2=West, 3=North.) |
14 | -- 6. The configuration is sent to the robot in the format: | |
15 | - | -- ASSUMPTIONS: |
15 | + | -- "config:<width>:<height>:<facing>" |
16 | - | -- - Starting position is (0,0,0). |
16 | + | |
17 | - | -- - A chest is behind the starting point. |
17 | + | -- Press 'r' to refresh the robot list. |
18 | - | -- - Wireless modem attached on side "back" (adjust if needed). |
18 | + | |
19 | ||
20 | -- Open rednet on the modem on the specified side. | |
21 | - | -- CONFIGURATION defaults (will be updated via remote command) |
21 | + | local modemSide = "back" |
22 | - | local areaWidth = 16 |
22 | + | |
23 | - | local areaHeight = 16 |
23 | + | |
24 | - | local startFacing = 0 -- 0=East, 1=South, 2=West, 3=North |
24 | + | |
25 | ||
26 | - | local sleepTime = 0.1 |
26 | + | local robots = {} -- key: robot ID, value: {name, lastCheckin} |
27 | ||
28 | - | -- Position and orientation tracking (starting at (0,0,0)) |
28 | + | |
29 | - | local posX, posY, posZ = 0, 0, 0 |
29 | + | -- Helper: Draw Button |
30 | - | local facing = startFacing -- will be updated with config |
30 | + | |
31 | local function drawButton(button) | |
32 | - | local startX, startY, startZ = 0, 0, 0 |
32 | + | term.setBackgroundColor(button.bgColor) |
33 | term.setTextColor(button.textColor) | |
34 | - | -- Control flags |
34 | + | for y = button.y1, button.y2 do |
35 | - | local miningActive = false |
35 | + | term.setCursorPos(button.x1, y) |
36 | - | local stopMining = false |
36 | + | term.write(string.rep(" ", button.x2 - button.x1 + 1)) |
37 | end | |
38 | - | -- Modem configuration |
38 | + | local textLength = #button.text |
39 | - | local modemSide = "left" |
39 | + | local btnWidth = button.x2 - button.x1 + 1 |
40 | local startX = button.x1 + math.floor((btnWidth - textLength) / 2) | |
41 | local centerY = math.floor((button.y1 + button.y2) / 2) | |
42 | term.setCursorPos(startX, centerY) | |
43 | term.write(button.text) | |
44 | - | local robotName = "Minerobot " .. os.getComputerID() |
44 | + | |
45 | - | rednet.broadcast("checkin:" .. robotName, "control") |
45 | + | |
46 | - | print("Checked in as " .. robotName) |
46 | + | local function inButton(x, y, button) |
47 | - | print("Waiting for configuration...") |
47 | + | return x >= button.x1 and x <= button.x2 and y >= button.y1 and y <= button.y2 |
48 | end | |
49 | ||
50 | - | -- INVENTORY & FUEL FUNCTIONS |
50 | + | |
51 | -- Rednet Check-In Listener | |
52 | - | local function inventoryFull() |
52 | + | |
53 | - | for slot = 1, 14 do |
53 | + | local function handleRednetMessages() |
54 | - | if turtle.getItemCount(slot) == 0 then |
54 | + | |
55 | - | return false |
55 | + | local sender, message, protocol = rednet.receive() |
56 | if type(message) == "string" then | |
57 | local cmd, data = message:match("^(%w+):?(.*)") | |
58 | - | return true |
58 | + | if cmd == "checkin" then |
59 | local name = (data ~= "" and data) or ("Robot " .. sender) | |
60 | robots[sender] = { name = name, lastCheckin = os.time() } | |
61 | - | local function dumpInventory() |
61 | + | print("Robot checked in: " .. sender .. " (" .. name .. ")") |
62 | - | for slot = 1, 14 do |
62 | + | |
63 | - | turtle.select(slot) |
63 | + | |
64 | - | turtle.drop() |
64 | + | |
65 | end | |
66 | - | turtle.select(1) |
66 | + | |
67 | -------------------------------------------------------------------------------- | |
68 | -- Draw Main List of Robots as Buttons | |
69 | - | local function ensureFuel() |
69 | + | |
70 | - | if turtle.getFuelLevel() == "unlimited" then return end |
70 | + | local function drawMainList() |
71 | - | local threshold = 10 |
71 | + | term.clear() |
72 | - | if turtle.getFuelLevel() < threshold then |
72 | + | term.setCursorPos(1,1) |
73 | - | print("Low fuel (" .. turtle.getFuelLevel() .. "). Refueling...") |
73 | + | term.setBackgroundColor(colors.black) |
74 | - | for slot = 1, 16 do |
74 | + | term.setTextColor(colors.white) |
75 | - | turtle.select(slot) |
75 | + | print("Robot Control Main List (touch a robot)") |
76 | - | turtle.refuel(1) |
76 | + | print("Press 'r' to refresh") |
77 | - | if turtle.getFuelLevel() >= threshold then break end |
77 | + | local btns = {} |
78 | local btnY = 4 | |
79 | - | if turtle.getFuelLevel() < threshold then |
79 | + | local btnHeight = 3 |
80 | - | error("Not enough fuel. Please add more fuel and restart.") |
80 | + | local btnMargin = 1 |
81 | local index = 1 | |
82 | for id, info in pairs(robots) do | |
83 | local btn = { | |
84 | x1 = 2, | |
85 | y1 = btnY, | |
86 | - | -- TURNING & FACING FUNCTIONS |
86 | + | x2 = term.getSize() - 2, |
87 | y2 = btnY + btnHeight - 1, | |
88 | - | local function turnLeft() |
88 | + | bgColor = colors.blue, |
89 | - | turtle.turnLeft() |
89 | + | textColor = colors.white, |
90 | - | facing = (facing + 3) % 4 |
90 | + | text = info.name .. " (ID:" .. id .. ")" |
91 | } | |
92 | drawButton(btn) | |
93 | - | local function turnRight() |
93 | + | btns[index] = { id = id, btn = btn } |
94 | - | turtle.turnRight() |
94 | + | btnY = btnY + btnHeight + btnMargin |
95 | - | facing = (facing + 1) % 4 |
95 | + | index = index + 1 |
96 | end | |
97 | return btns | |
98 | - | local function turnAround() |
98 | + | |
99 | - | turtle.turnLeft() |
99 | + | |
100 | - | turtle.turnLeft() |
100 | + | |
101 | - | facing = (facing + 2) % 4 |
101 | + | -- Draw Submenu for a Selected Robot (Stats, Start, Stop, Home, Config) |
102 | -------------------------------------------------------------------------------- | |
103 | local function drawRobotMenu(robotId) | |
104 | - | local function faceDirection(dir) |
104 | + | term.clear() |
105 | - | local diff = (dir - facing) % 4 |
105 | + | term.setCursorPos(1,1) |
106 | - | if diff == 1 then |
106 | + | term.setBackgroundColor(colors.black) |
107 | - | turnRight() |
107 | + | term.setTextColor(colors.white) |
108 | - | elseif diff == 2 then |
108 | + | local robot = robots[robotId] |
109 | - | turnAround() |
109 | + | print("Robot: " .. robot.name .. " (ID:" .. robotId .. ")") |
110 | - | elseif diff == 3 then |
110 | + | print("Select a command:") |
111 | - | turnLeft() |
111 | + | local btnWidth = math.floor((term.getSize() - 6) / 2) |
112 | local btnHeight = 3 | |
113 | local startX = 2 | |
114 | local startY = 4 | |
115 | local cmds = { | |
116 | - | -- MOVEMENT FUNCTIONS |
116 | + | {text="Stats", cmd="stats", bgColor=colors.lime}, |
117 | {text="Start", cmd="start", bgColor=colors.green}, | |
118 | - | local function mineForwardStep() |
118 | + | {text="Stop", cmd="stop", bgColor=colors.red}, |
119 | - | if inventoryFull() then |
119 | + | {text="Home", cmd="home", bgColor=colors.orange}, |
120 | - | return false |
120 | + | {text="Config", cmd="config", bgColor=colors.cyan} |
121 | } | |
122 | - | ensureFuel() |
122 | + | local btns = {} |
123 | - | turtle.dig() |
123 | + | for i = 1, #cmds do |
124 | - | while not turtle.forward() do |
124 | + | local col = ((i - 1) % 2) |
125 | - | if turtle.detect() then turtle.dig() end |
125 | + | local row = math.floor((i - 1) / 2) |
126 | - | sleep(sleepTime) |
126 | + | local btn = { |
127 | x1 = startX + col * (btnWidth + 2), | |
128 | - | if facing == 0 then |
128 | + | y1 = startY + row * (btnHeight + 1), |
129 | - | posX = posX + 1 |
129 | + | x2 = startX + col * (btnWidth + 2) + btnWidth - 1, |
130 | - | elseif facing == 1 then |
130 | + | y2 = startY + row * (btnHeight + 1) + btnHeight - 1, |
131 | - | posZ = posZ + 1 |
131 | + | bgColor = cmds[i].bgColor, |
132 | - | elseif facing == 2 then |
132 | + | textColor = colors.white, |
133 | - | posX = posX - 1 |
133 | + | text = cmds[i].text |
134 | - | elseif facing == 3 then |
134 | + | } |
135 | - | posZ = posZ - 1 |
135 | + | drawButton(btn) |
136 | btns[i] = { cmd = cmds[i].cmd, btn = btn } | |
137 | - | turtle.digUp() |
137 | + | |
138 | - | turtle.digDown() |
138 | + | local backBtn = { |
139 | - | return true |
139 | + | x1 = 2, |
140 | y1 = term.getSize() - 3, | |
141 | x2 = term.getSize() - 2, | |
142 | - | local function moveForwardNoDump() |
142 | + | y2 = term.getSize() - 1, |
143 | - | ensureFuel() |
143 | + | bgColor = colors.gray, |
144 | - | turtle.dig() |
144 | + | textColor = colors.white, |
145 | - | while not turtle.forward() do |
145 | + | text = "Back" |
146 | - | if turtle.detect() then turtle.dig() end |
146 | + | } |
147 | - | sleep(sleepTime) |
147 | + | drawButton(backBtn) |
148 | btns[#cmds+1] = { cmd = "back", btn = backBtn } | |
149 | - | if facing == 0 then |
149 | + | return btns |
150 | - | posX = posX + 1 |
150 | + | |
151 | - | elseif facing == 1 then |
151 | + | |
152 | - | posZ = posZ + 1 |
152 | + | |
153 | - | elseif facing == 2 then |
153 | + | -- Configuration UI for a Robot |
154 | - | posX = posX - 1 |
154 | + | -- Allows setting mine width, height, and starting facing direction. |
155 | - | elseif facing == 3 then |
155 | + | |
156 | - | posZ = posZ - 1 |
156 | + | local function configureRobot(robotId) |
157 | local configWidth = 16 | |
158 | local configHeight = 16 | |
159 | local configFacing = 0 -- 0=East, 1=South, 2=West, 3=North | |
160 | - | local function moveUpNoDump() |
160 | + | local directions = {"East", "South", "West", "North"} |
161 | - | ensureFuel() |
161 | + | |
162 | - | turtle.digUp() |
162 | + | local function redrawConfig() |
163 | - | while not turtle.up() do |
163 | + | term.clear() |
164 | - | turtle.digUp() |
164 | + | term.setCursorPos(1,1) |
165 | - | sleep(sleepTime) |
165 | + | print("Configure Robot " .. robotId) |
166 | print("---------------------") | |
167 | - | posY = posY + 1 |
167 | + | print("Mine Width: " .. configWidth) |
168 | print("Mine Height: " .. configHeight) | |
169 | print("Facing: " .. directions[configFacing+1]) | |
170 | - | local function moveDownNoDump() |
170 | + | |
171 | - | ensureFuel() |
171 | + | |
172 | - | turtle.digDown() |
172 | + | redrawConfig() |
173 | - | while not turtle.down() do |
173 | + | -- We'll use a simple loop waiting for key presses to adjust values. |
174 | - | turtle.digDown() |
174 | + | -- For touch, we define colored buttons. |
175 | - | sleep(sleepTime) |
175 | + | local btns = {} |
176 | local screenWidth, screenHeight = term.getSize() | |
177 | - | posY = posY - 1 |
177 | + | -- Define buttons for width (- and +) |
178 | btns[1] = {cmd="wdec", btn = {x1=2, y1=6, x2=12, y2=8, bgColor=colors.red, textColor=colors.white, text="- Width"}} | |
179 | btns[2] = {cmd="winc", btn = {x1=14, y1=6, x2=screenWidth-2, y2=8, bgColor=colors.green, textColor=colors.white, text="+ Width"}} | |
180 | -- Buttons for height | |
181 | - | -- 3D NAVIGATION FUNCTION |
181 | + | btns[3] = {cmd="hdec", btn = {x1=2, y1=9, x2=12, y2=11, bgColor=colors.red, textColor=colors.white, text="- Height"}} |
182 | btns[4] = {cmd="hinc", btn = {x1=14, y1=9, x2=screenWidth-2, y2=11, bgColor=colors.green, textColor=colors.white, text="+ Height"}} | |
183 | - | local function navigateTo3DNoDump(tx, ty, tz) |
183 | + | -- Buttons for facing |
184 | - | while posY < ty do |
184 | + | btns[5] = {cmd="fdec", btn = {x1=2, y1=12, x2=12, y2=14, bgColor=colors.red, textColor=colors.white, text="- Facing"}} |
185 | - | moveUpNoDump() |
185 | + | btns[6] = {cmd="finc", btn = {x1=14, y1=12, x2=screenWidth-2, y2=14, bgColor=colors.green, textColor=colors.white, text="+ Facing"}} |
186 | -- Send and Cancel buttons | |
187 | - | while posY > ty do |
187 | + | btns[7] = {cmd="send", btn = {x1=2, y1=screenHeight-4, x2=math.floor(screenWidth/2)-1, y2=screenHeight-2, bgColor=colors.lime, textColor=colors.white, text="Send Config"}} |
188 | - | moveDownNoDump() |
188 | + | btns[8] = {cmd="cancel", btn = {x1=math.floor(screenWidth/2)+1, y1=screenHeight-4, x2=screenWidth-2, y2=screenHeight-2, bgColor=colors.gray, textColor=colors.white, text="Cancel"}} |
189 | ||
190 | - | if posX < tx then |
190 | + | -- Draw all buttons |
191 | - | faceDirection(0) |
191 | + | for i = 1, #btns do |
192 | - | while posX < tx do |
192 | + | drawButton(btns[i].btn) |
193 | - | moveForwardNoDump() |
193 | + | |
194 | ||
195 | - | elseif posX > tx then |
195 | + | |
196 | - | faceDirection(2) |
196 | + | local event, button, x, y = os.pullEvent("mouse_click") |
197 | - | while posX > tx do |
197 | + | for i = 1, #btns do |
198 | - | moveForwardNoDump() |
198 | + | if inButton(x, y, btns[i].btn) then |
199 | local cmd = btns[i].cmd | |
200 | if cmd == "wdec" then | |
201 | - | if posZ < tz then |
201 | + | if configWidth > 1 then configWidth = configWidth - 1 end |
202 | - | faceDirection(1) |
202 | + | elseif cmd == "winc" then |
203 | - | while posZ < tz do |
203 | + | configWidth = configWidth + 1 |
204 | - | moveForwardNoDump() |
204 | + | elseif cmd == "hdec" then |
205 | if configHeight > 1 then configHeight = configHeight - 1 end | |
206 | - | elseif posZ > tz then |
206 | + | elseif cmd == "hinc" then |
207 | - | faceDirection(3) |
207 | + | configHeight = configHeight + 1 |
208 | - | while posZ > tz do |
208 | + | elseif cmd == "fdec" then |
209 | - | moveForwardNoDump() |
209 | + | configFacing = (configFacing - 1) % 4 |
210 | elseif cmd == "finc" then | |
211 | configFacing = (configFacing + 1) % 4 | |
212 | elseif cmd == "send" then | |
213 | -- Send configuration command to robot | |
214 | local configCmd = "config:" .. configWidth .. ":" .. configHeight .. ":" .. configFacing | |
215 | - | -- DUMP INVENTORY ROUTINE |
215 | + | rednet.send(robotId, configCmd) |
216 | term.setCursorPos(1,screenHeight-1) | |
217 | - | local function manageInventoryDump() |
217 | + | print("Config sent: " .. configCmd) |
218 | - | local savedX, savedY, savedZ = posX, posY, posZ |
218 | + | os.sleep(2) |
219 | - | local savedFacing = facing |
219 | + | return |
220 | - | print("Inventory full. Going to dump...") |
220 | + | elseif cmd == "cancel" then |
221 | - | navigateTo3DNoDump(startX, startY, startZ) |
221 | + | return |
222 | - | faceDirection(2) -- face the chest |
222 | + | |
223 | - | dumpInventory() |
223 | + | redrawConfig() |
224 | - | faceDirection(0) |
224 | + | for j = 1, #btns do drawButton(btns[j].btn) end |
225 | - | print("Dump complete. Returning to mining spot...") |
225 | + | |
226 | - | navigateTo3DNoDump(savedX, savedY, savedZ) |
226 | + | |
227 | - | faceDirection(savedFacing) |
227 | + | |
228 | end | |
229 | ||
230 | -------------------------------------------------------------------------------- | |
231 | - | -- DESCEND TO NEXT LAYER FUNCTION |
231 | + | -- Wait for a button press from a set of buttons; returns the command. |
232 | -------------------------------------------------------------------------------- | |
233 | - | local function descendToNextLayer() |
233 | + | local function waitForButtonPress(buttons) |
234 | - | navigateTo3DNoDump(startX, posY, startZ) |
234 | + | |
235 | - | for i = 1, 3 do |
235 | + | local event, btn, x, y = os.pullEvent("mouse_click") |
236 | - | turtle.digDown() |
236 | + | for _, data in ipairs(buttons) do |
237 | - | while not turtle.down() do |
237 | + | if inButton(x, y, data.btn) then |
238 | - | turtle.digDown() |
238 | + | return data.cmd, data.id |
239 | - | sleep(sleepTime) |
239 | + | |
240 | end | |
241 | - | posY = posY - 1 |
241 | + | |
242 | end | |
243 | - | return true |
243 | + | |
244 | -------------------------------------------------------------------------------- | |
245 | -- Main UI Loop | |
246 | -------------------------------------------------------------------------------- | |
247 | - | -- MINE ONE LAYER (Serpentine based on configured areaWidth and areaHeight) |
247 | + | local function main() |
248 | while true do | |
249 | - | local function mineLayer() |
249 | + | local mainButtons = drawMainList() |
250 | - | for row = 0, areaHeight - 1 do |
250 | + | local event, btn, x, y = os.pullEvent("mouse_click") |
251 | - | if row % 2 == 0 then |
251 | + | for _, data in ipairs(mainButtons) do |
252 | - | faceDirection( (startFacing + 0) % 4 ) -- use starting direction for even rows |
252 | + | if inButton(x, y, data.btn) then |
253 | - | for i = 1, areaWidth - 1 do |
253 | + | local robotId = data.id |
254 | - | if not mineForwardStep() then |
254 | + | local submenuButtons = drawRobotMenu(robotId) |
255 | - | manageInventoryDump() |
255 | + | while true do |
256 | - | mineForwardStep() |
256 | + | local cmd, _ = waitForButtonPress(submenuButtons) |
257 | if cmd == "back" then | |
258 | break | |
259 | - | else |
259 | + | elseif cmd == "stats" then |
260 | - | faceDirection( (startFacing + 2) % 4 ) -- opposite direction for odd rows |
260 | + | rednet.send(robotId, "stats") |
261 | - | for i = 1, areaWidth - 1 do |
261 | + | term.setCursorPos(2, term.getSize()-5) |
262 | - | if not mineForwardStep() then |
262 | + | term.setBackgroundColor(colors.black) |
263 | - | manageInventoryDump() |
263 | + | term.setTextColor(colors.white) |
264 | - | mineForwardStep() |
264 | + | write("Waiting for stats...") |
265 | local sender, reply, protocol = rednet.receive(nil, 3) | |
266 | term.clearLine() | |
267 | if sender == robotId and reply then | |
268 | - | if row < areaHeight - 1 then |
268 | + | write("Stats: " .. reply) |
269 | - | faceDirection( (startFacing + 1) % 4 ) -- move to next row (assumes southward shift) |
269 | + | else |
270 | - | if not mineForwardStep() then |
270 | + | write("No stats received from robot " .. robotId) |
271 | - | manageInventoryDump() |
271 | + | end |
272 | - | mineForwardStep() |
272 | + | write(" -- Touch to continue.") |
273 | os.pullEvent("mouse_click") | |
274 | submenuButtons = drawRobotMenu(robotId) | |
275 | elseif cmd == "config" then | |
276 | - | navigateTo3DNoDump(0, posY, 0) |
276 | + | configureRobot(robotId) |
277 | - | faceDirection(startFacing) |
277 | + | submenuButtons = drawRobotMenu(robotId) |
278 | else | |
279 | rednet.send(robotId, cmd) | |
280 | term.setCursorPos(2, term.getSize()-5) | |
281 | - | -- MINING LOOP |
281 | + | term.setBackgroundColor(colors.black) |
282 | term.setTextColor(colors.white) | |
283 | - | local function miningLoop() |
283 | + | write("Sent command '" .. cmd .. "' to robot " .. robotId) |
284 | - | print("Mining started.") |
284 | + | os.sleep(1) |
285 | - | while miningActive and not stopMining do |
285 | + | submenuButtons = drawRobotMenu(robotId) |
286 | - | mineLayer() |
286 | + | end |
287 | - | local success, data = turtle.inspectDown() |
287 | + | |
288 | - | if success and data.name == "minecraft:bedrock" then |
288 | + | break |
289 | - | print("Bedrock below. Stopping mining.") |
289 | + | |
290 | - | miningActive = false |
290 | + | |
291 | - | break |
291 | + | |
292 | end | |
293 | - | print("Layer complete. Descending...") |
293 | + | |
294 | - | if not descendToNextLayer() then |
294 | + | |
295 | - | print("Cannot descend further. Stopping mining.") |
295 | + | -- MAIN: Run the UI and rednet listener in parallel. |
296 | - | miningActive = false |
296 | + | |
297 | - | break |
297 | + | parallel.waitForAny(main, handleRednetMessages) |
298 | ||
299 | - | sleep(0.1) -- yield frequently |
299 | + | |
300 | rednet.close(modemSide) | |
301 | - | print("Mining stopped.") |
301 | + | |
302 |