Redstone Card
A redstone card puts out a redstone signal on any face of the block it is installed in, and reads the signal on each face.
Facts
| | |
|—|—|
| Type | redstone |
| Address | An expansion slot path such as exp:0, or a hub’s |
| Interrupt | Raised while a change to a face waits |
| Power | Nothing at rest; each change of an output costs a tick of a running CPU |
| Info | kind (redstone) |
It works where it is installed: in a case it sets and reads redstone at the case, in an expansion hub at the hub. A block may hold several cards. On each face it puts out the strongest signal of any of its cards. Without power it puts out nothing.
Faces
All six, by face name: north, south, east, west, up, down, or
front, back, left, right relative to the block’s front. Answers always use the world’s
names. Each face has two numbers:
level: the signal there, 0 to 15, coming in or put out by any card in the block.powered: the signal this card puts out there, 0 to 15.
So a second card beside one that puts out 15 reads level = 15, powered = 0.
In mcos
/dev/redstone0 (redstone1 for the next card) is text, a line a face, in this order:
north 7 7
south 0 0
east 0 0
west 9 0
up 0 0
down 0 0
Each line is face level powered. The state is taken at the first read after the file is opened
or sought back to its start, so a read from the start always gives the faces as they are then.
A line written, face powered, sets that face as soon as the line is written (flushed), as a sysfs
attribute does: north 11. Relative names work too (front 15). A line that is not a face and a
number fails the write with EINVAL, and so does an unknown face (no such face).
The file is always readable (poll’s IN) and writable (OUT). A change to any face since the
file was last sought to its start shows as PRI, and stays until the file is sought there again
(f:seek("set", 0), sys.lseek(fd, 0, "set")), as a GPIO’s value file does. tail -f shows the
faces again each time they change.
cat /dev/redstone0
echo "north 15" > /dev/redstone0
tail -f /dev/redstone0
The file’s ioctls are the card’s methods, by name, in any open mode:
sys.ioctl(fd, "set", {north = {powered = 15}}). A method that fails answers nil, its message
and EINVAL; a card that is gone, ENODEV. setLabel labels the card, as for any device (see
The sys library).
The file’s PRI comes from the card’s events, and polling loses none: the events a poll found are
the next ones pull answers, and once a program has pulled them all, PRI is off until the next
change (as reading the file from its start turns it off). So a program can wait with sys.poll
and then take every change with pull until it answers nil.
Methods
get([sides]) → table
Every face as {level, powered}, by world name: {north = {level = 7, powered = 7}, ...}. With a
table, only the faces it names, as keys or as a list of names, relative names too ({"front"}
answers the face that is the block’s front, by its world name). An unknown face fails with
no such face.
local west = sys.ioctl(fd, "get", {"west"}).west.level
set(sides) → table
Takes a table in the same shape and sets each face it names: {north = {powered = 11}}.
powered is the signal to put out; it is clamped to 0 through 15 and must be an integer
(north: powered: integer expected). level is what is detected, so it is ignored, and a face the
table leaves out stays as it was. Returns every face, as get(). The arguments are all checked
before anything changes, so a table with one bad face changes nothing. An unknown face fails with
no such face; a face that is not a table, with north: table expected. Each output that changes
costs a tick of a running CPU’s power.
sys.ioctl(fd, "set", {north = {powered = 15}, up = {powered = 0}})
pull() → event or nil
The oldest change waiting, as an event table (see Events), or nil when none waits. Each is taken once.
local event = sys.ioctl(fd, "pull") -- {name = "redstone", side = "west", level = 9, powered = 0}
Events
Any change to a face’s state raises an event, from wherever it came: a signal coming in, this
card’s own set, or another card in the block. Its fields are the face as it is now:
| Field | Value |
|---|---|
name |
"redstone" |
side |
the face, by world name |
level |
the signal there, 0 to 15 |
powered |
this card’s output there |
At most 32 wait. When more come, the oldest is dropped and the next event carries lost, the number
dropped. The events are kept across a save, and dropped when no program runs on the computer.
Examples
A program drives the card by writing lines to its file and reading it back, or through its methods
as ioctls. Run these with lua name.lua, from a computer with a redstone card and something on its
faces (add & to keep the shell). Each is a complete program. For why they loop without yielding,
see Writing programs.
Set an output
local f = io.open("/dev/redstone0", "w")
f:write("north 15\n") -- "face strength": 0 to 15
f:close()
A line written takes effect when it is flushed (f:flush(), or closing the file). From a shell,
echo "north 15" > /dev/redstone0 does the same.
Read an input
local f = io.open("/dev/redstone0")
local text = f:read("a") -- one line a face: "west 9 0"
local level = tonumber(text:match("west (%d+)"))
print("west is " .. level)
Each line is face level powered: the signal on that face, and how much of it is this card’s own.
Blink a lamp forever
-- Blink whatever is on the redstone card's north face: on half a second, off half a second, forever.
--
-- There is no event loop and nothing to yield: this is a plain loop. sys.sleep waits (the
-- computer does other work meanwhile) and the loop goes on until you stop it with Ctrl+C.
local sys = require("sys")
local FACE = "north" -- any face: north, south, east, west, up, down (or front, back, left, right)
local HALF = 0.5 -- seconds on, and seconds off
-- A line written to the card's file is "face strength" and takes effect at once.
local card = io.open("/dev/redstone0", "w")
local function set(strength)
card:write(FACE, " ", strength, "\n")
card:flush()
end
while true do
set(15)
sys.sleep(HALF)
set(0)
sys.sleep(HALF)
end
Follow an input
-- Copy the signal coming in on the west face out of the east face, and keep doing it.
--
-- The card's file says "face level powered" for each face: the level of the signal there, and how
-- much of it is this card's own. sys.poll waits (it blocks, it does not yield) until the card
-- reports a change, so the program uses no time while nothing happens.
local sys = require("sys")
local FROM = "west"
local TO = "east"
local reader = io.open("/dev/redstone0")
local writer = io.open("/dev/redstone0", "w")
local watch = {[sys.fileno(reader)] = {PRI = true}} -- PRI: the device's state changed
-- Read the whole file from its start, and pick out one face's level.
local function level(face)
reader:seek("set", 0)
return tonumber(reader:read("a"):match(face .. " (%d+)")) or 0
end
local last = -1
while true do
local now = level(FROM)
if now ~= last then
writer:write(TO, " ", now, "\n")
writer:flush()
last = now
end
sys.poll(watch) -- block until the card changes
end
sys.poll blocks until the card reports a change (PRI), so the program uses no time while the
signal is steady. The redstone tutorial walks through both programs.
A door that closes by itself
-- A door that opens when a button on the west face is pressed, and closes by itself once nothing
-- has changed for a few seconds (a pressure plate or a button held keeps it open).
--
-- The door (an iron door, a piston) is on the north face. It uses the card's methods, through its
-- file's ioctls: get to read the button, set to power the door. sys.poll waits for the card to
-- change (PRI), and with a time limit for the door to stay open. A redstone file's PRI stays on
-- until the file is sought back to its start, so the program seeks after each wake.
local sys = require("sys")
local BUTTON = "west"
local DOOR = "north"
local HOLD = 3 -- seconds the door stays open after the last change
local card = assert(sys.open("/dev/redstone0"))
local watch = {[card] = {PRI = true}}
-- Waits until the card changes, or 'seconds' pass: whether it changed.
local function changed(seconds)
local ready = sys.poll(watch, seconds)
sys.lseek(card, 0, "set") -- (PRI again only for a newer change)
return ready[card] ~= nil
end
local function pressed()
return sys.ioctl(card, "get", {BUTTON})[BUTTON].level > 0
end
local function door(strength)
sys.ioctl(card, "set", {[DOOR] = {powered = strength}})
end
door(0)
while true do
if pressed() then
door(15)
while changed(HOLD) do -- (each change, the button let go too, starts the wait again)
end
door(0)
end
changed() -- wait for the next press
end
The door’s own change (its face powered) wakes poll too; that only starts the wait again.
Count pulses
-- Count the pulses that come in on the west face (a button, a hopper clock, a daylight sensor) and
-- log how many came each minute, a line a minute: "Day 3 08:12:00 4".
--
-- It uses the card's methods, through its file's ioctls: get for a face's level now, and pull for
-- each change the card kept since the last look. The card keeps its changes (32 at most) while the
-- program sleeps, so sys.sleep is the whole wait and no pulse between looks is missed. A change it
-- had to drop shows as "lost" on the next one.
local sys = require("sys")
local FACE = "west" -- where the pulses come in
local PERIOD = 60 -- seconds between counts
local LOG = "/home/pulses.log" -- a line a count, appended
local card = assert(sys.open("/dev/redstone0"))
-- Every change the card kept, oldest first (pull answers nil when there are none left).
local function changes()
local list = {}
local event = sys.ioctl(card, "pull")
while event do
list[#list + 1] = event
event = sys.ioctl(card, "pull")
end
return list
end
changes() -- (what changed before the program started is not counted)
local was = sys.ioctl(card, "get", {FACE})[FACE].level
while true do
sys.sleep(PERIOD)
local pulses, lost = 0, 0
for _, event in ipairs(changes()) do
lost = lost + (event.lost or 0)
if event.side == FACE then
if event.level > 0 and was == 0 then
pulses = pulses + 1 -- off to on: a pulse
end
was = event.level
end
end
local log = assert(io.open(LOG, "a"))
log:write(os.date("%c"), " ", pulses, lost > 0 and " (some changes lost)" or "", "\n")
log:close() -- (a disk's file shows what's written when it's closed)
end
The card keeps at most 32 changes: a pulse is two, so with more than 16 pulses a period some are
lost (the log says so). Shorten PERIOD for a faster clock.