Create: Computing AgesBrass Docs
Libraries

net

Messages between computers, by cable, radio or Wi-Fi.

Minicomputer and newer

The net library lets computers talk to each other. One computer sends a piece of data (a number, a word, a whole table of readings), and the other one receives it as a message event. With it, a sensor in the warehouse can tell the factory how much iron is left, a dashboard can show every machine of the base on one LCD Monitor, and a remote control can open a door on the other side of the map.

Brass
-- on the warehouse computer: tell computer #12 how much iron there is
net.send(12, {item = "minecraft:iron_ingot", count = 1830}, "stock")

-- on computer #12: wait for the next message and show it
local m = net.receive()
print(m.data.item .. ": " .. m.data.count)

Which computers have it, and how they reach each other:

computernetlinks
Tube Computer, Transistor Mainframenonone: net is nil
MinicomputeryesData Cable only
Personal Computer, Microcontroller, Modern ComputeryesData Cable, Radio Modem, Wi-Fi Router

How cables, radios and routers connect computers, with their ranges, is explained in Networks. This page is about the functions. A program meant to run anywhere can test for the library first:

Brass
if net == nil then
  print("This computer has no network port.")
  return
end
print("Network ready")
Screen
This computer has no network port.

(This is what a Tube Computer shows. A Minicomputer or newer prints Network ready.)

Functions
net.id()The address of this computer, the number other computers use to send it a message. It is the same as os.id().
net.send(id, data [, channel])Sends data to the computer whose address is id.
net.broadcast(data [, channel])Sends data to every computer in reach, and returns how many accepted it.
net.receive([timeout])Waits for the next message and returns it. With a timeout, gives up after that many seconds and returns nil.
net.computers()The computers this one can send to right now, each one as a table:
net.networks()The Wi-Fi networks this computer is on: those of its own Wi-Fi Routers, and those of the routers its radios reach. Each one is a table:
net.wireless()Tells whether the computer has a radio: a Radio Modem or a Wi-Fi Router touching it, or anywhere on its Data Cable network. Always false on a Minicomputer, which only uses cables.

Addresses

Every computer has an address: a number given the first time it is placed, the same number os.id() returns and the shell command id prints. It stays with the computer when you break it or pick it up with a wrench and place it somewhere else. Messages are sent to an address.

#

net.id()

→ number

The address of this computer, the number other computers use to send it a message. It is the same as os.id().

Returns
number
the address of this computer
Brass
print("My address: " .. net.id())

Write it on a sign next to the computer, or give the computer a name with the shell command label (or os.label): net.computers shows the names of the computers in reach, so a program can find "the warehouse" without knowing its number.

See also net.computers()

Sending

A message carries one value, the data: nil, a boolean, a number, a string, or a table made of those (tables in tables are fine). A function cannot travel: the program stops with cannot send a function. The receiver gets a copy: changing your table after sending it does not change what the other computer got.

A message also has a channel, a short word you choose ("stock", "door", "chat"), "default" when you give none. Channels let one computer run several conversations and ignore the ones it does not care about.

A message has a size limit, checked when you send it:

  • at most 2048 values, where a table counts one, each of its values one, and each key of a table with names one more ({x = 1, y = 2} counts 5, a list of 1000 numbers counts 1001);
  • at most 32768 characters in all its strings, keys included;
  • at most 32 tables inside each other.

Past those limits the program stops with message too large (or table is too deep (or cyclic) to convert for a table that contains itself). For more, split the data into several messages.

Sending is instant: the message is in the receiver's queue in the same tick. It is accepted only when:

  • the receiver is in reach: on the same Data Cable network, or linked by radio or Wi-Fi (see Networks);
  • it is switched on, in a chunk that is loaded and ticking;
  • its receive buffer is not full: the messages waiting in its queue may take up to a quarter of its memory (4 K cells on a Microcontroller, 8 K on a Minicomputer, 32 K on a Personal Computer, 256 K on a Modern Computer). Past that, new messages are refused, like a full network buffer, instead of crashing the receiving program.
