Create: Computing AgesDoc Brass
Bibliothèques

net

Des messages entre ordinateurs, par câble, radio ou Wi-Fi.

Mini-ordinateur et suivants

La bibliothèque net permet aux ordinateurs de se parler. Un ordinateur envoie une donnée (un nombre, un mot, toute une table de mesures), et l'autre la reçoit sous la forme d'un évènement message. Grâce à elle, un capteur de l'entrepôt peut dire à l'usine combien de fer il reste, un tableau de bord peut afficher toutes les machines de la base sur un seul Moniteur LCD, et une télécommande peut ouvrir une porte à l'autre bout de la carte.

Brass
-- sur l'ordinateur de l'entrepôt : dire à l'ordinateur n°12 combien il y a de fer
net.send(12, {objet = "minecraft:iron_ingot", nombre = 1830}, "stock")

-- sur l'ordinateur n°12 : attendre le prochain message et l'afficher
local m = net.receive()
print(m.data.objet .. " : " .. m.data.nombre)

Quels ordinateurs l'ont, et comment ils se joignent :

ordinateurnetliaisons
Calculateur à tubes, Ordinateur à transistorsnonaucune : net vaut nil
Mini-ordinateurouiCâble de données seulement
Micro-ordinateur, Microcontrôleur, Ordinateur moderneouiCâble de données, Modem radio, Routeur Wi-Fi

La façon dont câbles, radios et routeurs relient les ordinateurs, avec leurs portées, est expliquée dans Réseaux. Cette page-ci parle des fonctions. Un programme fait pour tourner partout peut d'abord vérifier que la bibliothèque existe :

Brass
if net == nil then
  print("Cet ordinateur n'a pas de port réseau.")
  return
end
print("Réseau prêt")
Écran
Cet ordinateur n'a pas de port réseau.

(C'est ce qu'affiche un Calculateur à tubes. Un Mini-ordinateur ou plus récent affiche Réseau prêt.)

Fonctions
net.id()L'adresse de cet ordinateur, le numéro que les autres utilisent pour lui envoyer un message. C'est la même chose que os.id().
net.send(id, data [, channel])Envoie data à l'ordinateur dont l'adresse est id.
net.broadcast(data [, channel])Envoie data à tous les ordinateurs joignables, et renvoie combien l'ont accepté.
net.receive([timeout])Attend le prochain message et le renvoie. Avec un timeout, abandonne au bout de ce nombre de secondes et renvoie nil.
net.computers()Les ordinateurs auxquels celui-ci peut envoyer un message en ce moment, chacun sous forme de table :
net.networks()Les réseaux Wi-Fi dont fait partie cet ordinateur : ceux de ses propres Routeurs Wi-Fi, et ceux des routeurs que ses radios atteignent. Chacun est une table :
net.wireless()Dit si l'ordinateur a une radio : un Modem radio ou un Routeur Wi-Fi qui le touche, ou n'importe où sur son réseau de Câbles de données. Toujours false sur un Mini-ordinateur, qui n'utilise que les câbles.

Adresses

Chaque ordinateur a une adresse : un numéro reçu la première fois qu'il est posé, le même que renvoie os.id() et qu'affiche la commande id du shell. Il reste attaché à l'ordinateur quand vous le cassez ou le ramassez à la clé pour le poser ailleurs. C'est à une adresse qu'on envoie un message.

#

net.id()

→ number

L'adresse de cet ordinateur, le numéro que les autres utilisent pour lui envoyer un message. C'est la même chose que os.id().

Renvoie
number
l'adresse de cet ordinateur
Brass
print("Mon adresse : " .. net.id())

Notez-la sur une pancarte à côté de l'ordinateur, ou donnez un nom à l'ordinateur avec la commande label du shell (ou os.label) : net.computers montre le nom des ordinateurs joignables, et un programme peut ainsi trouver « l'entrepôt » sans connaître son numéro.

Voir aussi net.computers()

Envoyer

