Robotics device
A robot’s robotics device is its body’s motors: soldered onto a Bot Golem’s board, it’s the only way the golem moves and acts. It walks to a point relative to the robot, turns it, and stops it; it senses the six spaces round it; and it works with what’s in the robot’s hand: uses it on a space (places a block, opens a door, presses a button), breaks blocks and hits mobs with it, and moves items in and out of a container. It says how each went.
The robot knows nothing of where it is, not even which way is north: every face is named in its
own frame, front, back, left, right, up and down, its left and right its own (as it
turns and walks), and a block’s direction it reads (a door’s facing) is in that frame too.
Facts
| | |
|—|—|
| Type | robotics |
| Where | Soldered onto a bot golem’s last expansion lane (exp:3) |
| /dev | robotics0 |
| Power | A running CPU’s tick for each block walked, each turn, and each action with its hand |
In mcos
/dev/robotics0 reads as what it’s doing now, a word: idle, walking, turning or
breaking. A line written is an action, taking effect as it’s written:
| Line | Does |
|---|---|
walk forward [right [up]] |
Walks to the point that many blocks from it, in its own frame |
turn left, turn right, turn around, turn degrees |
Turns in place (degrees: to its right; negative, its left) |
use face |
Uses what it holds on the space at a face |
swing face |
Swings what it holds at a face: hits a mob, or breaks the block |
stop |
Stops, where it is |
A line that isn’t one fails the write with EINVAL. How an action went comes as an event: the
file is ready for sys.poll’s PRI while one waits, and the pull ioctl takes it. The file’s
ioctls are the device’s methods, by name, in any open mode: sys.ioctl(fd, "walk", 3).
echo "walk 3" > /dev/robotics0
cat /dev/robotics0
echo "swing front" > /dev/robotics0
Its scan, its hand and containers are ioctls: sys.ioctl(fd, "scan"), sys.ioctl(fd, "take", "front").
Its hand
The golem holds one item in its hand, in the hand slot of its view (right of its board, under its buttons), and has an off hand under it. What it does with it is what a player does: the game’s own interactions, through a player standing where it is and looking at the space. A block placed, a door opened, a bucket filled, a lever flipped, wool sheared from a sheep: each as a player’s right click. Breaking a block takes a player’s time with what it holds (a pickaxe on stone, an axe on wood; the wrong tool is slow and may drop nothing), and the tool wears. A hit on a mob does the weapon’s damage, as strong as a player’s swing at that pace: full strength once the weapon has recovered.
What a broken block drops is caught: into the robot’s storage (a crafting card’s)
first, else its off hand catches it, in the background (when it’s empty, or holds the same). What it
can’t catch stays in the world, and the broke event says so (kept, dropped), so a program knows
from the one event. Anything the hand is given besides (a filled bucket from a stack of empty ones)
is caught the same way. swap puts what the off hand caught in its hand, to store or use.
On its legs it presses pressure plates and trips tripwires, as anything walking does.
Methods
walk(forward[, right[, up]]) → true
Walks to the point forward blocks the way it faces, right to its right (negative: its left) and
up above, by the way it finds. It answers at once; its event says how it went: arrived, or
blocked and why (no way there). A walk or turn under way is given up. It fails with
too far: at most 64 blocks each way.
turn(way) → true
Turns in place: "left", "right", "around", or a number of degrees (to its right; negative, its
left). Its event: turned. It fails with not a way to turn for any other word.
stop() → true
Stops walking or turning, where it is. Its event, when it was doing something: stopped.
scan([face]) → space, or {face = space, …}
What’s in the space at a face, or with none, in all six at once, by face. Each space is a table:
kind |
Other fields |
|---|---|
air |
|
block |
name (minecraft:oak_log), state (its properties: {axis = "y"}, a door’s {facing = "back", open = false, ...}, directions in its frame), hardness (-1: unbreakable), solid (it can’t walk through it), container (its slots, when it’s one), fluid (when waterlogged) |
fluid |
name (minecraft:water), amount (1 to 8), source |
entity |
name (the first’s: minecraft:pig; an item’s, its item’s), entities (every one’s name), block (the block there) |
held([hand]) → item or nil
What its hand holds ("off": its off hand): {name, count, damage, maxDamage, label} (damage
and maxDamage for what wears; label when it’s named), or nil.
swap() → true
Swaps what its hands hold, as a player’s swap key: its main hand works, its off hand catches.
use(face) → what, or nil and why
Uses what it holds on the space at a face, as a player’s right click. A mob there is used on first
(shears on a sheep); then a block there is clicked on its face toward the robot (a door, a button,
a lever, a log stripped with an axe); an empty space gets what it holds placed into it, against a
neighbouring block, or with nothing to place against, what it holds is used the way it looks (a
bucket into water). Answers "placed" or "used"; fails with nothing happened, nothing to
place against or nothing there.
swing(face) → what, or nil and why
Swings what it holds at a face, as a player’s left click. A mob there is hit: "attacked". Else
the block there starts breaking: "breaking", and its event says how it went: broke (with the
block’s name, and kept: true when it caught all the block dropped, else false and how many it
dropped into the world), or failed and why (the block changed, what it held changed). A walk or turn
under way is given up. It fails with nothing there or unbreakable.
contents([face]) → list, or nil and why
What the container at a face (in front, when none is given) holds (a chest, a barrel, a furnace, a chest minecart): a list of its
slots with something in, each {slot, name, count, ...} (slots from 1). It fails with
no container there.
store([face][, count[, to]]) → true, 0 or false, remaining; or nil and why
Puts what it holds (or count of it) into the container at a face (in front, when none is
given), into its slot to or where it fits, as a hopper
would (a furnace takes fuel from its side, what it smelts from above). One answer says it all: true,
0 when none of it is left in its hand (an empty hand too), else false and how many are left (the
container is full), so a program never has to look again to know. It fails (nil and why) with no
container there.
take([face][, slot[, count]]) → number, or nil and why
Takes items from the container at a face (in front, when none is given) into its hand: from slot, or the first it can take (the
same as it holds, when it holds something); all its hand can hold, or count. How many came: 0
when there were none it could take. It fails with no container there or no slot.
detachLegs() → true, or nil and why
Detaches its legs, leaving them standing where it is. Held in its own
energy field (capture("self") first), it flies on, the field
moving it; with nothing holding it up, it falls over and powers off. Without its legs it walks
nowhere (no legs), swings nothing (no tools: no legs: it can't swing without its footing), and
presses no pressure plates; it still uses what’s round it (buttons, doors, levers: a tool in its
hand works as an empty hand), places what it holds, and its other cards work. A right click pops
legs up into an inventory, its own use too. Fails with no legs to detach.
attachLegs() → true, or nil and why
Goes to its legs, standing within three blocks of it, and onto them; a field holding it lets go
as it lands (its event there: lost, landing on its legs). Its event: attached, or failed
and why (its legs are gone). Fails with no legs within 3 blocks or already on its legs.
get() → {state, legs}
What it’s doing now (idle, walking, turning, breaking or attaching), and whether it stands
on its legs.
set(action) → true
An action as a table: {action = "walk", forward, right, up}, {action = "turn", way},
{action = "use", face}, {action = "swing", face}, or {action = "stop"} (the file’s lines are
these).
pull() → event or nil
Its oldest event, or nil.
Events
event |
Other fields | When |
|---|---|---|
arrived |
a walk got there | |
blocked |
why |
a walk can’t go on: no way there; interrupted when its world was saved during it |
turned |
a turn is done | |
stopped |
stop ended a walk, turn or swing |
|
broke |
block, kept, dropped |
a swing broke the block (its name); whether it caught all it dropped, and how many it didn’t |
failed |
why |
a swing couldn’t go on, or it couldn’t get onto its legs |
attached |
it’s on its legs again |
Its interrupt is raised when an event comes, and stays raised while one waits. With no program running on its board it takes no data: its events go, and it stops.
Examples
Walk to a point and say how it went, through the device’s file (a write is the action):
-- Walk the robot to a point relative to where it stands, and say how it went.
--
-- lua robotics-goto.lua forward [right [up]]
--
-- A line written to the robotics device's file is an action ("walk 3 -1"); how it went comes as
-- an event. sys.poll waits (it blocks, it does not yield) until the device has news, and the pull
-- ioctl takes the event. Exit status 0: it arrived; 1: it was blocked, and why is printed.
local sys = require("sys")
local DEVICE = "/dev/robotics0"
local forward, right, up = tonumber(arg[1]), tonumber(arg[2] or "0"), tonumber(arg[3] or "0")
if not forward or not right or not up then
io.stderr:write("usage: robotics-goto forward [right [up]]\n")
os.exit(2)
end
local robot = assert(sys.open(DEVICE, "w"))
sys.write(robot, ("walk %s %s %s\n"):format(forward, right, up)) -- (a line takes effect as it's written)
while true do
local event = sys.ioctl(robot, "pull")
if event and event.event == "arrived" then
print("arrived")
os.exit(0)
elseif event and event.event == "blocked" then
print("blocked: " .. tostring(event.why))
os.exit(1)
end
sys.poll({[robot] = {PRI = true}}) -- wait for its news
end
Walk round a square with the device’s methods, waiting for each move:
-- Walk the robot round a square, SIDE blocks a side, coming back where it started, facing as it
-- did. Each move is a method of the robotics device (an ioctl on its file): walk, then turn right,
-- four times, each waited for: sys.poll blocks until the device has news, and pull takes the
-- event. If a walk is blocked, it stops there and says why (exit status 1).
local sys = require("sys")
local SIDE = tonumber(arg[1] or "4")
local robot = assert(sys.open("/dev/robotics0"))
-- Waits for the device's next event, and gives it.
local function event()
while true do
local got = sys.ioctl(robot, "pull")
if got then
return got
end
sys.poll({[robot] = {PRI = true}})
end
end
for side = 1, 4 do
assert(sys.ioctl(robot, "walk", SIDE))
local walked = event()
if walked.event ~= "arrived" then
print("side " .. side .. ": " .. walked.event .. " (" .. tostring(walked.why) .. ")")
os.exit(1)
end
assert(sys.ioctl(robot, "turn", "right"))
event() -- turned
end
print("back where it started")
Look round, from one scan:
-- Look round the robot: one scan of the six spaces round it, in its own frame, a line each: the
-- face, what kind of thing is there, and its name (a container says how many slots it has).
local sys = require("sys")
local FACES = {"front", "back", "left", "right", "up", "down"}
local robot = assert(sys.open("/dev/robotics0"))
local around = assert(sys.ioctl(robot, "scan"))
for _, face in ipairs(FACES) do
local space = around[face]
local line = face .. ": " .. space.kind
if space.name then
line = line .. " " .. space.name
end
if space.container then
line = line .. " (" .. space.container .. " slots)"
end
print(line)
end
Dig a tunnel with the pickaxe it holds, breaking and walking in:
-- Dig a tunnel: break the block in front with what the robot holds (a pickaxe), walk into the
-- space, and again, LENGTH times. Breaking takes a player's time with that tool; its event says
-- when it's done (broke), or why it couldn't (failed). It stops at what it can't break or walk
-- into, and says how far it got.
local sys = require("sys")
local LENGTH = tonumber(arg[1] or "8")
local robot = assert(sys.open("/dev/robotics0"))
-- Waits for the device's next event, and gives it.
local function event()
while true do
local got = sys.ioctl(robot, "pull")
if got then
return got
end
sys.poll({[robot] = {PRI = true}})
end
end
local held = sys.ioctl(robot, "held")
print("digging with " .. (held and held.name or "a bare hand"))
for dug = 0, LENGTH - 1 do
local ahead = assert(sys.ioctl(robot, "scan", "front"))
if ahead.kind == "block" then
local ok, why = sys.ioctl(robot, "swing", "front")
if not ok then
print("stopped after " .. dug .. ": " .. why)
os.exit(1)
end
local swung = event()
if swung.event ~= "broke" then
print("stopped after " .. dug .. ": " .. tostring(swung.why))
os.exit(1)
end
end
assert(sys.ioctl(robot, "walk", 1))
local walked = event()
if walked.event ~= "arrived" then
print("stopped after " .. dug .. ": " .. tostring(walked.why))
os.exit(1)
end
end
print("dug " .. LENGTH)