WLAN Cards
A WLAN card is a network card that sends frames through the air instead of over cables. It has the same methods, plus a signal strength.
Facts
| | |
|—|—|
| Type | network |
| Address | An expansion slot path such as exp:0 |
| Tiers | 1 (reaches 16 blocks), 2 (reaches 400 blocks) |
| Interrupt | Raised while frames wait; also a wake line |
| Power | 5 W at rest; a send costs more the farther the signal reaches |
| Frame size | At most 8192 bytes |
| Info | kind (tier1_wlan or tier2_wlan), wireless (true) |
Reach
A frame reaches every WLAN card within the sender’s signal strength, in blocks, in the same world. Any tier hears any other: a tier 1 card hears a tier 2 card’s frame if the sender’s signal reaches it. A WLAN card hears only WLAN cards, never a wired one.
A card starts at its tier’s strongest signal. A program lowers it to save power and raises it again, up to the tier’s:
- A send costs
ceil(strength * 100 / 16)power units: a tier 1 card at full strength costs one tick of a running CPU, a tier 2 card at full strength 25 ticks’ worth. - A
distancein a received frame is the straight line between the two blocks, in meters, to one decimal place. A card at strength 0 reaches only cards in its own block.
A card tells the world where it is as its block ticks. A card that has not for a couple of ticks (its chunk unloaded, its block broken, the card taken out) is not on the network.
Everything else (addresses, send, broadcast, receive, the "busy" and "unreachable"
answers, nothing being dropped, the traffic log, and wake-on-LAN) works as it does for a wired card;
read Network Card for it.
In mcos
mcos reaches it through sockets as wlan0 (wlan1 for the next); see
Networking. Its file in /dev is a network card’s (network0, counted with
the wired ones): its traffic, a line a frame, read only. The distance in each line is the card’s
own measure, which no socket gives:
in 5f0c2a9e-41d7-4c4e-9b0a-2f3c4d5e6f70 3.0m 9 broadcast
out broadcast - 9 2 took it
Reading gives the next line and waits for one; opening it for writing fails with EACCES; poll
gives IN while a line waits and PRI while the card has news. Its methods are the file’s ioctls,
in any open mode:
local sys = require("sys")
local card = assert(sys.open("/dev/network0"))
print(sys.ioctl(card, "setStrength", 16)) -- 16: how far it reaches now
print(sys.ioctl(card, "getStrength"))
Methods
The methods of a network card: address, send, broadcast, receive,
pull, getWake and setWake. And these two.
getStrength() → blocks
How far the signal reaches now, in blocks.
setStrength(blocks) → blocks
Sets how far the signal reaches, from 0 up to the tier’s range (16 or 400). A number outside that is clamped to it, so asking for a huge one gives the tier’s most. It must be an integer. Gives what it is set to. The setting is kept with the card across saves.
Events
A frame that arrives is an event, taken with receive: {from, data, distance, broadcast}, and
each frame in or out is a line of its traffic log. See Network Card.
Examples
Each is a complete program for a computer with a WLAN card; run it with lua.
Turn the signal down
Finds the weakest signal that still reaches another card, with setStrength and send as ioctls,
and leaves the card there: lua wlan-tune.lua <the other card's address>.
-- Turn a WLAN card's signal down to the weakest that still reaches another computer, to save
-- power: a send costs more the farther the signal reaches.
--
-- It works through the card's methods, ioctls on its file: setStrength sets how far the signal
-- reaches (clamped to the card's tier, so asking for a huge one gives the tier's most), and send
-- puts a frame to one card and says whether it took it. A card that answers "busy" was reached
-- too: it just had no room. The strength is found by halving the range each try, so a tier 2
-- card's 400 blocks take nine.
--
-- usage: lua wlan-tune.lua ADDRESS
local sys = require("sys")
local CARD = "/dev/network0"
local PROBE = "tune" -- a probe frame's data: no protocol a kernel takes, so the other computer drops it
local HUGE = 1000000 -- more than any tier reaches: setStrength gives the tier's most
local address = arg[1] or error("usage: wlan-tune ADDRESS")
local card = assert(sys.open(CARD))
-- Whether a frame reaches the other card at this strength.
local function reaches(blocks)
assert(sys.ioctl(card, "setStrength", blocks))
local taken, why = sys.ioctl(card, "send", address, PROBE)
if taken == nil then
error(why) -- not a WLAN card, or it's gone
end
return taken or why == "busy"
end
local most = assert(sys.ioctl(card, "setStrength", HUGE))
if not reaches(most) then
io.stderr:write("wlan-tune: ", address, " is out of reach even at ", most, " blocks\n")
os.exit(1)
end
local low, high = 0, most -- 'high' reaches; below 'low' nothing has been tried yet
while low < high do
local middle = (low + high) // 2
if reaches(middle) then
high = middle
else
low = middle + 1
end
end
assert(sys.ioctl(card, "setStrength", high))
print(string.format("%s: %d blocks (of %d)", address, sys.ioctl(card, "getStrength"), most))
A beacon
Says the computer’s name over the air every few seconds, only as far as it should reach:
lua wlan-beacon.lua 20 &. Hear it elsewhere with nc -ul 4001.
-- A beacon: say this computer's name over the air every few seconds, to every computer near
-- enough, as a Wi-Fi access point does. Run it as a service, or in the background with &.
--
-- The card's signal is set first (its setStrength method, an ioctl on its file), so the beacon is
-- heard only as far as it should be, and costs no more than that. Each beacon is a UDP datagram
-- broadcast to PORT, its data the computer's name. sys.sleep blocks between beacons.
--
-- usage: lua wlan-beacon.lua [blocks]
local sys = require("sys")
local CARD = "/dev/network0"
local PORT = 4001 -- where listeners take beacons
local INTERVAL = 5 -- seconds between beacons
local STRENGTH = tonumber(arg[1]) or 16 -- how far it's heard, in blocks
local card = assert(sys.open(CARD))
local strength = assert(sys.ioctl(card, "setStrength", STRENGTH))
sys.close(card)
io.stderr:write("beacon: heard up to ", strength, " blocks away\n")
local sock = assert(sys.socket())
while true do
sys.sendto(sock, sys.gethostname() .. "\n", "broadcast", PORT) -- (best effort: nobody near is fine)
sys.sleep(INTERVAL)
end
A site survey
Listens for a while through the card’s file, and writes down every card heard and how far, nearest
first: lua wlan-survey.lua /home/survey.txt 30.
-- A site survey: listen to the air for a while, and write down every card heard and how far away
-- it is, nearest first.
--
-- The card's file, /dev/network0, is its traffic: a line a frame, as "in 5f0c2a9e-... 12.0m 17
-- broadcast" (the direction, the other card, the distance in meters, the bytes, how it went). The
-- distance is the card's own measure, which no socket gives. sys.poll waits for the next line, or
-- for the time left, so the program uses nothing while the air is quiet.
--
-- usage: lua wlan-survey.lua [report [seconds]]
local sys = require("sys")
local CARD = "/dev/network0"
local REPORT = arg[1] or "/home/survey.txt"
local SECONDS = tonumber(arg[2]) or 10 -- how long it listens
local READ_SIZE = 512 -- the most one read takes
local traffic = assert(sys.open(CARD))
local nearest = {} -- each card heard, to its nearest distance
local pending = "" -- what has been read of a line not yet ended
local stop = sys.uptime() + SECONDS
while true do
local left = stop - sys.uptime()
if left <= 0 then
break
end
local ready = sys.poll({[traffic] = {IN = true}}, left)
if ready[traffic] then
pending = pending .. assert(sys.read(traffic, READ_SIZE))
for line in pending:gmatch("([^\n]*)\n") do
local direction, address, meters = line:match("^(%S+) (%S+) ([%d.]+)m ")
if direction == "in" then
local distance = tonumber(meters)
if not nearest[address] or distance < nearest[address] then
nearest[address] = distance
end
end
end
pending = pending:match("[^\n]*$")
end
end
local cards = {}
for address in pairs(nearest) do
cards[#cards + 1] = address
end
table.sort(cards, function(a, b)
return nearest[a] < nearest[b]
end)
local out = assert(io.open(REPORT, "w"))
for _, address in ipairs(cards) do
out:write(string.format("%s %.1f\n", address, nearest[address]))
end
out:close()
print(#cards .. " cards heard; see " .. REPORT)