Un message transporte une valeur, la donnée : nil, un booléen, un nombre, une chaîne, ou une table faite de tout cela (des tables dans des tables, aucun souci). Une fonction ne voyage pas : le programme s'arrête avec cannot send a function. Le destinataire reçoit une copie : modifier votre table après l'envoi ne change pas ce que l'autre ordinateur a reçu.

Un message a aussi un canal, un petit mot de votre choix ("stock", "porte", "chat"), "default" si vous n'en donnez pas. Les canaux permettent à un ordinateur de mener plusieurs conversations et d'ignorer celles qui ne le concernent pas.

La taille d'un message est limitée, et vérifiée à l'envoi :

  • 2048 valeurs au plus, où une table compte une, chacune de ses valeurs une, et chaque clé d'une table à noms une de plus ({x = 1, y = 2} compte 5, une liste de 1000 nombres compte 1001) ;
  • 32768 caractères au plus dans l'ensemble de ses chaînes, clés comprises ;
  • 32 tables imbriquées au plus.

Au-delà, le programme s'arrête avec message too large (ou table is too deep (or cyclic) to convert pour une table qui se contient elle-même). Pour envoyer plus, découpez la donnée en plusieurs messages.

L'envoi est immédiat : le message est dans la file du destinataire dès le même tick. Il n'est accepté que si :

  • le destinataire est joignable : sur le même réseau de Câbles de données, ou relié par radio ou Wi-Fi (voir Réseaux) ;
  • il est allumé, dans un chunk chargé et actif ;
  • son tampon de réception n'est pas plein : les messages en attente dans sa file peuvent occuper jusqu'au quart de sa mémoire (4 K cellules sur un Microcontrôleur, 8 K sur un Mini-ordinateur, 32 K sur un Micro-ordinateur, 256 K sur un Ordinateur moderne). Au-delà, les nouveaux messages sont refusés, comme par un tampon réseau plein, au lieu de faire planter le programme qui reçoit.
Important

true veut dire « le message est dans la file du destinataire », pas « le destinataire l'a traité ». Un ordinateur qui attend à l'invite du shell, sans programme lancé, jette les messages qu'il reçoit, bien que net.send ait renvoyé true : un destinataire doit tourner en programme, en général son startup. Pour savoir qu'un ordre a été exécuté, faites répondre le destinataire (voir les motifs plus bas et Réseaux).

#

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

→ boolean⚙ coût 32

Envoie data à l'ordinateur dont l'adresse est id.

Paramètres
id number
l'adresse du destinataire
data any
la valeur à envoyer : nil, booléen, nombre, chaîne ou table de ces valeurs
channel string facultatif
le canal, "default" s'il est omis
Renvoie
boolean
true si le destinataire a accepté le message, false sinon
Brass
local USINE = 12
local ok = net.send(USINE, {type = "commande", objet = "minecraft:iron_ingot", nombre = 64}, "commandes")
if not ok then
  print("L'usine ne répond pas : hors de portée, éteinte ou saturée.")
end

net.send renvoie false quand aucun ordinateur de cette adresse n'est joignable (trop loin, dans un chunk non chargé, dans une autre dimension, ou c'est cet ordinateur lui-même), quand il est éteint, ou quand son tampon de réception est plein. Il n'attend pas : le programme continue tout de suite.

La donnée peut être omise, pour un message dont le canal dit tout :

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

De mauvais arguments arrêtent le programme : bad argument #1 to 'send' (number expected, got string) quand l'adresse n'est pas un nombre, bad argument #3 to 'send' (string expected, got number) quand le canal n'est pas une chaîne.

En plus de ses 32 instructions, un envoi coûte une instruction par valeur du message et une tous les 16 caractères (la copie), plus la recherche du destinataire : une instruction par case autour des Câbles de données de l'ordinateur et, sur un Micro-ordinateur, un Microcontrôleur ou un Ordinateur moderne, une par ordinateur chargé dans le monde. Sur un grand réseau, envoyez peu de gros messages plutôt que beaucoup de petits.

Voir aussi net.broadcast() net.receive()

#

net.broadcast(data [, channel])

