Motion Sensor
A motion sensor reports living things that move near the block it is installed in: which kind, and where.
Facts
| | |
|—|—|
| Type | motion |
| Address | An expansion slot path such as exp:0, or a hub’s |
| Range | 8 blocks around its block |
| Interrupt | Raised while events wait |
| Power | 9 W |
| Info | kind (motion_sensor) |
It has no settings. It looks every 5 ticks (four times a second) at every living entity in a box 8 blocks around its block (players too). An entity that has moved 0.1 of a block or more in any direction since the last look, or that it did not see last time, is an event. It works while a running computer powers it.
In mcos
/dev/motion0 (motion1 for the next) is a read-only file, an event a line:
minecraft:pig 1.5 0.0 -3.0
That is mob x y z: the entity’s type and where it is, in blocks, relative to the sensor’s block
(its center). A read with nothing waiting waits for the next event, as a keyboard does, and poll
reports the file readable (IN) while one waits. When events were dropped, a line lost n comes
before the next one. Opening it to write fails with EACCES.
head -n 3 /dev/motion0
tail -f /dev/motion0
The file’s ioctls are the sensor’s methods, by name, in any open mode:
sys.ioctl(fd, "pull") answers the oldest event as a table, with numbers for its coordinates. A
sensor that is gone answers ENODEV. setLabel labels it, as for any device (see
The sys library).
The file and pull take from the same buffer, in order: an event read from the file is gone from
pull, and the other way round. Polling loses nothing: an event a poll found waiting is the next
one pull answers (or the next line read), so a program can wait with sys.poll and then take
events with pull until it answers nil.
Methods
pull() → event or nil
The oldest motion waiting, as an event table (see Events), or nil when none waits. Each is taken once.
local event = sys.ioctl(fd, "pull") -- {mob = "minecraft:pig", x = 1.5, y = 0.0, z = -3.0}
Events
| Field | Value |
|---|---|
mob |
the entity’s type, such as minecraft:pig |
x, y, z |
its position relative to the block’s center, in blocks, to one decimal place (floats) |
lost |
present when events were dropped: how many |
Each event raises the sensor’s interrupt, which stays raised while any wait. The sensor keeps at most 32 events. When it is full the oldest goes, and the next event says how many were lost. Events are kept across a save, and dropped while no program runs.
The coordinates are the world’s (not relative to the block’s front). x grows east, y up and z
south.
Examples
Wait for something to come near
local f = io.open("/dev/motion0")
local line = f:read("l") -- blocks until the sensor has an event
print("something moved: " .. line)
read("l") waits until the sensor has an event, then returns it as mob x y z. Run it with
lua /watch.lua.
An alarm that never stops watching
-- Sound an alarm (a redstone signal on the north face) whenever the motion sensor sees something move.
--
-- Reading a sensor's file blocks until it has an event, so the loop below is a plain while loop:
-- the program sleeps in read() until something moves, with no event.pull and no yield.
local sys = require("sys")
local ALARM = "north"
local PULSE = 1 -- seconds the alarm stays on
local motion = io.open("/dev/motion0")
local alarm = io.open("/dev/redstone0", "w")
local function set(strength)
alarm:write(ALARM, " ", strength, "\n")
alarm:flush()
end
while true do
local line = motion:read("l") -- blocks until the sensor has something to say
local mob, x, y, z = line:match("^(%S+) (%S+) (%S+) (%S+)$")
if mob then -- (a line "lost n" says n events were dropped: nothing to sound for)
print(mob .. " at " .. x .. " " .. y .. " " .. z)
set(15)
sys.sleep(PULSE)
set(0)
end
end
The while true do loop is the program’s whole event loop: it sleeps in read until something
moves. It needs a redstone card as well, for the alarm’s output. See
Writing programs.
Count what walks past
-- Keep a census of what walks past: a count of each kind of mob the motion sensor reports, kept in
-- a file, a line a kind ("minecraft:pig 12"), sorted by name. `cat /home/census.txt` shows it.
--
-- It reads the sensor's file, a line an event ("minecraft:pig 1.5 0.0 -3.0"): read blocks until
-- the sensor has one, so the loop never spins. A mob that keeps moving is counted each time it is
-- seen moving: this counts sightings, not mobs.
local CENSUS = "/home/census.txt"
local counts = {}
local sensor = assert(io.open("/dev/motion0"))
local function save()
local kinds = {}
for kind in pairs(counts) do
kinds[#kinds + 1] = kind
end
table.sort(kinds)
local out = assert(io.open(CENSUS, "w"))
for _, kind in ipairs(kinds) do
out:write(kind, " ", counts[kind], "\n")
end
out:close()
end
for line in sensor:lines() do -- (each read waits for the next event)
local mob = line:match("^(%S+) %S+ %S+ %S+$") -- ("lost n" lines are not sightings)
if mob then
counts[mob] = (counts[mob] or 0) + 1
save()
end
end
A light that brightens as something comes near
-- A light that grows brighter the closer something moves: four times a second, the redstone card's
-- up face is set from the nearest motion the sensor saw since the last look (15 right at the block,
-- fading to 0 at the sensor's range), and goes dark when nothing moved.
--
-- It uses the sensor's method, pull, through its file's ioctls: each event is a table, its x, y and
-- z numbers, with no text to parse. The sensor keeps its events (32 at most) while the program
-- sleeps, so a look takes all that came since the last.
local sys = require("sys")
local LAMP = "up"
local RANGE = 8 -- the sensor's range, in blocks
local LOOK = 0.25 -- seconds between looks
local sensor = assert(sys.open("/dev/motion0"))
local card = assert(sys.open("/dev/redstone0"))
-- The distance of the nearest event waiting, or nil if there are none.
local function nearest()
local best
local event = sys.ioctl(sensor, "pull")
while event do
local d = math.sqrt(event.x ^ 2 + event.y ^ 2 + event.z ^ 2)
if not best or d < best then
best = d
end
event = sys.ioctl(sensor, "pull")
end
return best
end
local lit = -1
while true do
local d = nearest()
local strength = d and math.max(0, math.floor(15 * (1 - d / RANGE) + 0.5)) or 0
if strength ~= lit then
sys.ioctl(card, "set", {[LAMP] = {powered = strength}})
lit = strength
end
sys.sleep(LOOK)
end
Put the redstone card in the same computer, and a lamp on top of it.
pull gives the coordinates as numbers, so the distance is plain arithmetic.