Important

true means "the message is in the receiver's queue", not "the receiver handled it". A computer that sits at the shell prompt, with no program running, throws away the messages it gets, although net.send returned true: a receiver must run as a program, usually its startup. If you must know that an order was carried out, have the receiver answer (see the patterns below and Networks).

#

net.send(id, data [, channel])

→ boolean⚙ cost 32

Sends data to the computer whose address is id.

Parameters
id number
the address of the receiver
data any
the value to send: nil, boolean, number, string or a table of those
channel string optional
the channel, "default" when left out
Returns
boolean
true when the receiver accepted the message, false otherwise
Brass
local FACTORY = 12
local ok = net.send(FACTORY, {type = "order", item = "minecraft:iron_ingot", count = 64}, "orders")
if not ok then
  print("The factory does not answer: out of reach, switched off or overloaded.")
end

net.send returns false when no computer with that address is in reach (it is too far, in an unloaded chunk, in another dimension, or it is this very computer), when it is switched off, or when its receive buffer is full. It does not wait: the program goes on at once.

The data can be left out, for a message whose channel says everything:

Brass
net.send(12, nil, "ping")

Wrong arguments stop the program: bad argument #1 to 'send' (number expected, got string) when the address is not a number, bad argument #3 to 'send' (string expected, got number) when the channel is not a string.

Besides its 32 instructions, a send costs one instruction for each value of the message and one for every 16 characters (the copy), plus the search for the receiver: one instruction per block space around the computer's Data Cables and, on a Personal Computer, a Microcontroller or a Modern Computer, one per computer loaded in the world. On a large network, send a few big messages rather than many small ones.

See also net.broadcast() net.receive()

#

net.broadcast(data [, channel])

→ number⚙ cost 64

Sends data to every computer in reach, and returns how many accepted it.

Parameters
data any
the value to send, with the same rules as net.send
channel string optional
the channel, "default" when left out
Returns
number
how many computers accepted the message

A broadcast suits news that anyone may want: a sensor reading, an alarm, "I am here". The computers that do not care simply ignore the channel.

Brass
local count = net.broadcast("creeper at the north gate!", "alarm")
print("Warned " .. count .. " computers")

A computer that is off, out of reach or overloaded is skipped: it is not counted, and there is no error. 0 means nobody heard you. A broadcast costs 64 instructions, plus the copy and the search like net.send, plus 8 per computer in reach.

Watch out

Never answer a broadcast with a broadcast on the same channel: two computers doing that bounce the message between them forever. Reply with net.send(m.sender, ...), and see Networks for relays.

See also net.send()

Receiving

Messages arrive in the computer's event queue, as message events, and wait there until the program reads them, even while it sleeps. A message is a table:

fieldcontent
namealways "message"
senderthe address of the computer that sent it
channelthe channel it was sent on
dataa copy of the data (absent when nil was sent)
viahow it came: "cable", "radio" or "wifi"
distancethe distance between the two computers, in blocks, to a tenth
#

net.receive([timeout])

→ table|nil⏸ Waits

Waits for the next message and returns it. With a timeout, gives up after that many seconds and returns nil.

Parameters
timeout number optional
the longest wait in seconds; without it, waits as long as it takes
Returns
table|nil
the message, or nil when the time ran out
Brass
print("Listening as #" .. net.id())
while true do
  local m = net.receive()
  print("#" .. m.sender .. " on " .. m.channel .. ": " .. tostring(m.data))
end

The fields of a message, written here by hand to show what a program receives:

Brass
local m = {name = "message", sender = 7, channel = "stock", via = "wifi", distance = 182.4,
  data = {item = "minecraft:iron_ingot", count = 1830}}
print("from #" .. m.sender .. " by " .. m.via .. ", " .. m.distance .. " blocks away")
print(m.data.item .. ": " .. m.data.count)
Screen
from #7 by wifi, 182.4 blocks away
minecraft:iron_ingot: 1830