→ number⚙ coût 64

Envoie data à tous les ordinateurs joignables, et renvoie combien l'ont accepté.

Paramètres
data any
la valeur à envoyer, avec les mêmes règles que net.send
channel string facultatif
le canal, "default" s'il est omis
Renvoie
number
le nombre d'ordinateurs qui ont accepté le message

Une diffusion convient aux nouvelles qui peuvent intéresser n'importe qui : une mesure, une alarme, un « je suis là ». Les ordinateurs que ça ne concerne pas ignorent simplement le canal.

Brass
local nombre = net.broadcast("creeper à la porte nord !", "alarme")
print(nombre .. " ordinateurs prévenus")

Un ordinateur éteint, hors de portée ou saturé est sauté : il n'est pas compté, et il n'y a pas d'erreur. 0 veut dire que personne ne vous a entendu. Une diffusion coûte 64 instructions, plus la copie et la recherche comme net.send, plus 8 par ordinateur joignable.

Attention

Ne répondez jamais à une diffusion par une diffusion sur le même canal : deux ordinateurs qui font ça se renvoient le message sans fin. Répondez avec net.send(m.sender, ...), et voyez Réseaux pour les relais.

Voir aussi net.send()

Recevoir

Les messages arrivent dans la file d'évènements de l'ordinateur, sous forme d'évènements message, et y attendent que le programme les lise, même pendant qu'il dort. Un message est une table :

