Alarme de stock et panneau d'affichage
Surveiller un Coffre à objet renforcé de Create, afficher le stock sur un moniteur et un Panneau d'affichage, allumer une lampe et sonner une cloche quand un objet vient à manquer.
Votre usine remplit un Coffre à objet renforcé de Create avec du fer, du cuivre, de l'alliage d'andésite et du laiton, et le reste de la base vient s'y servir. Un jour, le laiton s'épuise, les établis mécaniques s'arrêtent, et vous ne le remarquez qu'une heure plus tard. Ce projet place à côté du coffre renforcé un ordinateur qui compte chaque objet qui vous intéresse, affiche les quantités sur son écran (et sur un mur de moniteurs), les recopie sur un Panneau d'affichage de Create par une Liaison d'affichage, allume une lampe tant qu'un objet est sous son seuil et sonne une cloche au moment où il passe dessous.
C'est le premier projet du livre de recettes, parce qu'il emploie les outils de base de tout programme d'usine : un périphérique d'inventaire (Inventaires), les sorties redstone (rs), une fréquence de Liaison de redstone (link), les lignes de la Liaison d'affichage (display), un écran en couleurs (term) et une boucle d'évènements rythmée par des minuteurs (Évènements).
Ce qu'il vous faut
- Un ordinateur avec un écran. Les bibliothèques utilisées ici (
peripheral,rs,link,display) existent sur tous les ordinateurs. Un Micro-ordinateur affiche les couleurs ; un Ordinateur à transistors ou un Mini-ordinateur montre le même écran en vert ou en ambre. À partir du Mini-ordinateur, le coffre renforcé peut aussi se trouver n'importe où sur un Câble de données au lieu de toucher l'ordinateur. - Un Coffre à objet renforcé de Create (un coffre, un tonneau ou un dépôt de Create marchent de la même façon : tout ce qui a un inventaire).
- Un mur de Moniteurs cathodiques ou de Moniteurs LCD qui touche l'ordinateur : il montre l'écran de l'ordinateur en grand. Facultatif.
- Une lampe à redstone contre l'ordinateur.
- Une cloche avec une Liaison de redstone réglée en réception, n'importe où à portée : l'alarme l'atteint sans aucun fil.
- Une Liaison d'affichage et un Panneau d'affichage. Faites un clic droit sur le Panneau d'affichage avec la Liaison d'affichage pour le cibler, puis posez la liaison contre l'ordinateur, le bloc qu'elle lit (
displaydétaille le réglage). Le panneau affiche alors les lignes que le programme écrit avecdisplay.set.
Vu de face, en regardant l'écran :
+------------+------------+
| Moniteur | Moniteur | mur de moniteurs, contre l'ordinateur
+-----------+ +------------+------------+
| Coffre | | Ordinateur | Lampe | coffre renforcé à gauche, lampe à droite
+-----------+ +------------+------------+
arbre à l'arrière
Ailleurs : [Liaison de redstone, réception] [Cloche]
fréquence : lingot de fer + clocheMettez un lingot de fer et une cloche dans les deux emplacements de fréquence de la Liaison de redstone réceptrice : c'est la fréquence sur laquelle le programme émet.
Le programme, étape par étape
1. Les réglages
Tout ce que vous voudrez peut-être changer est en haut : où se trouvent le coffre renforcé et la lampe, la fréquence de la cloche, le rythme des lectures, et la liste des objets suivis avec leur seuil. Un objet « manque » quand le coffre en contient moins que seuil.
local COFFRE = "left" -- un côté, ou un nom de câble comme "inventory@12,64,-3"
local LAMPE = "right" -- côté de la lampe à redstone
local CLOCHE = {"minecraft:iron_ingot", "minecraft:bell"} -- fréquence de la Liaison de redstone
local PERIODE = 2 -- secondes entre deux lectures
local BARRE = 13 -- largeur des barres de niveau, en caractères
local SUIVIS = {
{id = "minecraft:iron_ingot", nom = "Fer (lingots)", seuil = 256},
{id = "minecraft:copper_ingot", nom = "Cuivre (lingots)", seuil = 128},
{id = "create:andesite_alloy", nom = "Alliage andésite", seuil = 64},
{id = "create:brass_ingot", nom = "Laiton (lingots)", seuil = 32},
{id = "minecraft:coal", nom = "Charbon", seuil = 64},
}Pour connaître l'identifiant d'un objet, posez-le dans le coffre renforcé et regardez le champ name de coffre.list() (voir plus bas), ou appuyez sur F3+H dans Minecraft pour voir les identifiants dans les infobulles.
2. Trouver et lire le coffre renforcé
peripheral.wrap renvoie un objet pour piloter le bloc d'un côté (ou du câble), ou nil s'il n'y a rien. Si le côté réglé ne porte aucun inventaire, le programme se rabat sur peripheral.find("inventory"), le premier inventaire qu'il peut atteindre.
local coffre = nil -- le coffre prêt à piloter, nil tant qu'il est introuvable
local function connecter()
coffre = peripheral.wrap(COFFRE)
if coffre == nil or coffre.list == nil then
coffre = peripheral.find("inventory")
end
endPour compter, le programme pourrait appeler coffre.count(id) une fois par objet suivi. Mais un grand coffre renforcé a des centaines d'emplacements, et chaque count les parcourt tous. Un seul appel à coffre.list() lit chaque emplacement une fois ; le programme additionne ensuite les piles lui-même :
local function lire_stock()
local totaux = {}
for _, pile in pairs(coffre.list()) do
totaux[pile.name] = (totaux[pile.name] or 0) + pile.count
end
return totaux
endcoffre.list() renvoie une table indexée par numéro d'emplacement, avec des trous là où les emplacements sont vides : il faut la parcourir avec pairs, pas ipairs. Chaque pile est une table {name = "minecraft:iron_ingot", count = 64, display = "Iron Ingot"}.
Si le coffre renforcé est cassé ou si son chunk se décharge, l'appel lève une erreur (not an inventory anymore, peripheral is not loaded). Le programme le lance à travers pcall (voir l'étape 4) : un coffre absent affiche un message, et le programme continue d'essayer au lieu de s'arrêter.
3. Dessiner l'écran
L'écran a une barre de titre, une ligne par objet (nom, stock, seuil, une barre de niveau et un état) et un résumé en bas. La barre est une suite d'espaces sur fond coloré : pleine quand le coffre contient quatre fois le seuil, si bien que le seuil se trouve au quart de la barre.
local derniere_cloche = nil -- l'heure du dernier coup de cloche, en texte
local function heure_texte()
local t = (os.day_time() + 6000) % 24000 -- 0 = 6 h du matin
return string.format("%02d:%02d", t // 1000, t % 1000 * 60 // 1000)
end
local function barre(part, couleur)
local plein = math.floor(math.max(0, math.min(1, part)) * BARRE + 0.5)
term.set_bg(couleur)
term.write(string.rep(" ", plein))
term.set_bg(term.colors.gray)
term.write(string.rep(" ", BARRE - plein))
term.set_bg(term.colors.black)
end
local function dessiner(totaux)
term.set_bg(term.colors.black)
term.clear()
term.set_cursor(1, 1)
term.set_bg(term.colors.blue)
term.set_fg(term.colors.white)
term.write(string.format(" %-43s%6s ", "ALARME DE STOCK", heure_texte()))
term.set_bg(term.colors.black)
term.set_fg(term.colors.light_gray)
term.set_cursor(1, 3)
term.write(string.format(" %-16s%7s%7s %-13s %-5s", "Objet", "Stock", "Seuil", "Niveau", "État"))
local bas = 0
for i, objet in ipairs(SUIVIS) do
local quantite = totaux[objet.id] or 0
local manque = quantite < objet.seuil
term.set_cursor(1, 3 + i * 2)
term.set_fg(term.colors.white)
term.write(string.format(" %-16s%7d%7d ", objet.nom, quantite, objet.seuil))
if manque then
bas = bas + 1
barre(quantite / (objet.seuil * 4), term.colors.red)
term.set_fg(term.colors.red)
term.write(" BAS")
else
barre(quantite / (objet.seuil * 4), term.colors.green)
term.set_fg(term.colors.green)
term.write(" OK")
end
end
term.set_cursor(2, 18)
if bas == 0 then
term.set_fg(term.colors.green)
term.write("Tous les stocks sont au-dessus de leur seuil.")
else
term.set_fg(term.colors.red)
local texte = bas .. " objet(s) bas, lampe allumée"
if derniere_cloche then texte = texte .. ", cloche à " .. derniere_cloche end
term.write(texte .. ".")
end
term.set_cursor(2, 19)
term.set_fg(term.colors.gray)
term.write("Lecture toutes les " .. PERIODE .. " s. Clic : lecture immédiate.")
endterm.write écrit au curseur sans passer à la ligne suivante, ce qu'il faut pour une mise en page fixe ; string.format aligne chaque colonne (%-16s veut dire « du texte sur 16 caractères, calé à gauche », %7d « un nombre entier sur 7, calé à droite »).
Quand le coffre renforcé ne peut pas être lu, un court message remplace le tableau :
local function dessiner_absent()
term.set_bg(term.colors.black)
term.clear()
term.set_cursor(2, 2)
term.set_fg(term.colors.red)
term.write("Aucun coffre trouvé sur '" .. COFFRE .. "'.")
term.set_cursor(2, 4)
term.set_fg(term.colors.light_gray)
term.write("Nouvel essai toutes les " .. PERIODE .. " s.")
endLe Panneau d'affichage reçoit des lignes plus courtes, une par objet, avec BAS au bout de celles qui sont sous leur seuil :
local function lignes_tableau(totaux)
local lignes = {}
for _, objet in ipairs(SUIVIS) do
local quantite = totaux[objet.id] or 0
local ligne = objet.nom .. " : " .. quantite
if quantite < objet.seuil then ligne = ligne .. " BAS" end
table.insert(lignes, ligne)
end
return lignes
end4. Décider : lampe et cloche
La lampe est simple : elle reste allumée tant qu'au moins un objet manque. La cloche, elle, ne doit pas sonner toutes les deux secondes pendant une heure : elle ne sonne que quand un objet devient bas. La table etait_bas retient quels objets l'étaient déjà à la lecture précédente.
Une cloche sonne sur un front montant de redstone : le programme envoie donc une courte impulsion, en allumant la fréquence puis en lançant un minuteur qui l'éteint une demi-seconde plus tard.
local etait_bas = {} -- id d'objet -> true s'il était bas à la dernière lecture
local minuteur_cloche = nil -- le minuteur qui termine l'impulsion de la cloche
local function sonner()
link.set(CLOCHE[1], CLOCHE[2], 15)
minuteur_cloche = os.start_timer(0.5)
derniere_cloche = heure_texte()
end
local function mettre_a_jour()
if coffre == nil then connecter() end
local resultat = nil
if coffre ~= nil then resultat = pcall(lire_stock) end
if resultat == nil or not resultat.ok then
coffre = nil
rs.set(LAMPE, true) -- une alarme en panne ne doit pas faire croire que tout va bien
dessiner_absent()
display.set({"Alarme de stock", "coffre introuvable"})
return
end
local totaux = resultat.value
local un_bas, nouveau_bas = false, false
for _, objet in ipairs(SUIVIS) do
local manque = (totaux[objet.id] or 0) < objet.seuil
if manque then
un_bas = true
if not etait_bas[objet.id] then nouveau_bas = true end
end
etait_bas[objet.id] = manque
end
rs.set(LAMPE, un_bas)
if nouveau_bas then sonner() end
dessiner(totaux)
display.set(lignes_tableau(totaux))
endpcall(lire_stock) renvoie une table : {ok = true, value = totaux} quand la lecture a marché, {ok = false, error = "..."} quand elle a échoué. Notez que rs.set accepte true ou false aussi bien qu'une puissance : true vaut 15.
5. La boucle d'évènements
Le programme attend les évènements avec os.pull_event(). Trois sortes comptent :
- le minuteur de lecture : lire le coffre, puis lancer le minuteur suivant ;
- le minuteur de la cloche : terminer l'impulsion ;
- un clic sur l'écran ou sur le moniteur : lire tout de suite, sans attendre.
connecter()
mettre_a_jour()
local minuteur_lecture = os.start_timer(PERIODE)
while true do
local e = os.pull_event()
if e.name == "timer" and e.id == minuteur_lecture then
mettre_a_jour()
minuteur_lecture = os.start_timer(PERIODE)
elseif e.name == "timer" and e.id == minuteur_cloche then
link.set(CLOCHE[1], CLOCHE[2], 0)
elseif e.name == "click" then
mettre_a_jour()
end
endPourquoi des minuteurs plutôt qu'un sleep(2) dans une boucle ? Pendant un sleep, le programme ne peut ni terminer l'impulsion de la cloche ni répondre à un clic. Avec des minuteurs, chaque attente passe par un seul os.pull_event, et le programme réagit à ce qui arrive en premier. La page Évènements explique ce schéma en détail.
Comme le programme attend toutes les sortes d'évènements, clics compris, un clic droit sur le moniteur appuie sur l'écran (et lance une lecture) au lieu d'ouvrir le terminal. Accroupissez-vous et faites un clic droit pour ouvrir le terminal, puis Ctrl+T pour arrêter le programme.
Le programme complet
Enregistrez-le sous le nom startup pour qu'il démarre avec l'ordinateur (après un redémarrage du serveur aussi) :
-- Alarme de stock : surveille un coffre renforcé de Create, affiche le stock
-- à l'écran et sur un panneau d'affichage, allume une lampe et sonne une cloche
-- quand un objet vient à manquer.
local COFFRE = "left" -- un côté, ou un nom de câble comme "inventory@12,64,-3"
local LAMPE = "right" -- côté de la lampe à redstone
local CLOCHE = {"minecraft:iron_ingot", "minecraft:bell"} -- fréquence de la Liaison de redstone
local PERIODE = 2 -- secondes entre deux lectures
local BARRE = 13 -- largeur des barres de niveau, en caractères
local SUIVIS = {
{id = "minecraft:iron_ingot", nom = "Fer (lingots)", seuil = 256},
{id = "minecraft:copper_ingot", nom = "Cuivre (lingots)", seuil = 128},
{id = "create:andesite_alloy", nom = "Alliage andésite", seuil = 64},
{id = "create:brass_ingot", nom = "Laiton (lingots)", seuil = 32},
{id = "minecraft:coal", nom = "Charbon", seuil = 64},
}
local coffre = nil -- le coffre prêt à piloter, nil tant qu'il est introuvable
local etait_bas = {} -- id d'objet -> true s'il était bas à la dernière lecture
local minuteur_cloche = nil -- le minuteur qui termine l'impulsion de la cloche
local derniere_cloche = nil -- l'heure du dernier coup de cloche, en texte
-- Trouver et lire le coffre ----------------------------------------------------
local function connecter()
coffre = peripheral.wrap(COFFRE)
if coffre == nil or coffre.list == nil then
coffre = peripheral.find("inventory")
end
end
local function lire_stock()
local totaux = {}
for _, pile in pairs(coffre.list()) do
totaux[pile.name] = (totaux[pile.name] or 0) + pile.count
end
return totaux
end
-- Dessin ----------------------------------------------------------------------------
local function heure_texte()
local t = (os.day_time() + 6000) % 24000 -- 0 = 6 h du matin
return string.format("%02d:%02d", t // 1000, t % 1000 * 60 // 1000)
end
local function barre(part, couleur)
local plein = math.floor(math.max(0, math.min(1, part)) * BARRE + 0.5)
term.set_bg(couleur)
term.write(string.rep(" ", plein))
term.set_bg(term.colors.gray)
term.write(string.rep(" ", BARRE - plein))
term.set_bg(term.colors.black)
end
local function dessiner(totaux)
term.set_bg(term.colors.black)
term.clear()
term.set_cursor(1, 1)
term.set_bg(term.colors.blue)
term.set_fg(term.colors.white)
term.write(string.format(" %-43s%6s ", "ALARME DE STOCK", heure_texte()))
term.set_bg(term.colors.black)
term.set_fg(term.colors.light_gray)
term.set_cursor(1, 3)
term.write(string.format(" %-16s%7s%7s %-13s %-5s", "Objet", "Stock", "Seuil", "Niveau", "État"))
local bas = 0
for i, objet in ipairs(SUIVIS) do
local quantite = totaux[objet.id] or 0
local manque = quantite < objet.seuil
term.set_cursor(1, 3 + i * 2)
term.set_fg(term.colors.white)
term.write(string.format(" %-16s%7d%7d ", objet.nom, quantite, objet.seuil))
if manque then
bas = bas + 1
barre(quantite / (objet.seuil * 4), term.colors.red)
term.set_fg(term.colors.red)
term.write(" BAS")
else
barre(quantite / (objet.seuil * 4), term.colors.green)
term.set_fg(term.colors.green)
term.write(" OK")
end
end
term.set_cursor(2, 18)
if bas == 0 then
term.set_fg(term.colors.green)
term.write("Tous les stocks sont au-dessus de leur seuil.")
else
term.set_fg(term.colors.red)
local texte = bas .. " objet(s) bas, lampe allumée"
if derniere_cloche then texte = texte .. ", cloche à " .. derniere_cloche end
term.write(texte .. ".")
end
term.set_cursor(2, 19)
term.set_fg(term.colors.gray)
term.write("Lecture toutes les " .. PERIODE .. " s. Clic : lecture immédiate.")
end
local function dessiner_absent()
term.set_bg(term.colors.black)
term.clear()
term.set_cursor(2, 2)
term.set_fg(term.colors.red)
term.write("Aucun coffre trouvé sur '" .. COFFRE .. "'.")
term.set_cursor(2, 4)
term.set_fg(term.colors.light_gray)
term.write("Nouvel essai toutes les " .. PERIODE .. " s.")
end
local function lignes_tableau(totaux)
local lignes = {}
for _, objet in ipairs(SUIVIS) do
local quantite = totaux[objet.id] or 0
local ligne = objet.nom .. " : " .. quantite
if quantite < objet.seuil then ligne = ligne .. " BAS" end
table.insert(lignes, ligne)
end
return lignes
end
-- Lampe et cloche -------------------------------------------------------------------
local function sonner()
link.set(CLOCHE[1], CLOCHE[2], 15)
minuteur_cloche = os.start_timer(0.5)
derniere_cloche = heure_texte()
end
local function mettre_a_jour()
if coffre == nil then connecter() end
local resultat = nil
if coffre ~= nil then resultat = pcall(lire_stock) end
if resultat == nil or not resultat.ok then
coffre = nil
rs.set(LAMPE, true) -- une alarme en panne ne doit pas faire croire que tout va bien
dessiner_absent()
display.set({"Alarme de stock", "coffre introuvable"})
return
end
local totaux = resultat.value
local un_bas, nouveau_bas = false, false
for _, objet in ipairs(SUIVIS) do
local manque = (totaux[objet.id] or 0) < objet.seuil
if manque then
un_bas = true
if not etait_bas[objet.id] then nouveau_bas = true end
end
etait_bas[objet.id] = manque
end
rs.set(LAMPE, un_bas)
if nouveau_bas then sonner() end
dessiner(totaux)
display.set(lignes_tableau(totaux))
end
-- Boucle principale -------------------------------------------------------------------
connecter()
mettre_a_jour()
local minuteur_lecture = os.start_timer(PERIODE)
while true do
local e = os.pull_event()
if e.name == "timer" and e.id == minuteur_lecture then
mettre_a_jour()
minuteur_lecture = os.start_timer(PERIODE)
elseif e.name == "timer" and e.id == minuteur_cloche then
link.set(CLOCHE[1], CLOCHE[2], 0)
elseif e.name == "click" then
mettre_a_jour()
end
endÀ quoi ressemble l'écran
Voici le code de dessin du programme, nourri avec une lecture d'exemple au lieu d'un vrai coffre (le charbon et le laiton manquent) :
local PERIODE = 2
local BARRE = 13
local SUIVIS = {
{id = "minecraft:iron_ingot", nom = "Fer (lingots)", seuil = 256},
{id = "minecraft:copper_ingot", nom = "Cuivre (lingots)", seuil = 128},
{id = "create:andesite_alloy", nom = "Alliage andésite", seuil = 64},
{id = "create:brass_ingot", nom = "Laiton (lingots)", seuil = 32},
{id = "minecraft:coal", nom = "Charbon", seuil = 64},
}
local derniere_cloche = "11:40"
local function heure_texte()
local t = (os.day_time() + 6000) % 24000
return string.format("%02d:%02d", t // 1000, t % 1000 * 60 // 1000)
end
local function barre(part, couleur)
local plein = math.floor(math.max(0, math.min(1, part)) * BARRE + 0.5)
term.set_bg(couleur)
term.write(string.rep(" ", plein))
term.set_bg(term.colors.gray)
term.write(string.rep(" ", BARRE - plein))
term.set_bg(term.colors.black)
end
local function dessiner(totaux)
term.set_bg(term.colors.black)
term.clear()
term.set_cursor(1, 1)
term.set_bg(term.colors.blue)
term.set_fg(term.colors.white)
term.write(string.format(" %-43s%6s ", "ALARME DE STOCK", heure_texte()))
term.set_bg(term.colors.black)
term.set_fg(term.colors.light_gray)
term.set_cursor(1, 3)
term.write(string.format(" %-16s%7s%7s %-13s %-5s", "Objet", "Stock", "Seuil", "Niveau", "État"))
local bas = 0
for i, objet in ipairs(SUIVIS) do
local quantite = totaux[objet.id] or 0
local manque = quantite < objet.seuil
term.set_cursor(1, 3 + i * 2)
term.set_fg(term.colors.white)
term.write(string.format(" %-16s%7d%7d ", objet.nom, quantite, objet.seuil))
if manque then
bas = bas + 1
barre(quantite / (objet.seuil * 4), term.colors.red)
term.set_fg(term.colors.red)
term.write(" BAS")
else
barre(quantite / (objet.seuil * 4), term.colors.green)
term.set_fg(term.colors.green)
term.write(" OK")
end
end
term.set_cursor(2, 18)
if bas == 0 then
term.set_fg(term.colors.green)
term.write("Tous les stocks sont au-dessus de leur seuil.")
else
term.set_fg(term.colors.red)
local texte = bas .. " objet(s) bas, lampe allumée"
if derniere_cloche then texte = texte .. ", cloche à " .. derniere_cloche end
term.write(texte .. ".")
end
term.set_cursor(2, 19)
term.set_fg(term.colors.gray)
term.write("Lecture toutes les " .. PERIODE .. " s. Clic : lecture immédiate.")
end
dessiner({
["minecraft:iron_ingot"] = 1830,
["minecraft:copper_ingot"] = 412,
["create:andesite_alloy"] = 96,
["create:brass_ingot"] = 9,
["minecraft:coal"] = 40,
})
Le Panneau d'affichage montre la même lecture en lignes courtes :
Fer (lingots) : 1830
Cuivre (lingots) : 412
Alliage andésite : 96
Laiton (lingots) : 9 BAS
Charbon : 40 BASL'essayer
- Tapez le programme avec
edit startup, enregistrez avec Ctrl+S, sortez avec Échap et lancez-le avecstartup. - Avec un coffre vide, tous les objets manquent : la lampe s'allume, la cloche sonne une fois, et chaque ligne affiche
BAS. C'est normal : la première lecture trouve tout « nouvellement bas ». - Posez une pile de charbon dans le coffre et cliquez sur l'écran : la ligne du charbon passe au vert, la cloche reste muette.
- Reprenez le charbon : à la lecture suivante, la ligne repasse au rouge et la cloche sonne de nouveau.
- Cassez le coffre (ou réglez
COFFREsur un mauvais côté) : l'écran signale l'absence du coffre et la lampe reste allumée. Remettez-le : le programme le retrouve tout seul.
Si l'écran reste sur « Aucun coffre trouvé », vérifiez depuis le shell que l'ordinateur voit bien le coffre :
> brass Brass 1.0 - type 'exit' to leave. brass> peripheral.type("left") inventory brass> exit
L'exemple devices (devices) montre tous les blocs que l'ordinateur peut atteindre, avec leur nom.
Variantes
- Plus d'objets, d'autres seuils. Ajoutez des lignes à
SUIVIS. L'écran a de la place pour 7 objets ; au-delà, n'affichez que ceux qui manquent, ou partagez la liste entre deux ordinateurs. - Plusieurs coffres. Prenez-les tous (
peripheral.list("inventory")sur un Câble de données donne tous les noms) et additionnez leurs totaux avant la comparaison. - Relancer la production au lieu de sonner. Une fréquence de Liaison de redstone peut piloter un embrayage de Create qui démarre la ferme à fer tant que le fer manque. Attention : un embrayage alimenté débraye ; émettez
not manque, pasmanque. - Un réservoir de fluide. Un Réservoir à fluide de Create est un périphérique
tank:tank.tanks()donne le fluide et la quantité en mB de chaque cuve (Réservoirs). - Garder un historique. Ajoutez les totaux à un fichier chaque minute avec
fs.append, et tracez la courbe plus tard : voirEnregistreur de données et graphique. - Un écran central. Envoyez les totaux à un autre ordinateur avec
net.send(Mini-ordinateur et plus récents), qui rassemble tous les coffres de la base : voirTableau de bord d'usine.