Modular Computers the manual GitHub

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 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))

Download wlan-tune.lua

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

Download wlan-beacon.lua

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)

Download wlan-survey.lua