champcontenu
nametoujours "message"
senderl'adresse de l'ordinateur qui l'a envoyé
channelle canal sur lequel il a été envoyé
dataune copie de la donnée (absente si c'est nil qui a été envoyé)
viale chemin suivi : "cable", "radio" ou "wifi"
distancela distance entre les deux ordinateurs, en blocs, au dixième
#

net.receive([timeout])

→ table|nil⏸ Attend

Attend le prochain message et le renvoie. Avec un timeout, abandonne au bout de ce nombre de secondes et renvoie nil.

Paramètres
timeout number facultatif
l'attente maximale en secondes ; sans elle, attend aussi longtemps qu'il faut
Renvoie
table|nil
le message, ou nil si le temps est écoulé
Brass
print("J'écoute, je suis le n°" .. net.id())
while true do
  local m = net.receive()
  print("n°" .. m.sender .. " sur " .. m.channel .. " : " .. tostring(m.data))
end

Les champs d'un message, écrits ici à la main pour montrer ce que reçoit un programme :

Brass
local m = {name = "message", sender = 7, channel = "stock", via = "wifi", distance = 182.4,
  data = {objet = "minecraft:iron_ingot", nombre = 1830}}
print("du n°" .. m.sender .. " par " .. m.via .. ", à " .. m.distance .. " blocs")
print(m.data.objet .. " : " .. m.data.nombre)
Écran
du n°7 par wifi, à 182.4 blocs
minecraft:iron_ingot : 1830

Le délai est arrondi au tick suivant (un vingtième de seconde), et vaut au moins un tick : net.receive(0) renvoie un message déjà en attente, ou nil un tick plus tard. Mettez un délai dès qu'une réponse risque de ne jamais venir, pour que le programme ne reste pas bloqué :

Brass
net.send(12, nil, "ping")
local m = net.receive(2)
if m == nil then
  print("Pas de réponse en 2 secondes")
end
Attention

Pendant qu'il attend, net.receive jette tous les autres évènements : minuteurs, touches, clics, changements de redstone. Un programme qui en a aussi besoin utilise os.pull_event() sans filtre et teste e.name, comme ci-dessous.

Les mêmes messages sortent de os.pull_event, qui permet d'attendre des messages et autre chose. os.pull_event("message") fait exactement comme net.receive(). Un ordinateur d'entrepôt qui annonce son stock toutes les 10 secondes et répond aux questions à tout moment :

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

Filtrez ce que vous acceptez. N'importe quel ordinateur joignable peut envoyer sur n'importe quel canal : vérifiez le canal, l'expéditeur et la forme de la donnée avant d'agir. Une porte qui n'obéit qu'aux deux salles de contrôle :

Brass
local DE_CONFIANCE = {[4] = true, [9] = true}
local function accepter(m)
  return m.channel == "porte" and DE_CONFIANCE[m.sender] == true
end
print(accepter({sender = 4, channel = "porte", data = "ouvrir"}))
print(accepter({sender = 5, channel = "porte", data = "ouvrir"}))
print(accepter({sender = 9, channel = "chat", data = "salut"}))
Écran
true
false
false

Dans le vrai programme, accepter se place dans la boucle de réception : if accepter(m) then rs.set("top", m.data == "ouvrir") end. m.via == "cable" est un autre test utile : seul un ordinateur câblé au vôtre peut le passer.

Voir aussi net.send() os.pull_event() Évènements

Qui est joignable

#

net.computers()

→ table⚙ coût 64

Les ordinateurs auxquels celui-ci peut envoyer un message en ce moment, chacun sous forme de table :

Renvoie
table
une liste de {id, via, distance, label}, une entrée par ordinateur joignable
champcontenu
idson adresse
via"cable", "radio" ou "wifi"
distancela distance en blocs, au dixième
labelson nom (donné avec label ou os.label), absent s'il n'en a pas

Les ordinateurs du même câble viennent en premier. Un ordinateur éteint est dans la liste (il est joignable), mais net.send vers lui renvoie false.

Brass
for _, o in ipairs(net.computers()) do
  print("n°" .. o.id, o.via, o.distance .. " blocs", o.label or "")
end

Retrouvez un ordinateur par son nom plutôt que d'écrire son adresse dans le programme : il continuera de marcher le jour où vous remplacerez un ordinateur.

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

local entrepot = trouver("entrepot")
if entrepot then
  net.send(entrepot, "combien ?", "stock")
end

net.computers coûte 64 instructions plus la recherche (voir net.send) : appelez-la une fois au démarrage, pas à chaque tour de boucle.

Voir aussi net.send() net.networks()

#

net.networks()

→ table⚙ coût 64

Les réseaux Wi-Fi dont fait partie cet ordinateur : ceux de ses propres Routeurs Wi-Fi, et ceux des routeurs que ses radios atteignent. Chacun est une table :

Renvoie
table
une liste de {ssid, routers, distance, own}, une entrée par réseau Wi-Fi joignable
champcontenu
ssidle nom du réseau
routersle nombre de routeurs de ce nom qui y participent, dans toute la dimension
distancela distance du plus proche d'entre eux, en blocs, au dixième
owntrue si l'un d'eux appartient à cet ordinateur (il le touche ou il est sur son câble)
Brass
for _, r in ipairs(net.networks()) do
  local genre = "voisin"
  if r.own then genre = "à moi" end
  print(r.ssid .. " : " .. r.routers .. " routeurs, le plus proche à " .. r.distance .. " blocs (" .. genre .. ")")
end

La liste est vide sur un Mini-ordinateur, sans routeur à portée, et avec des routeurs qui n'ont pas encore de SSID : un routeur reçoit son nom de réseau d'un programme, avec radio.set_ssid().

Voir aussi net.computers() radio.set_ssid()

#

net.wireless()

→ boolean

Dit si l'ordinateur a une radio : un Modem radio ou un Routeur Wi-Fi qui le touche, ou n'importe où sur son réseau de Câbles de données. Toujours false sur un Mini-ordinateur, qui n'utilise que les câbles.

Renvoie
boolean
true si un Modem radio ou un Routeur Wi-Fi est branché
Brass
if not net.wireless() then
  print("Pas de radio : seuls les ordinateurs du câble m'entendront.")
end

Motifs courants

Une messagerie. Chaque ligne tapée part vers tous les ordinateurs joignables ; les messages reçus entre-temps s'affichent après chaque ligne (appuyez sur Entrée sur une ligne vide pour voir les nouveaux). Les messages sont des tables avec le nom de l'auteur, pour que les autres sachent qui parle :

Brass
local moi = os.label() or ("n°" .. net.id())
print("Messagerie : vous êtes " .. moi .. ". Tapez /quitter pour sortir.")
while true do
  write("> ")
  local ligne = read()
  if ligne == "/quitter" then break end
  if ligne ~= "" then
    local auditeurs = net.broadcast({de = moi, texte = ligne}, "chat")
    print("(" .. auditeurs .. " à l'écoute)")
  end
  local m = net.receive(0)
  while m do
    if m.channel == "chat" and type(m.data) == "table" then
      print(tostring(m.data.de) .. " : " .. tostring(m.data.texte))
    end
    m = net.receive(0)
  end
end

Les messages qui arrivent pendant que read() attend votre ligne restent dans la file : aucun n'est perdu.

Une question avec délai et nouvelles tentatives. Un message peut se perdre : l'autre ordinateur était éteint, saturé, ou son chunk n'était pas chargé. Un client qui demande sa vitesse à l'usine essaie trois fois, et reconnaît la réponse grâce à un id qu'il met dans la question :

Brass
local USINE = 12
local prochain_id = 1

local function demander(question)
  local id = prochain_id
  prochain_id = prochain_id + 1
  for essai = 1, 3 do
    net.send(USINE, {type = "requete", id = id, question = question}, "usine")
    local limite = os.clock() + 2
    while os.clock() < limite do
      local m = net.receive(limite - os.clock())
      if m and m.sender == USINE and type(m.data) == "table"
          and m.data.type == "reponse" and m.data.id == id then
        return m.data.reponse
      end
    end
  end
  return nil
end

local tr_min = demander("vitesse")
if tr_min == nil then print("L'usine ne répond pas") else print("Usine à " .. tr_min .. " tr/min") end

Côté usine, chaque requête reçoit une réponse avec le même id :

Brass
local moteur = peripheral.wrap("top")   -- un Contrôleur de vitesse de rotation
while true do
  local m = net.receive()
  local d = m.data
  if m.channel == "usine" and type(d) == "table" and d.type == "requete" then
    local reponse = nil
    if d.question == "vitesse" then reponse = moteur.speed() end
    net.send(m.sender, {type = "reponse", id = d.id, reponse = reponse}, "usine")
  end
end

Une nouvelle tentative peut atteindre l'usine deux fois (quand seule la réponse s'est perdue) : gardez des requêtes qu'on peut répéter sans risque, « règle la vitesse à 64 » plutôt que « ajoute 16 tr/min ».