The timeout is rounded up to the next tick (a twentieth of a second), and is at least one tick: net.receive(0) returns a message already waiting, or nil one tick later. Use a timeout whenever an answer may never come, so that the program does not hang:

Brass
net.send(12, nil, "ping")
local m = net.receive(2)
if m == nil then
  print("No answer within 2 seconds")
end
Watch out

While it waits, net.receive throws away every other event: timers, keys, clicks, redstone changes. A program that needs those too uses os.pull_event() without a filter and tests e.name, as below.

The same messages come out of os.pull_event, which is the way to wait for messages and something else. os.pull_event("message") does exactly what net.receive() does. A warehouse computer that reports its stock every 10 seconds and answers questions at any moment:

Brass
local vault = peripheral.wrap("back")
local timer = os.start_timer(10)
while true do
  local e = os.pull_event()
  if e.name == "message" and e.channel == "stock" and e.data == "how much?" then
    net.send(e.sender, vault.count("minecraft:iron_ingot"), "stock")
  elseif e.name == "timer" and e.id == timer then
    net.broadcast(vault.count("minecraft:iron_ingot"), "stock")
    timer = os.start_timer(10)
  end
end

Filter what you accept. Any computer in reach can send on any channel, so check the channel, the sender and the shape of the data before acting. A door that only obeys the two control rooms:

Brass
local TRUSTED = {[4] = true, [9] = true}
local function accept(m)
  return m.channel == "door" and TRUSTED[m.sender] == true
end
print(accept({sender = 4, channel = "door", data = "open"}))
print(accept({sender = 5, channel = "door", data = "open"}))
print(accept({sender = 9, channel = "chat", data = "hi"}))
Screen
true
false
false

In the real program, accept sits in the receive loop: if accept(m) then rs.set("top", m.data == "open") end. m.via == "cable" is another useful test: only a computer wired to yours can pass it.

See also net.send() os.pull_event() Events

Who is in reach

#

net.computers()

→ table⚙ cost 64

The computers this one can send to right now, each one as a table:

Returns
table
a list of {id, via, distance, label}, one per computer in reach
fieldcontent
idits address
via"cable", "radio" or "wifi"
distancethe distance in blocks, to a tenth
labelits name (given with label or os.label), absent when it has none

The computers on the same cable come first. A computer that is switched off is listed (it is in reach), but net.send to it returns false.

Brass
for _, c in ipairs(net.computers()) do
  print("#" .. c.id, c.via, c.distance .. " blocks", c.label or "")
end

Find a computer by its label instead of writing its address in the program, so that it keeps working when you replace a computer:

Brass
local function find(label)
  for _, c in ipairs(net.computers()) do
    if c.label == label then return c.id end
  end
  return nil
end

local warehouse = find("warehouse")
if warehouse then
  net.send(warehouse, "how much?", "stock")
end

net.computers costs 64 instructions plus the search (see net.send): call it once at start, not in every turn of a loop.

See also net.send() net.networks()

#

net.networks()

→ table⚙ cost 64

The Wi-Fi networks this computer is on: those of its own Wi-Fi Routers, and those of the routers its radios reach. Each one is a table:

Returns
table
a list of {ssid, routers, distance, own}, one per Wi-Fi network in reach
fieldcontent
ssidthe name of the network
routershow many routers of that name take part, across the dimension
distancethe distance to the nearest of them, in blocks, to a tenth
owntrue when one of them belongs to this computer (touching it or on its cable)
Brass
for _, n in ipairs(net.networks()) do
  local kind = "nearby"
  if n.own then kind = "mine" end
  print(n.ssid .. ": " .. n.routers .. " routers, nearest " .. n.distance .. " blocks (" .. kind .. ")")
end

The list is empty on a Minicomputer, without any router in reach, and with routers that have no SSID yet: a router gets its network name from a program, with radio.set_ssid().

