Panneau de commande tactile
Un mur de moniteurs avec un bouton par machine : un appui commute une fréquence de Liaison de redstone, et les états reviennent après un redémarrage.
Votre usine a grandi : des broyeurs d'un côté, des presses et des mixeurs de l'autre, une ligne de ventilateurs pour le lavage, des perceuses dans la carrière. Chacun a son levier quelque part, et tout arrêter le soir demande une promenade. Ce projet rassemble les interrupteurs sur un écran tactile : un mur de moniteurs dans la salle de contrôle, un gros bouton par machine. Un clic droit sur un bouton démarre ou arrête cette machine par une fréquence de Liaison de redstone, où qu'elle soit dans la base. Les états sont enregistrés dans un fichier : après un redémarrage du serveur, chaque machine revient comme vous l'avez laissée.
C'est la référence pour les écrans cliquables en Brass : dessiner des boutons avec gfx et term, lire les évènements click (e.x, e.y, e.source) et tenir un petit fichier d'état avec fs.
Ce qu'il vous faut
- Un Micro-ordinateur (ou un Ordinateur moderne) : les couleurs rendent le panneau lisible d'un coup d'œil. N'importe quel ordinateur à partir de l'Ordinateur à transistors le fait tourner, en une seule couleur.
- Un mur de Moniteurs LCD (ou de Moniteurs cathodiques), par exemple 3 de large et 2 de haut, contre l'ordinateur. Des moniteurs du même type côte à côte, tournés du même côté, forment un seul écran qui montre le terminal de l'ordinateur. Un clic droit dessus appuie sur l'écran tant que le programme attend des clics ; accroupissez-vous et faites un clic droit pour ouvrir plutôt le terminal.
- Une Liaison de redstone par machine, réglée en réception, à côté du bloc qui commande la machine. Chacune reçoit deux objets dans ses emplacements de fréquence : le programme utilise un objet de la machine et un levier (
Roue de concassage + Levierpour les broyeurs,Presse mécanique + Levierpour les presses...). - Pour les machines de Create, un Embrayage sur l'arbre qui entraîne chaque chaîne, avec la Liaison de redstone à côté.
Vu de côté :
+-------+-------+-------+
| LCD | LCD | LCD | l'écran tactile, tourné vers la salle
+-------+-------+-------+
| LCD | LCD | LCD |
+-------+-------+-------+
| PC | <- l'ordinateur derrière le mur, contre lui
+-------+
À chaque machine : arbre ==[Embrayage]== arbre ==> presse mécanique
[Liaison de redstone, réception : presse mécanique + levier]Un embrayage alimenté débraye son arbre. Pour faire tourner une machine derrière un embrayage, le programme ne doit rien émettre, et pour l'arrêter, émettre un signal. Le réglage embrayage = true inverse le signal pour ces machines. Une lampe, au contraire, s'allume quand elle est alimentée : pas d'embrayage pour l'éclairage.
Le programme, étape par étape
1. Les machines
Une ligne par bouton : le nom affiché, les deux objets de la fréquence, et si la machine passe par un embrayage.
local FICHIER_ETAT = "panneau/etat"
local MACHINES = {
{nom = "Broyeurs", freq = {"create:crushing_wheel", "minecraft:lever"}, embrayage = true},
{nom = "Presses", freq = {"create:mechanical_press", "minecraft:lever"}, embrayage = true},
{nom = "Mixeurs", freq = {"create:mechanical_mixer", "minecraft:lever"}, embrayage = true},
{nom = "Lavage", freq = {"create:encased_fan", "minecraft:lever"}, embrayage = true},
{nom = "Perceuses", freq = {"create:mechanical_drill", "minecraft:lever"}, embrayage = true},
{nom = "Lampes", freq = {"minecraft:redstone_lamp", "minecraft:lever"}},
}
local BL, BH = 23, 4 -- taille d'un bouton, en caractèresLes identifiants d'objets doivent exister : link.set s'arrête avec unknown item 'create:crushing_wheels' sur une faute de frappe. Le programme les vérifie tous au démarrage (étape 3) : une erreur se voit tout de suite.
2. Commuter une machine
link.set émet sur une fréquence depuis l'ordinateur lui-même, sans bloc Liaison de redstone à côté de lui. true vaut une puissance de 15, false vaut 0 :
local dernier = "rien pour l'instant" -- la dernière action, pour la ligne d'état
local function appliquer(m)
local puissance = m.on
if m.embrayage then puissance = not m.on end
link.set(m.freq[1], m.freq[2], puissance)
end
local function regler_machine(m, on, source)
m.on = on
appliquer(m)
local mot = "ARRÊT"
if on then mot = "MARCHE" end
dernier = m.nom .. " " .. mot .. " (" .. source .. ")"
end
local function tout_arreter(source)
for _, m in ipairs(MACHINES) do
m.on = false
appliquer(m)
end
dernier = "tout arrêté (" .. source .. ")"
endUn ordinateur cesse d'émettre sur toutes ses fréquences quand il redémarre ou s'éteint : les embrayages des chaînes Create ne reçoivent alors plus rien, et leurs machines tournent. C'est pourquoi le programme réapplique les états enregistrés dès qu'il démarre.
3. Enregistrer et relire les états
Le fichier d'état est du texte simple, une machine par ligne : Presses=1. string.split découpe le fichier en lignes, et chaque ligne au = :
local function enregistrer()
local lignes = {}
for _, m in ipairs(MACHINES) do
local valeur = "0"
if m.on then valeur = "1" end
table.insert(lignes, m.nom .. "=" .. valeur)
end
fs.write(FICHIER_ETAT, table.concat(lignes, "\n"))
end
local function charger_etats()
local texte = fs.read(FICHIER_ETAT)
if texte == nil then return end
for _, ligne in ipairs(texte:split("\n")) do
local morceaux = ligne:split("=")
for _, m in ipairs(MACHINES) do
if #morceaux == 2 and m.nom == morceaux[1] then m.on = morceaux[2] == "1" end
end
end
endLes machines sont retrouvées par leur nom : vous pouvez réordonner MACHINES ou en ajouter une sans perdre l'état des autres.
local texte = "Broyeurs=1\nPresses=0\nLampes=1"
for _, ligne in ipairs(texte:split("\n")) do
local morceaux = ligne:split("=")
print(morceaux[1], morceaux[2] == "1")
endBroyeurs true Presses false Lampes true
preparer place les boutons sur deux colonnes, charge les états et les applique. Un mauvais identifiant d'objet arrête le programme ici, avec le nom de la machine :
local function preparer()
for i, m in ipairs(MACHINES) do
m.x = 3 + ((i - 1) % 2) * 24
m.y = 3 + ((i - 1) // 2) * 5
m.on = false
end
charger_etats()
for _, m in ipairs(MACHINES) do
local r = pcall(appliquer, m)
if not r.ok then error("machine '" .. m.nom .. "' : " .. r.error) end
end
end4. Dessiner les boutons
Un bouton se dessine en deux couches. En dessous, avec gfx, un rectangle plein, un bord de deux pixels d'épaisseur et un voyant rond. Au-dessus, avec term, le numéro et le nom de la machine, et son état. La couche de pixels est sous le texte : les caractères s'affichent par-dessus le rectangle coloré.
Un caractère fait 6 pixels de large et 9 de haut : le caractère de la colonne c commence au pixel (c - 1) * 6 + 1. Deux petites fonctions changent les positions en caractères en pixels :
local function px(col) return (col - 1) * 6 + 1 end
local function py(ligne) return (ligne - 1) * 9 + 1 end
local function dessiner_bouton(m, numero)
local fond, bord, voyant = "gray", "light_gray", "red"
if m.on then fond, bord, voyant = "green", "lime", "lime" end
local x, y, l, h = px(m.x), py(m.y), BL * 6, BH * 9
gfx.rect(x, y, l, h, fond, true)
gfx.rect(x, y, l, h, bord, 2)
gfx.circle(x + l - 16, y + h // 2, 7, "black", true)
gfx.circle(x + l - 16, y + h // 2, 5, voyant, true)
term.set_cursor(m.x + 2, m.y + 1)
term.set_fg(term.colors.white)
term.write(numero .. " " .. m.nom)
term.set_cursor(m.x + 5, m.y + 2)
if m.on then term.write("MARCHE") else term.write("ARRÊT") end
end
local function dessiner()
term.set_bg(term.colors.black)
term.clear()
gfx.clear()
local actives = 0
for _, m in ipairs(MACHINES) do
if m.on then actives = actives + 1 end
end
term.set_cursor(1, 1)
term.set_bg(term.colors.blue)
term.set_fg(term.colors.white)
term.write(string.format(" %-36s%13s ", "PANNEAU DE COMMANDE", actives .. "/" .. #MACHINES .. " en marche"))
term.set_bg(term.colors.black)
for i, m in ipairs(MACHINES) do dessiner_bouton(m, i) end
gfx.rect(px(3), py(18), 11 * 6, 9, "red", true)
term.set_cursor(4, 18)
term.set_fg(term.colors.white)
term.write("TOUT ARRÊT")
term.set_cursor(16, 18)
term.set_fg(term.colors.light_gray)
term.write("Fait : " .. dernier)
term.set_cursor(3, 19)
term.set_fg(term.colors.gray)
term.write("Cliquez un bouton, ou tapez 1 à 6 (0 : tout).")
end5. Clics et touches
Un évènement click donne le caractère sous le pointeur : e.x (colonne) et e.y (ligne), comptés depuis 1 en haut à gauche, que le clic vienne du terminal ou d'un moniteur. e.source dit lequel ("terminal" ou "monitor"), et la ligne d'état l'affiche. Trouver le bouton sous un clic, c'est un test sur son rectangle :
local function bouton_sous(x, y)
for i, m in ipairs(MACHINES) do
if x >= m.x and x < m.x + BL and y >= m.y and y < m.y + BH then return i end
end
return nil
endLa boucle principale attend n'importe quel évènement. Les clics appuient sur les boutons ; les chiffres tapés dans le terminal font de même, 0 voulant dire « tout arrêter ». Chaque changement est enregistré aussitôt.
preparer()
dessiner()
while true do
local e = os.pull_event()
if e.name == "click" then
local i = bouton_sous(e.x, e.y)
if i then
regler_machine(MACHINES[i], not MACHINES[i].on, e.source)
enregistrer()
dessiner()
elseif e.y == 18 and e.x >= 3 and e.x <= 13 then
tout_arreter(e.source)
enregistrer()
dessiner()
end
elseif e.name == "char" then
local n = tonumber(e.char)
if n == 0 then
tout_arreter("clavier")
enregistrer()
dessiner()
elseif n ~= nil and MACHINES[n] ~= nil then
regler_machine(MACHINES[n], not MACHINES[n].on, "clavier")
enregistrer()
dessiner()
end
end
endLe programme attend avec os.pull_event() sans filtre : c'est ce qui fait d'un clic droit sur le mur de moniteurs un appui sur l'écran (un programme qui attend les évènements "click" fait de même). Voir Évènements pour ce genre de boucle.
Le programme complet
-- Panneau de commande tactile : un bouton par machine sur un mur de
-- moniteurs. Chaque bouton commute une fréquence de Liaison de redstone ; les états
-- survivent à un redémarrage.
local FICHIER_ETAT = "panneau/etat"
local MACHINES = {
-- embrayage = true : la chaîne passe par un embrayage de Create, qui débraye
-- quand il est alimenté, donc « en marche » veut dire aucun signal.
{nom = "Broyeurs", freq = {"create:crushing_wheel", "minecraft:lever"}, embrayage = true},
{nom = "Presses", freq = {"create:mechanical_press", "minecraft:lever"}, embrayage = true},
{nom = "Mixeurs", freq = {"create:mechanical_mixer", "minecraft:lever"}, embrayage = true},
{nom = "Lavage", freq = {"create:encased_fan", "minecraft:lever"}, embrayage = true},
{nom = "Perceuses", freq = {"create:mechanical_drill", "minecraft:lever"}, embrayage = true},
{nom = "Lampes", freq = {"minecraft:redstone_lamp", "minecraft:lever"}},
}
local BL, BH = 23, 4 -- taille d'un bouton, en caractères
local dernier = "rien pour l'instant" -- la dernière action, pour la ligne d'état
-- Machines ------------------------------------------------------------------------------
local function appliquer(m)
local puissance = m.on
if m.embrayage then puissance = not m.on end
link.set(m.freq[1], m.freq[2], puissance)
end
local function regler_machine(m, on, source)
m.on = on
appliquer(m)
local mot = "ARRÊT"
if on then mot = "MARCHE" end
dernier = m.nom .. " " .. mot .. " (" .. source .. ")"
end
local function tout_arreter(source)
for _, m in ipairs(MACHINES) do
m.on = false
appliquer(m)
end
dernier = "tout arrêté (" .. source .. ")"
end
-- Fichier d'état ------------------------------------------------------------------------
local function enregistrer()
local lignes = {}
for _, m in ipairs(MACHINES) do
local valeur = "0"
if m.on then valeur = "1" end
table.insert(lignes, m.nom .. "=" .. valeur)
end
fs.write(FICHIER_ETAT, table.concat(lignes, "\n"))
end
local function charger_etats()
local texte = fs.read(FICHIER_ETAT)
if texte == nil then return end
for _, ligne in ipairs(texte:split("\n")) do
local morceaux = ligne:split("=")
for _, m in ipairs(MACHINES) do
if #morceaux == 2 and m.nom == morceaux[1] then m.on = morceaux[2] == "1" end
end
end
end
local function preparer()
for i, m in ipairs(MACHINES) do
m.x = 3 + ((i - 1) % 2) * 24
m.y = 3 + ((i - 1) // 2) * 5
m.on = false
end
charger_etats()
for _, m in ipairs(MACHINES) do
local r = pcall(appliquer, m)
if not r.ok then error("machine '" .. m.nom .. "' : " .. r.error) end
end
end
-- Écran -----------------------------------------------------------------------------------
local function px(col) return (col - 1) * 6 + 1 end
local function py(ligne) return (ligne - 1) * 9 + 1 end
local function dessiner_bouton(m, numero)
local fond, bord, voyant = "gray", "light_gray", "red"
if m.on then fond, bord, voyant = "green", "lime", "lime" end
local x, y, l, h = px(m.x), py(m.y), BL * 6, BH * 9
gfx.rect(x, y, l, h, fond, true)
gfx.rect(x, y, l, h, bord, 2)
gfx.circle(x + l - 16, y + h // 2, 7, "black", true)
gfx.circle(x + l - 16, y + h // 2, 5, voyant, true)
term.set_cursor(m.x + 2, m.y + 1)
term.set_fg(term.colors.white)
term.write(numero .. " " .. m.nom)
term.set_cursor(m.x + 5, m.y + 2)
if m.on then term.write("MARCHE") else term.write("ARRÊT") end
end
local function dessiner()
term.set_bg(term.colors.black)
term.clear()
gfx.clear()
local actives = 0
for _, m in ipairs(MACHINES) do
if m.on then actives = actives + 1 end
end
term.set_cursor(1, 1)
term.set_bg(term.colors.blue)
term.set_fg(term.colors.white)
term.write(string.format(" %-36s%13s ", "PANNEAU DE COMMANDE", actives .. "/" .. #MACHINES .. " en marche"))
term.set_bg(term.colors.black)
for i, m in ipairs(MACHINES) do dessiner_bouton(m, i) end
gfx.rect(px(3), py(18), 11 * 6, 9, "red", true)
term.set_cursor(4, 18)
term.set_fg(term.colors.white)
term.write("TOUT ARRÊT")
term.set_cursor(16, 18)
term.set_fg(term.colors.light_gray)
term.write("Fait : " .. dernier)
term.set_cursor(3, 19)
term.set_fg(term.colors.gray)
term.write("Cliquez un bouton, ou tapez 1 à 6 (0 : tout).")
end
local function bouton_sous(x, y)
for i, m in ipairs(MACHINES) do
if x >= m.x and x < m.x + BL and y >= m.y and y < m.y + BH then return i end
end
return nil
end
-- Boucle principale -------------------------------------------------------------------------
preparer()
dessiner()
while true do
local e = os.pull_event()
if e.name == "click" then
local i = bouton_sous(e.x, e.y)
if i then
regler_machine(MACHINES[i], not MACHINES[i].on, e.source)
enregistrer()
dessiner()
elseif e.y == 18 and e.x >= 3 and e.x <= 13 then
tout_arreter(e.source)
enregistrer()
dessiner()
end
elseif e.name == "char" then
local n = tonumber(e.char)
if n == 0 then
tout_arreter("clavier")
enregistrer()
dessiner()
elseif n ~= nil and MACHINES[n] ~= nil then
regler_machine(MACHINES[n], not MACHINES[n].on, "clavier")
enregistrer()
dessiner()
end
end
endÀ quoi ressemble le panneau
Le code de dessin du programme, avec quatre machines en marche et le dernier appui fait sur le mur de moniteurs :
local MACHINES = {
{nom = "Broyeurs", on = true},
{nom = "Presses", on = true},
{nom = "Mixeurs", on = false},
{nom = "Lavage", on = true},
{nom = "Perceuses", on = false},
{nom = "Lampes", on = true},
}
local BL, BH = 23, 4
local dernier = "Lavage MARCHE (monitor)"
for i, m in ipairs(MACHINES) do
m.x = 3 + ((i - 1) % 2) * 24
m.y = 3 + ((i - 1) // 2) * 5
end
local function px(col) return (col - 1) * 6 + 1 end
local function py(ligne) return (ligne - 1) * 9 + 1 end
local function dessiner_bouton(m, numero)
local fond, bord, voyant = "gray", "light_gray", "red"
if m.on then fond, bord, voyant = "green", "lime", "lime" end
local x, y, l, h = px(m.x), py(m.y), BL * 6, BH * 9
gfx.rect(x, y, l, h, fond, true)
gfx.rect(x, y, l, h, bord, 2)
gfx.circle(x + l - 16, y + h // 2, 7, "black", true)
gfx.circle(x + l - 16, y + h // 2, 5, voyant, true)
term.set_cursor(m.x + 2, m.y + 1)
term.set_fg(term.colors.white)
term.write(numero .. " " .. m.nom)
term.set_cursor(m.x + 5, m.y + 2)
if m.on then term.write("MARCHE") else term.write("ARRÊT") end
end
local function dessiner()
term.set_bg(term.colors.black)
term.clear()
gfx.clear()
local actives = 0
for _, m in ipairs(MACHINES) do
if m.on then actives = actives + 1 end
end
term.set_cursor(1, 1)
term.set_bg(term.colors.blue)
term.set_fg(term.colors.white)
term.write(string.format(" %-36s%13s ", "PANNEAU DE COMMANDE", actives .. "/" .. #MACHINES .. " en marche"))
term.set_bg(term.colors.black)
for i, m in ipairs(MACHINES) do dessiner_bouton(m, i) end
gfx.rect(px(3), py(18), 11 * 6, 9, "red", true)
term.set_cursor(4, 18)
term.set_fg(term.colors.white)
term.write("TOUT ARRÊT")
term.set_cursor(16, 18)
term.set_fg(term.colors.light_gray)
term.write("Fait : " .. dernier)
term.set_cursor(3, 19)
term.set_fg(term.colors.gray)
term.write("Cliquez un bouton, ou tapez 1 à 6 (0 : tout).")
end
dessiner()
L'essayer
- Posez une Liaison de redstone à côté d'une lampe, réglez-la en réception, et mettez une lampe à redstone et un levier dans ses emplacements de fréquence. Lancez le programme et appuyez sur 6 Lampes : la lampe s'allume ; appuyez de nouveau : elle s'éteint.
- Mettez un Embrayage sur l'arbre d'une Presse mécanique, une Liaison de redstone réceptrice à côté avec une presse mécanique et un levier. Appuyez sur 2 Presses : la presse tourne quand le bouton est vert, s'arrête quand il est gris.
- Mettez quelques machines en marche, puis redémarrez l'ordinateur (Ctrl+R dans le terminal) : le panneau revient avec les mêmes boutons allumés, et les machines suivent.
- Regardez les états enregistrés :
> cat panneau/etat Broyeurs=1 Presses=1 Mixeurs=0 Lavage=1 Perceuses=0 Lampes=1
Variantes
- Plus de boutons. Sur un Ordinateur moderne (64 x 24), trois colonnes de 20 caractères et quatre rangées donnent douze boutons. Calculez
m.xetm.yd'après la taille de l'écran donnée parterm.get_size()plutôt qu'avec des nombres fixes. - Un retour des machines. Une machine peut répondre sur sa propre fréquence : une Liaison de redstone en émission à côté d'un capteur (un Détecteur de seuil sur un Coffre à objet renforcé, un capteur de lumière du jour...).
link.get(a, b)la lit, et un évènementlinkarrive à chaque changement : coloriez le voyant du bouton avec ce que fait vraiment la machine. - Des minuteries. Ajoutez un bouton « Lampes jusqu'à l'aube » qui allume l'éclairage et surveille l'heure ; quand
os.day_time()repasse par 0 (6 h du matin), éteignez-le. VoirÉclairage jour et nuit. - Un panneau à distance. Envoyez les appuis à un autre ordinateur avec
net.sendau lieu delink.set, pour des machines hors de portée des Liaisons de redstone : voirTélécommande par le réseau. - Une confirmation. Pour les machines dangereuses (les perceuses de la carrière), faites passer le bouton au jaune au premier appui, et ne commutez la machine qu'à un second appui dans les trois secondes, avec
os.start_timer(3).