Des capteurs et un tableau de bord. Chaque capteur diffuse sa mesure toutes les quelques secondes sur le canal "capteurs" ; le tableau de bord garde la dernière mesure de chacun et se redessine chaque seconde, ce qui montre aussi l'âge de chaque valeur :

Brass
-- sur chaque capteur (un Microcontrôleur à côté d'un Coffre à objet renforcé)
local coffre = peripheral.wrap("back")
while true do
  net.broadcast({quoi = "fer", valeur = coffre.count("minecraft:iron_ingot")}, "capteurs")
  sleep(5)
end
Brass
-- sur le tableau de bord
local mesures = {}
while true do
  local m = net.receive(1)   -- nil au bout d'une seconde : de quoi redessiner quand même
  if m and m.channel == "capteurs" and type(m.data) == "table" then
    mesures[m.sender] = {quoi = m.data.quoi, valeur = m.data.valeur, quand = os.clock()}
  end
  term.clear()
  term.set_cursor(1, 1)
  for id, mesure in pairs(mesures) do
    local age = math.floor(os.clock() - mesure.quand)
    print("n°" .. id .. " " .. tostring(mesure.quoi) .. " : " .. tostring(mesure.valeur) .. " (il y a " .. age .. " s)")
  end
end

Un capteur dont l'âge ne cesse de grandir s'est tu : cassé, éteint, ou son chunk déchargé. Une version complète à trois ordinateurs, avec des ordres et des accusés de réception, se trouve dans Réseaux. Les programmes intégrés talk, listen et pong sont d'autres exemples complets.

Voir aussi net.networks()