See also net.computers() radio.set_ssid()

#

net.wireless()

→ boolean

Tells whether the computer has a radio: a Radio Modem or a Wi-Fi Router touching it, or anywhere on its Data Cable network. Always false on a Minicomputer, which only uses cables.

Returns
boolean
true when a Radio Modem or a Wi-Fi Router is attached
Brass
if not net.wireless() then
  print("No radio: only computers on the cable will hear me.")
end

Common patterns

A chat. Each line you type goes to every computer in reach; the messages received meanwhile are shown after each line (press Enter on an empty line to look for new ones). The messages are tables with the author's name, so that the others know who speaks:

Brass
local me = os.label() or ("#" .. net.id())
print("Chat as " .. me .. ". Type /quit to leave.")
while true do
  write("> ")
  local line = read()
  if line == "/quit" then break end
  if line ~= "" then
    local heard = net.broadcast({from = me, text = line}, "chat")
    print("(" .. heard .. " listening)")
  end
  local m = net.receive(0)
  while m do
    if m.channel == "chat" and type(m.data) == "table" then
      print(tostring(m.data.from) .. ": " .. tostring(m.data.text))
    end
    m = net.receive(0)
  end
end

Messages that arrive while read() waits for your line are kept in the queue, so none is lost.

A question with a timeout and retries. A message can be lost: the other computer was off, overloaded, or its chunk was not loaded. A client that asks the factory for its speed tries three times, and recognises the answer by an id it puts in the question:

Brass
local FACTORY = 12
local next_id = 1

local function ask(question)
  local id = next_id
  next_id = next_id + 1
  for attempt = 1, 3 do
    net.send(FACTORY, {type = "request", id = id, question = question}, "factory")
    local deadline = os.clock() + 2
    while os.clock() < deadline do
      local m = net.receive(deadline - os.clock())
      if m and m.sender == FACTORY and type(m.data) == "table"
          and m.data.type == "reply" and m.data.id == id then
        return m.data.answer
      end
    end
  end
  return nil
end

local rpm = ask("speed")
if rpm == nil then print("The factory does not answer") else print("Factory at " .. rpm .. " RPM") end

The factory side answers each request with the same id:

Brass
local motor = peripheral.wrap("top")   -- a Rotation Speed Controller
while true do
  local m = net.receive()
  local d = m.data
  if m.channel == "factory" and type(d) == "table" and d.type == "request" then
    local answer = nil
    if d.question == "speed" then answer = motor.speed() end
    net.send(m.sender, {type = "reply", id = d.id, answer = answer}, "factory")
  end
end

A retry may reach the factory twice (when only the answer was lost), so keep requests safe to repeat: "set the speed to 64" rather than "add 16 RPM".

Sensors and a dashboard. Each sensor broadcasts its reading every few seconds on the "sensors" channel; the dashboard keeps the last reading of each one and redraws every second, which also shows how old each value is:

Brass
-- on each sensor (a Microcontroller next to a vault)
local vault = peripheral.wrap("back")
while true do
  net.broadcast({what = "iron", value = vault.count("minecraft:iron_ingot")}, "sensors")
  sleep(5)
end
Brass
-- on the dashboard
local readings = {}
while true do
  local m = net.receive(1)   -- nil after one second: time to redraw anyway
  if m and m.channel == "sensors" and type(m.data) == "table" then
    readings[m.sender] = {what = m.data.what, value = m.data.value, at = os.clock()}
  end
  term.clear()
  term.set_cursor(1, 1)
  for id, r in pairs(readings) do
    local age = math.floor(os.clock() - r.at)
    print("#" .. id .. " " .. tostring(r.what) .. ": " .. tostring(r.value) .. " (" .. age .. " s ago)")
  end
end

A sensor whose age keeps growing has stopped talking: broken, switched off, or its chunk unloaded. A complete three-computer version of this, with commands and acknowledgements, is in Networks. The built-in programs talk, listen and pong are other complete examples.

See also net.networks()